Communicating with DUALSHOCK 4 via Python (Node-RED)
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.
▼Previous related articles:
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:
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.


