XIAO ESP32C3を使ってみる その6(DUALSHOCK 4との通信、Node-RED)
はじめに
今回はDUALSHOCK 4でXIAO ESP32C3を用いた小型ロボットを操作してみました。
以前調べていたときに、DUALSHOCK 4とXIAO ESP32C3はBluetoothの規格が違うので、直接通信できないという結論になっていました。
▼こちらの記事で調べていました。Bluetooth ClassicとBLEの違いです。
最近開発した、Node-REDでDUALSHOCK 4と通信できるdualshock4ノードを使うと、Node-REDで中継して通信できるのではないかということで試してみました。
▼dualshock4ノードについてはこちら
▼以前の記事はこちら
Node-RED側の設定
以前開発したdualshock4ノードでDUALSHOCK 4の入力を受け取り、MQTT通信で送信します。
RスティックとLスティックのY軸の値に応じて車輪が回転するようにするのですが、出力値は-1~1です。マイコンには0~180の値の範囲で送信するので、rangeノードを使って変換します。
▼ピンが多いので複雑に見えますが、送信しているのは下のmqtt outノードです。

[{"id":"6d44dbded6b925e9","type":"inject","z":"d6fad9e77ecc8414","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":2330,"y":460,"wires":[["1b99baa42461afc5"]]},{"id":"e448101471980908","type":"inject","z":"d6fad9e77ecc8414","name":"Finish","props":[{"p":"kill","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":2330,"y":500,"wires":[["1b99baa42461afc5"]]},{"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":2930,"y":400,"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":2930,"y":440,"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":2940,"y":480,"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":2940,"y":520,"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":2930,"y":560,"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":2930,"y":600,"wires":[]},{"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":2770,"y":260,"wires":[]},{"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":2770,"y":300,"wires":[]},{"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":2780,"y":340,"wires":[]},{"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":2780,"y":380,"wires":[]},{"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":2770,"y":460,"wires":[]},{"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":2770,"y":420,"wires":[]},{"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":2780,"y":500,"wires":[]},{"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":2770,"y":540,"wires":[]},{"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":2770,"y":580,"wires":[]},{"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":2770,"y":620,"wires":[]},{"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":2770,"y":660,"wires":[]},{"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":2770,"y":700,"wires":[]},{"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":2770,"y":740,"wires":[]},{"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":2770,"y":780,"wires":[]},{"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":2770,"y":820,"wires":[]},{"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":2780,"y":860,"wires":[]},{"id":"f3e18b9fa99890ea","type":"range","z":"d6fad9e77ecc8414","minin":"-1","maxin":"1","minout":"0","maxout":"180","action":"scale","round":false,"property":"payload","name":"","x":2780,"y":940,"wires":[["3592dfa80b138081"]]},{"id":"4cf5f35918f4293c","type":"range","z":"d6fad9e77ecc8414","minin":"-1","maxin":"1","minout":"180","maxout":"0","action":"scale","round":false,"property":"payload","name":"","x":2780,"y":900,"wires":[["790ecd8e383b167b"]]},{"id":"790ecd8e383b167b","type":"mqtt out","z":"d6fad9e77ecc8414","name":"","topic":"/right","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"f414466379b9f58d","x":2930,"y":900,"wires":[]},{"id":"3592dfa80b138081","type":"mqtt out","z":"d6fad9e77ecc8414","name":"","topic":"/left","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"f414466379b9f58d","x":2930,"y":940,"wires":[]},{"id":"1b99baa42461afc5","type":"dualshock4_multi","z":"d6fad9e77ecc8414","name":"","sleep":"100","x":2520,"y":480,"wires":[["d6ec5892bc127fe9"],["9cc87a9c3e6f0035"],["1f57f89452d3afe7"],["46f52d05fa3c247a"],["aa67e9490b49afb8"],["1a75d59c7a27052a"],["a54da400d2fea125"],["0c3040e3a045dde5"],["75885030a7baedc9"],["2c5e7d6378b5313d"],["c0e5db9534442249"],["ac98c870a5f214d2"],["066bb1ab3979c7ae"],["4dd05ee6ec60fadb"],["91fd7eba418305a4"],["c5f5d5bb1ed13491"],["39761b80535b3527"],["3ce2279ab30c4ce3","f3e18b9fa99890ea"],["d712775e6b8074bd"],["76d35c2ab7bfa619","4cf5f35918f4293c"],["81258300e2a180ca"],["8a00aa6ab2b170be"]]},{"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":"227d3ca77163ce82","type":"ui_group","name":"Buttons","tab":"f6642a80f4f5605a","order":2,"disp":true,"width":"6","collapse":false,"className":""},{"id":"f414466379b9f58d","type":"mqtt-broker","name":"","broker":"localhost","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"f6642a80f4f5605a","type":"ui_tab","name":"Controller","icon":"dashboard","disabled":false,"hidden":false}]
▼rangeノードで値の範囲を変更しています。

なお速度を0~180で指定するのは、180度回転するサーボモータ用のノードを開発していたときの名残です。もちろんマイコン側で値の範囲を変更することもできます。
マイコン側の設定
以前も利用した小型のロボットを用いて、Node-REDとMQTT通信をして操作できるようにしました。
▼この記事ではコマンドを受け取って動くようにしていましたが、今回はそれぞれの連続回転サーボの回転速度を送るようにしています。
プログラムはChatGPTに相談しながら作成しました。MQTT通信の/right、/leftのトピックで受信した値で連続回転サーボが回転するようになっています。
#include <WiFi.h>
#include <PubSubClient.h>
const char* ssid = "<your SSID>";
const char* password = "<your password>";
const char* mqtt_server = "<your ip>";
const int ServoPin1 = D1;
const int ServoPin2 = D2;
const int DutyMax = 2300;
const int DutyMin = 700;
int speed1 = 0;
int speed2 = 0;
WiFiClient espClient;
PubSubClient client(espClient);
void ServoSpeed(int pin, int speed) {
int Duty = map(speed, -10, 10, DutyMin, DutyMax);
digitalWrite(pin, HIGH);
delayMicroseconds(Duty);
digitalWrite(pin, LOW);
delayMicroseconds(20000 - Duty);
}
void reconnect() {
while (!client.connected()) {
Serial.print("MQTT接続を試みています...");
if (client.connect("ESP32Client")) {
Serial.println(" 接続成功");
client.subscribe("/right");
client.subscribe("/left");
} else {
Serial.print(" 失敗 (rc=");
Serial.print(client.state());
Serial.println(") 5秒後に再試行します");
delay(1000);
}
}
}
void callback(char* topic, byte* payload, unsigned int length) {
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
int value = message.toInt();
int mappedSpeed = map(value, 0, 180, -10, 10);
if (strcmp(topic, "/right") == 0) {
speed1 = mappedSpeed;
Serial.print("右車輪速度: "); Serial.println(speed1);
} else if (strcmp(topic, "/left") == 0) {
speed2 = mappedSpeed;
Serial.print("左車輪速度: "); Serial.println(speed2);
}
}
void setup() {
Serial.begin(115200);
pinMode(ServoPin1, OUTPUT);
pinMode(ServoPin2, OUTPUT);
ServoSpeed(ServoPin1, 0);
ServoSpeed(ServoPin2, 0);
Serial.print("WiFiに接続中...");
WiFi.begin(ssid, password);
int attempt = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
attempt++;
if (attempt > 20) {
Serial.println("\nWiFi接続に失敗しました。再起動します。");
ESP.restart();
}
}
Serial.println("\nWiFiに接続しました");
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
reconnect();
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
ServoSpeed(ServoPin1, speed1);
ServoSpeed(ServoPin2, speed2);
}
連続回転サーボの制御は、ライブラリを使わずに行っています。
▼こちらの記事で試していました。
要はパルス幅を指定してピンの制御を行っています。
動作を確認してみる
実際に通信して動かしてみました。
▼通信のラグがあり、機体がふらつくので操作しにくいです。
▼ちょっと慣れたころの操作。回転速度を細かく調整できないので、回転が難しいです。
▼ダッシュボードと並べるとこんな感じ。
右と左の回転速度があっていない気もするので、エンコーダ等で制御するか、もっと精度のいいモーターを使いたいところです。
最後に
新しく開発したdualshock4ノードを利用することで、XIAO ESP32C3と通信できるようになりました。通信のラグはできるだけ解消したいところです。
▼こちらの記事で通信していたときの方が、すぐに反応していた気がします。
ロボットのソフトウェア開発に使えそうな技術は集まってきたので、久しぶりに機体と基板の設計をしたいなと思っています。
▼ちなみにXIAO ESP32C3とDUALSHOCK 4で通信できるのかをChatGPTに聞いてみたのですが、最初は通信できると回答されました。もう一度聞いてみると、DUALSHOCK 4ではBLE通信できないと回答されました。ハードウェアには弱い印象があります。

