PythonでDUALSHOCK 4と通信する(Node-RED)

はじめに

 今回はPythonでPS4のコントローラであるDUALSHOCK 4と通信してみました。

 私が開発したpython-venvノードを利用すると、Node-REDでPythonのコードを実行できます。DUALSHOCK 4と通信して、さらにdashboardノードにつなげて状態を表示できるようにしてみました。

▼python-venvノードについては、Qiitaに開発の変遷を書きました。

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

 ロボットを操作できるようにしていきたいところです。

▼私は純正品のコントローラを利用しています。

▼以前の記事はこちら

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

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

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

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

環境を構築する

 Node-REDとPythonを実行できる環境を構築します。

▼Node-REDのインストールについてはこちら

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

▼dashboardノードについてはこちら。Node-REDのインストール後、パレットの管理からインストールできます。

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

 Pythonは事前にインストールしておいてください。

▼python-venvノードはこちら。こちらもパレットの管理からインストールできます。

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

pygameで受信してみる

 コードはChatGPTに聞いてみました。いくつかのパッケージが提案されたのですが、今回は異なるOSでも利用できそうなpygameを使いました。

▼Node-REDでpipノードとvenvノードを配置しました。

▼pipノードではpygameをインストールするように設定して、実行しました。

 まずは以下のコードを試してみました。

import pygame

# 初期化
pygame.init()
pygame.joystick.init()

# 接続されているコントローラーを取得
if pygame.joystick.get_count() == 0:
    print("ジョイスティックが接続されていません")
    exit()

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

print(f"コントローラー: {joystick.get_name()} が接続されました")

# イベントループ
running = True
while running:
    pygame.event.pump()  # イベントを更新

    # ボタンの状態を取得
    for i in range(joystick.get_numbuttons()):
        if joystick.get_button(i):
            print(f"ボタン {i} が押されました")

    # 軸の状態を取得
    for i in range(joystick.get_numaxes()):
        axis_value = joystick.get_axis(i)
        print(f"軸 {i}: {axis_value:.2f}")

    pygame.time.wait(100)  # CPU負荷を抑える

pygame.quit()

▼venvノードの中にコードを書くことができます。

 出力を順次受け取るので、Continuousを有効にしておきます。

▼実行すると、文字列として出力されました。

 このままでは受信後のデータの扱いが難しいので、JSON形式で出力するようにしてもらいました。

import pygame
import json
import time

# 初期化
pygame.init()
pygame.joystick.init()

# 接続されているジョイスティックを取得
if pygame.joystick.get_count() == 0:
    print("ジョイスティックが接続されていません")
    exit()

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

print(f"コントローラー: {joystick.get_name()} が接続されました")

# メインループ
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)  # 小数点3桁に丸める

    # 十字キー(POV Hat)の状態を取得
    for i in range(joystick.get_numhats()):
        input_data["hats"][f"hat_{i}"] = joystick.get_hat(i)

    # JSONとして出力
    json_output = json.dumps(input_data, indent=4)
    print(json_output)

    pygame.time.wait(100)  # CPU負荷を抑えるために 100ms スリープ

pygame.quit()

▼新しくノードを追加して実行してみました。

▼文字列のままですが、JSON形式になって出力されました。

ダッシュボードに値を表示する

 このままでは入力の確認が難しいので、ダッシュボードに表示してみます。

 まずはJSON形式にパースします。

▼jsonノードを入れました。

▼Objectになっていることが分かります。

 msg.payloadの一つ下の階層のキーを指定して、値を取り出します。

▼textノードを追加しました。

 先程デバッグウィンドウで確認していた値でアクセスするように設定します。

▼Value formatの中で、msg.payloadのbuttonsプロパティにアクセスしています。

 他のプロパティも同様に設定します。ダッシュボード画面で確認してみました。

▼一つ下の階層の値を表示しただけなので、ものすごく長くなっています。

 よく見るとhatsは全然変化せず、ボタンの名前は分かりにくかったので修正しました。

▼以下のように表示されるよう変更しました。

 修正後のプログラムは以下です。コントローラを操作しながら確認していると、ボタンマッピングを間違えているところがあったので修正しました。

