Communicating with DUALSHOCK 4 via Python (Node-RED)

Info

This article is translated from Japanese to English.

https://404background.com/program/python-dualshock-4/

Introduction

In this post, I tried communicating with a PS4 controller (DUALSHOCK 4) using Python. By using the "python-venv" node I developed, you can execute Python code directly within Node-RED. I set it up to communicate with a DUALSHOCK 4 and connected it to dashboard nodes to visualize the controller's state.

▼I wrote about the development history of the python-venv node on Qiita (Japanese):

https://qiita.com/background/items/d2e05e8d85427761a609

My goal is to eventually use this to operate robots.
▼I am using an official Sony controller.

ソニー・インタラクティブエンタテインメント
¥5,100 (2026/02/21 20:12時点 | Amazon調べ)

▼Previous related articles:

ESP32を使って、PS4コントローラとBluetooth接続する

はじめに  今回はESP32を使って、PS4のコントローラとのBluetooth接続をしました。ロボコンで使っているところを見たことがあります。最近はSwitchのコントローラで操作…

小型の二輪ロボットをPS4のコントローラで操作する(ESP32)

はじめに  これまでの記事でESP32とPS4のコントローラをBluetoothで接続することができたので、今回はコントローラで実際にロボットを制御してみました。以前製作した小…

Setting Up the Environment

First, we need to set up an environment where both Node-RED and Python can run.

▼For Node-RED installation:

https://nodered.org/docs/getting-started/local

▼For the dashboard nodes: After installing Node-RED, you can install these via "Manage palette."

https://flows.nodered.org/node/node-red-dashboard

Please ensure Python is installed on your system beforehand.
▼The python-venv node: This can also be installed via "Manage palette."

https://flows.nodered.org/node/@background404/node-red-contrib-python-venv

Receiving Data with pygame

I consulted ChatGPT for the code. Several packages were suggested, but I chose "pygame" because it is cross-platform and reliable.

▼I placed the pip and venv nodes in Node-RED.

▼I configured the pip node to install pygame and executed it.

First, I tested the following basic code:

import pygame

# Initialization
pygame.init()
pygame.joystick.init()

# Check for connected controllers
if pygame.joystick.get_count() == 0:
    print("No joystick connected")
    exit()

joystick = pygame.joystick.Joystick(0)
joystick.init()

print(f"Controller: {joystick.get_name()} connected")

# Event loop
running = True
while running:
    pygame.event.pump()  # Update events

    # Get button states
    for i in range(joystick.get_numbuttons()):
        if joystick.get_button(i):
            print(f"Button {i} pressed")

    # Get axis states
    for i in range(joystick.get_numaxes()):
        axis_value = joystick.get_axis(i)
        print(f"Axis {i}: {axis_value:.2f}")

    pygame.time.wait(100)  # Reduce CPU load

pygame.quit()

▼You can write the code directly inside the venv node.

Since we want to receive the output continuously, I enabled the "Continuous" option.
▼When executed, the output appeared as string data in the debug window.

However, raw strings are difficult to process, so I modified the script to output data in JSON format.

import pygame
import json
import time

# Initialization
pygame.init()
pygame.joystick.init()

# Check for connected joysticks
if pygame.joystick.get_count() == 0:
    print("No joystick connected")
    exit()

joystick = pygame.joystick.Joystick(0)
joystick.init()

print(f"Controller: {joystick.get_name()} connected")

# Main loop
running = True
while running:
    pygame.event.pump()

    input_data = {
        "buttons": {},
        "axes": {},
        "hats": {},
        "timestamp": time.time()
    }

    for i in range(joystick.get_numbuttons()):
        input_data["buttons"][f"button_{i}"] = joystick.get_button(i)

    for i in range(joystick.get_numaxes()):
        input_data["axes"][f"axis_{i}"] = round(joystick.get_axis(i), 3)

    for i in range(joystick.get_numhats()):
        input_data["hats"][f"hat_{i}"] = joystick.get_hat(i)

    json_output = json.dumps(input_data, indent=4)
    print(json_output)

    pygame.time.wait(100)

