Trying Out XIAO ESP32C3 Part 6 (Communication with DUALSHOCK 4 using Node-RED)
Introduction
In this post, I tried operating a small robot equipped with a XIAO ESP32C3 using a DUALSHOCK 4 controller. In my previous research, I had concluded that direct communication between a DUALSHOCK 4 and a XIAO ESP32C3 was not possible because they use different Bluetooth standards.
▼I investigated the differences between Bluetooth Classic and BLE in this article:
However, I recently developed a Node-RED node called "dualshock4" that can communicate with the controller. I decided to test if I could use Node-RED as a relay to facilitate communication.
▼Details about the dualshock4 node:
▼The product page for the XIAO ESP32C3 is here:
https://akizukidenshi.com/catalog/g/g117454
▼Previous series articles:
Node-RED Side Settings
Using the dualshock4 node, I receive inputs from the DUALSHOCK 4 and send them via MQTT. The wheels are set to rotate according to the Y-axis values of the Right and Left sticks. The output values of the sticks range from -1 to 1, but since I need to send values between 0 and 180 to the microcontroller, I use the "range" node for conversion.
▼It looks complex due to the number of pins, but the data is ultimately sent through the mqtt out node at the bottom.

[{"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}]▼The value range is adjusted using the range node.

Specifying the speed in the 0–180 range is a leftover from when I was developing a node for a 180-degree rotation servo. Of course, the value range could also be handled on the microcontroller side.
Microcontroller Side Settings
I used the small robot from a previous project and set it up to be operated via MQTT communication with Node-RED.
▼In this previous article, the robot was moved by receiving specific commands, but this time I am sending the rotation speed for each continuous rotation servo.
I created the program while consulting with ChatGPT. The continuous rotation servos rotate based on the values received from the /right and /left MQTT topics.
#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("Attempting MQTT connection...");
if (client.connect("ESP32Client")) {
Serial.println(" Connection successful");
client.subscribe("/right");
client.subscribe("/left");
} else {
Serial.print(" Failed (rc=");
Serial.print(client.state());
Serial.println(") Retrying in 5 seconds");
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("Right wheel speed: "); Serial.println(speed1);
} else if (strcmp(topic, "/left") == 0) {
speed2 = mappedSpeed;
Serial.print("Left wheel speed: "); Serial.println(speed2);
}
}
void setup() {
Serial.begin(115200);
pinMode(ServoPin1, OUTPUT);
pinMode(ServoPin2, OUTPUT);
ServoSpeed(ServoPin1, 0);
ServoSpeed(ServoPin2, 0);
Serial.print("Connecting to WiFi...");
WiFi.begin(ssid, password);
int attempt = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
attempt++;
if (attempt > 20) {
Serial.println("\nWiFi connection failed. Restarting.");
ESP.restart();
}
}
Serial.println("\nConnected to WiFi");
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
reconnect();
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
ServoSpeed(ServoPin1, speed1);
ServoSpeed(ServoPin2, speed2);
}The continuous rotation servo is controlled without using a library.
▼I tested this method in this article:
Essentially, it controls the pins by specifying the pulse width.
Checking the Operation
I actually tested the communication and movement.
▼There is some communication lag, causing the machine to wobble and making it difficult to control.
▼Operating it after getting a bit used to it. Since the rotation speed cannot be finely adjusted, turning is quite a challenge.
▼This is how it looks alongside the dashboard:
The rotation speeds of the left and right wheels don't seem perfectly synced, so I’d like to either control them using encoders or use higher-precision motors in the future.
Finally
By using the newly developed dualshock4 node, I am now able to communicate with the XIAO ESP32C3. I want to eliminate the communication lag as much as possible.
▼I feel like the reaction was faster when I was communicating in this article:
Now that I’ve gathered various technologies that can be used for robot software development, I’m planning to design a new chassis and circuit board for the first time in a while.
▼By the way, when I asked ChatGPT if the XIAO ESP32C3 could communicate directly with a DUALSHOCK 4, it initially said it was possible. When I asked again, it correctly answered that the DUALSHOCK 4 cannot perform BLE communication. It seems AI still has some weaknesses when it comes to hardware specifics.