import pygame
import json
import time

# DualShock 4 のボタンマッピング
DS4_BUTTON_MAP = {
    0: "cross",       # ×ボタン
    1: "circle",      # ○ボタン
    2: "square",      # □ボタン
    3: "triangle",    # △ボタン
    9: "L1",
    10: "R1",
    4: "share",
    6: "options",
    7: "L3",
    8: "R3",
    5: "ps",         # PSボタン
    15: "touchpad",   # タッチパッドボタン
    11: "dpad_up",    # 十字キー 上
    14: "dpad_right", # 十字キー 右
    12: "dpad_down",  # 十字キー 下
    13: "dpad_left"   # 十字キー 左
}

# DualShock 4 のスティック・トリガーのマッピング
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"
}

# 初期化
pygame.init()
pygame.joystick.init()

# ジョイスティックの取得
if pygame.joystick.get_count() == 0:
    print("ジョイスティックが接続されていません")
    exit()

joystick = pygame.joystick.Joystick(0)
joystick.init()
print(f"コントローラー: {joystick.get_name()} が接続されました")

# メインループ
running = True
while running:
    pygame.event.pump()  # イベント更新

    # 入力データを格納
    input_data = {
        "buttons": {},
        "axes": {}
    }

    # ボタンの状態取得
    for i in range(joystick.get_numbuttons()):
        button_name = DS4_BUTTON_MAP.get(i, f"button_{i}")  # 未知のボタンは button_X 形式に
        input_data["buttons"][button_name] = joystick.get_button(i)

    # 軸(スティック & トリガー)状態取得
    for i in range(joystick.get_numaxes()):
        axis_name = DS4_AXIS_MAP.get(i, f"axis_{i}")  # 未知の軸は axis_X 形式に
        input_data["axes"][axis_name] = round(joystick.get_axis(i), 3)

    # JSON出力
    json_output = json.dumps(input_data, indent=4)
    print(json_output)

    pygame.time.wait(100)  # CPU負荷を抑えるために 100ms スリープ

pygame.quit()

 プロパティ名と値が対応していることは確認できたので、さらにそれぞれの値をダッシュボードに表示できるようにしました。

▼以下のフローを作成しました。

 JSONノードでパースして、各プロパティの値をchangeノードでmsg.payloadに代入してから、textノードに渡しています。

 textノードでプロパティにアクセスしてもいいのですが、msg.payloadに一旦代入しておくことで、他のノードと接続しやすくしています。

▼ダッシュボードでは以下のように表示されました。

 入力に応じて値が変化することを確認できました。数値だけだと見づらいので、他のノードを使って見やすくしてみました。

 最終的に以下のような形にまとめました。

▼-1~1で値が変化するものは、gaugeノードで表示するようにしました。

[{"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}]

▼以下のように表示されました。

 実際にコントローラを操作しながら確認してみました。

▼プログラムで100msのスリープが入っていましたが、問題なく動作していました。

▼ボタンも数値ではない表示にしたいところですね。

 通信速度が重要な場面だと、スリープを調整する必要がありそうです。

最後に

 Node-REDでDUALSHOCK 4の入力を受け付けることができるようになりました。さらに他のソフトウェアと通信することで、いろいろ使えそうです。

 ESP32はDUALSHOCK 4と同じBluetooth Classicに対応していますが、ESP32C3はBLEのみでBluetooth Classicに対応していないという問題がありました。Node-REDで中継することで、ESP32C3でもDUALSHOCK 4と通信することができそうだなと考えています。

▼以下の記事で調べたことがあります。

ちょっと調べもの:ESP32C3とBluetoothの規格

はじめに  このホームページでは、度々ESP32とPS4のコントローラ(DUALSHOCK4)をBluetoothで接続する方法についてまとめています。今回の記事は、ESP32C3でもPS4のコン…

 また、ノードとしてDUALSHOCK 4と通信できるようにすると、便利に使えそうだなと思っています。ノードなら出力を複数のピンで指定することができるので、今回のようにchangeノードを大量に配置しないで済みます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です