pygame.quit()

▼I added a new node and ran it.

▼The output is now in JSON format, though it's still technically a string at this stage.

Displaying Values on the Dashboard

Checking inputs via the debug window is tedious, so let's display them on a dashboard.
First, we use the json node to parse the string into an object.
▼Added a json node.

▼Now you can see it's recognized as an Object in the debug window.

Next, I used text nodes to extract values by specifying the keys under msg.payload.
▼Added text nodes.

I configured them to access the properties I identified in the debug window.
▼In the "Value format" field, I'm accessing the buttons property of msg.payload.

I set up other properties similarly and checked the dashboard.
▼Since I just displayed the raw values, the list became extremely long.

I noticed that "hats" didn't change at all, and the button names (button_0, button_1, etc.) were hard to understand. I decided to fix the mapping.

▼I changed the display to be more intuitive.

Here is the updated program. While testing, I found some incorrect button mappings, so I corrected them as well.

import pygame
import json
import time

# DUALSHOCK 4 Button Mapping
DS4_BUTTON_MAP = {
    0: "cross",       # X button
    1: "circle",      # O button
    2: "square",      # Square button
    3: "triangle",    # Triangle button
    9: "L1",
    10: "R1",
    4: "share",
    6: "options",
    7: "L3",
    8: "R3",
    5: "ps",          # PS Button
    15: "touchpad",   # Touchpad click
    11: "dpad_up",
    14: "dpad_right",
    12: "dpad_down",
    13: "dpad_left"
}

# DUALSHOCK 4 Axis/Trigger Mapping
DS4_AXIS_MAP = {
    0: "left_stick_x",
    1: "left_stick_y",
    2: "right_stick_x",
    3: "right_stick_y",
    4: "L2_trigger",
    5: "R2_trigger"
}

# (Initialization and Main Loop logic same as above, but using the maps)

After confirming that the property names and values matched correctly, I built a flow to display everything nicely.
▼Here is the final flow.

I parse the JSON, use change nodes to assign specific property values to msg.payload, and then pass them to text nodes. While you can access properties directly in text nodes, assigning them to msg.payload makes it easier to connect to other nodes later.

▼The dashboard now looks like this:

I confirmed that the values change in real-time according to the controller inputs. Since numbers alone are hard to read, I used gauge nodes for values that range from -1 to 1.

▼Final Node-RED Flow JSON:

