A Python bridge that exposes ReachyMini robot control over MQTT. Arduino Uno Q participants connect to a local mosquitto broker and control the robot by publishing JSON messages.
# Activate the ReachyMini virtual environment
source ~/Documents/ReachyMini/reachy_mini_env/bin/activate
# Install dependencies
pip install paho-mqtt scipy
# Run the bridge (daemon must be running first)
cd ~/Documents/ReachyMiniServer.UnoQWorkshop
python -m bridge.mainOr use the convenience script:
./run_bridge.sh
./run_bridge.sh --log-level DEBUG
./run_bridge.sh --broker 192.168.1.100 # if broker is not on localhostPrerequisites:
- mosquitto broker running:
sudo systemctl start mosquitto - ReachyMini daemon running (starts with the robot)
Send a smooth head movement:
mosquitto_pub -t reachy/cmd/goto -m '{"client_id":"test","head":{"pitch":15,"roll":0,"yaw":0},"duration":1.5}'Watch all feedback:
mosquitto_sub -t 'reachy/state/#' -vPlay an emotion:
mosquitto_pub -t reachy/cmd/emotion -m '{"client_id":"test","name":"happy1"}'Move head, antennas, and/or body with smooth interpolation. This command is queued — it waits for the previous movement to finish.
{
"client_id": "arduino_1",
"head": { "pitch": 15.0, "roll": 0.0, "yaw": -30.0 },
"antennas": { "right": 20.0, "left": -20.0 },
"body_yaw": 10.0,
"duration": 1.5,
"method": "minjerk"
}- All fields optional except
client_id(must include at least one ofhead,antennas,body_yaw) - All angles in degrees
duration: seconds (clamped to 0.1–30.0, default 1.0)method:minjerk(default),linear,ease_in_out,cartoon
Set target immediately without waiting. Use at 10Hz+ for reactive/tracking control. Bypasses the queue (last writer wins).
{
"client_id": "arduino_1",
"head": { "pitch": 10.0, "roll": 5.0, "yaw": 0.0 },
"antennas": { "right": 0.0, "left": 0.0 }
}Same fields as goto, no duration or method.
{ "client_id": "arduino_1", "action": "wake_up" }Valid actions:
wake_up— move to init pose, play soundsleep— move to sleep pose, play soundcancel— cancel current movement immediately
{ "client_id": "arduino_1", "action": "enable" }Valid actions:
enable— motors powered, normal controldisable— motors unpowered (robot goes limp)gravity_comp— motors counteract gravity passively
{ "client_id": "arduino_1", "name": "happy1" }For dances, specify the dataset:
{ "client_id": "arduino_1", "name": "simple_nod", "dataset": "pollen-robotics/reachy-mini-dances-library" }Current joint positions, all in degrees:
{
"head": { "pitch": 12.3, "roll": -4.1, "yaw": 20.0 },
"body_yaw": 10.5,
"antennas": { "right": 15.0, "left": -10.0 },
"ts": 1711123456.789
}{
"motor_mode": "enabled",
"queue_depth": 2,
"ts": 1711123456.789
}motor_mode values: enabled, disabled, gravity_compensation
Every command you send gets an acknowledgement:
{
"client_id": "arduino_1",
"topic": "reachy/cmd/goto",
"status": "accepted",
"reason": ""
}status values:
accepted— command queued or executedrejected— client's queue is full (reason: "queue_full")error— bad payload or unknown action (seereason)
The bridge automatically clamps all values to safe ranges. You don't need to worry about breaking the robot — out-of-range values are silently adjusted.
| Joint | Safe Range |
|---|---|
| Head pitch | ±40° |
| Head roll | ±40° |
| Head yaw | ±180° |
| Body yaw | ±160° |
| Antennas | ±180° |
#include <ArduinoJson.h>
#include <PubSubClient.h>
// Subscribe to joint state
client.subscribe("reachy/state/joints");
client.subscribe("reachy/state/ack");
// Send a goto command
void sendHeadGoto(float pitch, float roll, float yaw, float duration) {
StaticJsonDocument<256> doc;
doc["client_id"] = "arduino_1";
doc["duration"] = duration;
JsonObject head = doc.createNestedObject("head");
head["pitch"] = pitch;
head["roll"] = roll;
head["yaw"] = yaw;
char buf[256];
serializeJson(doc, buf);
client.publish("reachy/cmd/goto", buf);
}
// In loop(): read reachy/state/joints to get current position
void callback(char* topic, byte* payload, unsigned int length) {
if (strcmp(topic, "reachy/state/joints") == 0) {
StaticJsonDocument<512> doc;
deserializeJson(doc, payload, length);
float pitch = doc["head"]["pitch"];
float right_antenna = doc["antennas"]["right"];
// ... use these values
}
}Topic: reachy/cmd/emotion, field "name", default dataset pollen-robotics/reachy-mini-emotions-library
amazed1 anxiety1 attentive1 attentive2 boredom1
boredom2 calming1 cheerful1 come1 confused1
contempt1 curious1 dance1 dance2 dance3
disgusted1 displeased1 displeased2 downcast1 dying1
electric1 enthusiastic1 enthusiastic2 exhausted1 fear1
frustrated1 furious1 go_away1 grateful1 helpful1
helpful2 impatient1 impatient2 indifferent1 inquiring1
inquiring2 inquiring3 irritated1 irritated2 laughing1
laughing2 lonely1 lost1 loving1 no1
no_excited1 no_sad1 oops1 oops2 proud1
proud2 proud3 rage1 relief1 relief2
reprimand1 reprimand2 reprimand3 resigned1 sad1
sad2 scared1 serenity1 shy1 sleep1
success1 success2 surprised1 surprised2 thoughtful1
thoughtful2 tired1 uncertain1 uncomfortable1 understanding1
understanding2 welcoming1 welcoming2 yes1 yes_sad1
Topic: reachy/cmd/emotion, field "name", set "dataset": "pollen-robotics/reachy-mini-dances-library"
chicken_peck chin_lead dizzy_spin
grid_snap groovy_sway_and_roll head_tilt_roll
interwoven_spirals jackson_square neck_recoil
pendulum_swing polyrhythm_combo sharp_side_tilt
side_glance_flick side_peekaboo side_to_side_sway
simple_nod stumble_and_recover uh_huh_tilt
yeah_nod
The bridge uses a per-client FIFO queue with max 2 pending commands per Arduino, executed round-robin. This means:
- Every Arduino gets a fair share of robot time
- If your queue is full, you'll get a
rejectedack — slow down or check your ack handler cmd/setbypasses the queue entirely — use it for real-time loops
With 8 Arduinos sending goto commands simultaneously, the robot will execute them one at a time in round-robin order. Expect ~0.5–2s latency depending on movement durations.
These features are planned for a future phase:
- Token system — exclusive control windows (e.g. 7s per Arduino)
- Swarm patterns — blended averaged control from all Arduinos
- IMU feedback — accelerometer/gyroscope data on
reachy/state/imu - Recording/playback — record a sequence and replay it