[{"id":"22052b56e60c6b59","type":"pip","z":"d6fad9e77ecc8414","venvconfig":"4657b6fbdbaf6f7e","name":"","arg":"pygame","action":"install","tail":false,"x":950,"y":1600,"wires":[["0c72442788c972a4"]]},{"id":"db0c6abf73dd7ddd","type":"inject","z":"d6fad9e77ecc8414","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":810,"y":1600,"wires":[["22052b56e60c6b59"]]},{"id":"0c72442788c972a4","type":"debug","z":"d6fad9e77ecc8414","name":"debug 372","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1110,"y":1600,"wires":[]},{"id":"0b5e3146dcca5a5d","type":"inject","z":"d6fad9e77ecc8414","name":"","props":[],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":810,"y":1660,"wires":[["24d547a46a258220"]]},{"id":"ca1bfe1d52949746","type":"debug","z":"d6fad9e77ecc8414","name":"debug 374","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1250,"y":1660,"wires":[]},{"id":"eff003e388bbf9ea","type":"json","z":"d6fad9e77ecc8414","name":"","property":"payload","action":"","pretty":false,"x":1090,"y":1660,"wires":[["ca1bfe1d52949746","491b7850367de634","b4597429d8641acb"]]},{"id":"51f6c7fef04fb561","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.axes.left_stick_x","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":1780,"wires":[["39761b80535b3527"]]},{"id":"5b7f35ec662a5e20","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.axes.left_stick_y","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":1820,"wires":[["3ce2279ab30c4ce3"]]},{"id":"8f4e6d55a7b038bb","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.axes.right_stick_x","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":1860,"wires":[["d712775e6b8074bd"]]},{"id":"a10d0facc73ff82b","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.axes.right_stick_y","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":1900,"wires":[["76d35c2ab7bfa619"]]},{"id":"3e63613dc47dd67e","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.axes.L2_trigger","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":1940,"wires":[["81258300e2a180ca"]]},{"id":"c46e64b54823dad9","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.axes.R2_trigger","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":1980,"wires":[["8a00aa6ab2b170be"]]},{"id":"24d547a46a258220","type":"venv","z":"d6fad9e77ecc8414","venvconfig":"4657b6fbdbaf6f7e","name":"","code":"import pygame\nimport json\nimport time\n\n# DualShock 4 のボタンマッピング\nDS4_BUTTON_MAP = {\n    0: \"cross\",       # ×ボタン\n    1: \"circle\",      # ○ボタン\n    2: \"square\",      # □ボタン\n    3: \"triangle\",    # △ボタン\n    9: \"L1\",\n    10: \"R1\",\n    4: \"share\",\n    6: \"options\",\n    7: \"L3\",\n    8: \"R3\",\n    5: \"ps\",         # PSボタン\n    15: \"touchpad\",   # タッチパッドボタン\n    11: \"dpad_up\",    # 十字キー 上\n    14: \"dpad_right\", # 十字キー 右\n    12: \"dpad_down\",  # 十字キー 下\n    13: \"dpad_left\"   # 十字キー 左\n}\n\n# DualShock 4 のスティック・トリガーのマッピング\nDS4_AXIS_MAP = {\n    0: \"left_stick_x\",\n    1: \"left_stick_y\",\n    2: \"right_stick_x\",\n    3: \"right_stick_y\",\n    4: \"L2_trigger\",\n    5: \"R2_trigger\"\n}\n\n# 初期化\npygame.init()\npygame.joystick.init()\n\n# ジョイスティックの取得\nif pygame.joystick.get_count() == 0:\n    print(\"ジョイスティックが接続されていません\")\n    exit()\n\njoystick = pygame.joystick.Joystick(0)\njoystick.init()\nprint(f\"コントローラー: {joystick.get_name()} が接続されました\")\n\n# メインループ\nrunning = True\nwhile running:\n    pygame.event.pump()  # イベント更新\n\n    # 入力データを格納\n    input_data = {\n        \"buttons\": {},\n        \"axes\": {}\n    }\n\n    # ボタンの状態取得\n    for i in range(joystick.get_numbuttons()):\n        button_name = DS4_BUTTON_MAP.get(i, f\"button_{i}\")  # 未知のボタンは button_X 形式に\n        input_data[\"buttons\"][button_name] = joystick.get_button(i)\n\n    # 軸(スティック & トリガー)状態取得\n    for i in range(joystick.get_numaxes()):\n        axis_name = DS4_AXIS_MAP.get(i, f\"axis_{i}\")  # 未知の軸は axis_X 形式に\n        input_data[\"axes\"][axis_name] = round(joystick.get_axis(i), 3)\n\n    # JSON出力\n    json_output = json.dumps(input_data, indent=4)\n    print(json_output)\n\n    pygame.time.wait(100)  # CPU負荷を抑えるために 100ms スリープ\n\npygame.quit()\n","continuous":true,"x":950,"y":1660,"wires":[["eff003e388bbf9ea"]]},{"id":"4f06a6b1dc5d680a","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.cross","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":1600,"wires":[["d6ec5892bc127fe9"]]},{"id":"d6ec5892bc127fe9","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"Cross","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":1600,"wires":[]},{"id":"1a6380332cf5cd52","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.circle","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":1640,"wires":[["9cc87a9c3e6f0035"]]},{"id":"9cc87a9c3e6f0035","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"Circle","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":1640,"wires":[]},{"id":"b44aa7c1d66b79b8","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.square","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":1680,"wires":[["1f57f89452d3afe7"]]},{"id":"1f57f89452d3afe7","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"Square","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1740,"y":1680,"wires":[]},{"id":"0f04b5cd85d1395e","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.triangle","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":1720,"wires":[["46f52d05fa3c247a"]]},{"id":"46f52d05fa3c247a","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"Triangle","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1740,"y":1720,"wires":[]},{"id":"91077cd2bba8529e","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.ps","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":1760,"wires":[["1a75d59c7a27052a"]]},{"id":"1a75d59c7a27052a","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":0,"height":0,"name":"","label":"PS","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":1760,"wires":[]},{"id":"2e60f14fbe23c3ce","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.share","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":1800,"wires":[["aa67e9490b49afb8"]]},{"id":"aa67e9490b49afb8","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":0,"height":0,"name":"","label":"Share","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":1800,"wires":[]},{"id":"c662c206f9937b74","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.options","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":1840,"wires":[["a54da400d2fea125"]]},{"id":"a54da400d2fea125","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":0,"height":0,"name":"","label":"Options","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1740,"y":1840,"wires":[]},{"id":"31e109f6ff5877da","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.L3","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":1880,"wires":[["0c3040e3a045dde5"]]},{"id":"0c3040e3a045dde5","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"L3","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":1880,"wires":[]},{"id":"98b3863756dff0d4","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.R3","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":1920,"wires":[["75885030a7baedc9"]]},{"id":"75885030a7baedc9","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"R3","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":1920,"wires":[]},{"id":"330ecca51674ad7b","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.L1","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":1960,"wires":[["2c5e7d6378b5313d"]]},{"id":"2c5e7d6378b5313d","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"L1","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":1960,"wires":[]},{"id":"9f81bcc020d2d0aa","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.R1","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":2000,"wires":[["c0e5db9534442249"]]},{"id":"c0e5db9534442249","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"R1","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":2000,"wires":[]},{"id":"7332be9e450c6c51","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.dpad_up","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":2040,"wires":[["ac98c870a5f214d2"]]},{"id":"ac98c870a5f214d2","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"Up","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":2040,"wires":[]},{"id":"439b65268ed931df","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.dpad_down","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":2080,"wires":[["066bb1ab3979c7ae"]]},{"id":"066bb1ab3979c7ae","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"Down","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":2080,"wires":[]},{"id":"3c27694e8cdd4289","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.dpad_left","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":2120,"wires":[["4dd05ee6ec60fadb"]]},{"id":"4dd05ee6ec60fadb","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"Left","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":2120,"wires":[]},{"id":"86d8287dcc58e2a2","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.dpad_right","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":2160,"wires":[["91fd7eba418305a4"]]},{"id":"91fd7eba418305a4","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":"3","height":"1","name":"","label":"Right","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1730,"y":2160,"wires":[]},{"id":"a6dcfadb9bf8838c","type":"change","z":"d6fad9e77ecc8414","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.buttons.touchpad","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1560,"y":2200,"wires":[["c5f5d5bb1ed13491"]]},{"id":"c5f5d5bb1ed13491","type":"ui_text","z":"d6fad9e77ecc8414","group":"227d3ca77163ce82","order":0,"width":0,"height":0,"name":"","label":"TouchPad","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1740,"y":2200,"wires":[]},{"id":"39761b80535b3527","type":"ui_gauge","z":"d6fad9e77ecc8414","name":"","group":"62a28eb47ad4b20e","order":6,"width":0,"height":0,"gtype":"compass","title":"Left X","label":"","format":"{{value}}","min":"1","max":"-1","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","diff":false,"className":"","x":1170,"y":1780,"wires":[]},{"id":"3ce2279ab30c4ce3","type":"ui_gauge","z":"d6fad9e77ecc8414","name":"","group":"62a28eb47ad4b20e","order":6,"width":0,"height":0,"gtype":"compass","title":"Left Y","label":"","format":"{{value}}","min":"-1","max":"1","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","diff":false,"className":"","x":1170,"y":1820,"wires":[]},{"id":"d712775e6b8074bd","type":"ui_gauge","z":"d6fad9e77ecc8414","name":"","group":"6e28d9e8ae4ff93d","order":6,"width":0,"height":0,"gtype":"compass","title":"Right X","label":"","format":"{{value}}","min":"1","max":"-1","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","diff":false,"className":"","x":1180,"y":1860,"wires":[]},{"id":"76d35c2ab7bfa619","type":"ui_gauge","z":"d6fad9e77ecc8414","name":"","group":"6e28d9e8ae4ff93d","order":6,"width":0,"height":0,"gtype":"compass","title":"Right Y","label":"","format":"{{value}}","min":"-1","max":"1","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","diff":false,"className":"","x":1180,"y":1900,"wires":[]},{"id":"81258300e2a180ca","type":"ui_gauge","z":"d6fad9e77ecc8414","name":"","group":"62a28eb47ad4b20e","order":6,"width":0,"height":0,"gtype":"gage","title":"L2","label":"","format":"{{value}}","min":"-1","max":"1","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","diff":false,"className":"","x":1170,"y":1940,"wires":[]},{"id":"8a00aa6ab2b170be","type":"ui_gauge","z":"d6fad9e77ecc8414","name":"","group":"6e28d9e8ae4ff93d","order":6,"width":0,"height":0,"gtype":"gage","title":"R2","label":"","format":"{{value}}","min":"-1","max":"1","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","diff":false,"className":"","x":1170,"y":1980,"wires":[]},{"id":"491b7850367de634","type":"junction","z":"d6fad9e77ecc8414","x":780,"y":1880,"wires":[["51f6c7fef04fb561","8f4e6d55a7b038bb","5b7f35ec662a5e20","a10d0facc73ff82b","3e63613dc47dd67e","c46e64b54823dad9"]]},{"id":"b4597429d8641acb","type":"junction","z":"d6fad9e77ecc8414","x":1340,"y":1880,"wires":[["4f06a6b1dc5d680a","1a6380332cf5cd52","b44aa7c1d66b79b8","0f04b5cd85d1395e","91077cd2bba8529e","2e60f14fbe23c3ce","c662c206f9937b74","31e109f6ff5877da","98b3863756dff0d4","330ecca51674ad7b","9f81bcc020d2d0aa","7332be9e450c6c51","439b65268ed931df","3c27694e8cdd4289","86d8287dcc58e2a2","a6dcfadb9bf8838c"]]},{"id":"4657b6fbdbaf6f7e","type":"venv-config","venvname":"pyenv","version":"default"},{"id":"227d3ca77163ce82","type":"ui_group","name":"Buttons","tab":"f6642a80f4f5605a","order":2,"disp":true,"width":"6","collapse":false,"className":""},{"id":"62a28eb47ad4b20e","type":"ui_group","name":"Left","tab":"f6642a80f4f5605a","order":3,"disp":true,"width":"6","collapse":false,"className":""},{"id":"6e28d9e8ae4ff93d","type":"ui_group","name":"Right","tab":"f6642a80f4f5605a","order":4,"disp":true,"width":"6","collapse":false,"className":""},{"id":"f6642a80f4f5605a","type":"ui_tab","name":"Controller","icon":"dashboard","disabled":false,"hidden":false}]

▼Final Dashboard View:

I tested the operation while moving the sticks.
▼Despite the 100ms sleep in the program, it operated smoothly.

▼I'd like the buttons to display non-numeric values as well.

In situations where communication speed is critical, it seems necessary to adjust the sleep settings.

Finally

Now, Node-RED can successfully receive DUALSHOCK 4 inputs. By communicating with other software, this opens up many possibilities.

As I researched previously, the standard ESP32 supports Bluetooth Classic (which DUALSHOCK 4 uses), but the ESP32C3 only supports BLE. By using Node-RED as a relay, I believe I can now use a DUALSHOCK 4 to control an ESP32C3.

▼I researched this issue in this article:

A Little Research: ESP32C3 and Bluetooth Standards

Info This article is translated from Japanese to English. Introduction On this website, I frequently summarize how to connect an ESP32 with a PS4 controller (D…

I'm also thinking it would be very convenient to package this DUALSHOCK 4 communication as a dedicated Node-RED node. A custom node would allow for multiple output pins, eliminating the need to place a massive amount of change nodes like I did this time.

Leave a Reply

Your email address will not be published. Required fields are marked *