A compact 12-DOF quadruped robot with ESP32 gait control, calibrated servo actuation, sensor reactions, controller-side speech commands, and simulation assets.
|
Build Pluto Assembly steps, leg construction, body integration, and final checks. |
Wire Pluto Power path, common ground, PCA9685, sensors, and safety checks. |
ESP32 Firmware Gaits, IK, servos, sensors, serial commands, and WiFi/UDP. |
Controller UI NiceGUI, keyboard/gamepad input, speech commands, and telemetry. |
|
Meshes Current STL inventory and MuJoCo mesh usage. |
Protocol Shared C++/Python UDP packet and message format. |
Simulation PyBullet UI path, MuJoCo bridge, and current limitations. |
Troubleshooting First-time setup, firmware, power, sensors, and WiFi issues. |
|
Pluto is designed as a reusable quadruped robotics platform rather than a single-purpose demo. The goal is to make a small robot that can stand, walk, trot, gallop, react to simple sensor events, and accept commands through serial, WiFi, UI controls, and speech. The same concepts appear across the firmware, controller, and simulation code: leg geometry, foot targets, inverse kinematics, gait phases, calibrated servo limits, and compact command messages. |
Pluto was developed as part of CS-358 Making Intelligent Things at EPFL. The repository documents both the final implementation and the current work-in-progress state so future development can continue from the same hardware, firmware, controller, protocol, and simulation foundation. |
| Layer | Role |
|---|---|
| ESP32 firmware | Servo control, gait execution, ultrasonic sensing, optional microphone sensing, serial testing, and WiFi/UDP command handling |
| Python tooling | NiceGUI control hub, keyboard/gamepad input, speech command handling, telemetry display, and simulation control |
| Shared communication | C++ and Python packet/message definitions kept aligned |
| CAD and simulation assets | 3D-printing meshes, simulation meshes, pluto.xml, PyBullet-facing code, and MuJoCo bridge |
|
|
| Area | Status | Notes |
|---|---|---|
| Physical robot | In progress | Main build and wiring docs exist; final gait validation still needed |
| Servo control | Implemented | Calibration lives in src/esp/legs/leg_data.h |
| Gaits | Implemented, tuning needed | Walk, trot, gallop, left/right turn, bow, paw, stand, and stop logic exist |
| Sensors | Implemented, tuning needed | Ultrasonic wall stop is active; microphone support exists but PLUTO_ENABLE_MICROPHONE and clap-control are currently commented out |
| Controller UI | Implemented | Connect button, mDNS scan, movement input, sit/flip/bow/give-paw quick actions, telemetry, speech worker |
| WiFi/UDP | Implemented, enabled in firmware | Add WiFi credentials in setup(); the controller discovers _pluto._udp.local with zeroconf |
| Meshes | Updated | 3D-printing meshes live in src/3D printing mesh; simulation meshes and pluto.xml live in src/sim/sim_mesh |
| PyBullet | Partial | UI path exists, but visual meshes still reference old generic filenames |
| MuJoCo | Partial | Model and C++ bridge exist, but not yet a validated physical twin |
For the Python controller:
- Python 3.13
- Desktop environment for the browser UI and simulation windows
- Audio permission if using speech commands
For the ESP32 firmware:
- PlatformIO Core or the PlatformIO VS Code extension
- ESP32 board connected over USB
- Pluto hardware assembled or a safe bench setup
For the physical robot:
- Common ground between ESP32, PCA9685, servo power, sensors, and battery system
- Buck converter output checked with a multimeter
- Servo calibration checked before full gait tests
macOS/Linux:
bash run.shWindows:
run.batOpen:
http://localhost:8090
Useful options:
bash run.sh --reinstall
bash run.sh --port 8081Manual setup, if needed:
python3.13 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python src/control/main.py --port 8090pio run
pio run -t upload
pio device monitor -b 115200If the upload port is not detected automatically:
pio device list
pio run -t upload --upload-port /dev/cu.usbserial-XXXXExpected startup behavior:
- PCA9685 initializes at 50 Hz.
- Enabled sensors initialize.
- Pluto enters a stand pose.
- The serial monitor prints available commands.
WiFi support is currently enabled in src/esp/main.cpp:
#define PLUTO_ENABLE_WIFIThen:
- Configure access points with
PLUTO_SERVER.addAP(...). - Make sure the computer and ESP32 are on the same network.
- Run the controller and open
/controller. - Press
Connect & Take Control; the Python client scans for Pluto via mDNS/zeroconf and then performs the UDP handshake.
See SOFTWARE_WIFI.md.
This is the recommended order for a new team starting with only the repository, parts, and a 3D printer.
| Phase | Goal | Main files/docs | Exit check |
|---|---|---|---|
| 1. Understand the system | Know what each subsystem does before building | README.md, SOFTWARE_OVERVIEW.md, HARDWARE_OVERVIEW.md |
Team can explain firmware, controller, communication, sensors, and simulation roles |
| 2. Print parts | Produce body, holder, leg, and TPU foot parts from the current 3D-printing mesh set | CAD_FILES.md, src/3D printing mesh/ |
Main body, board holder, sensor holder, TPU feet, coxa, femur, tibia, spacer, and linkage parts are printed and inspected |
| 3. Assemble one leg | Validate mechanical fit before repeating four times | ASSEMBLY.md | One leg moves freely by hand without binding |
| 4. Assemble all legs and body | Build the full physical robot frame | ASSEMBLY.md | Four legs are mounted with correct orientation |
| 5. Build power system | Prepare battery, switch, buck converter, grounds, and servo power | WIRING_ELECTRICAL.md | Buck output is measured and all grounds are common |
| 6. Wire controller and servos | Connect ESP32, PCA9685, servos, ultrasonic sensor, and microphone | WIRING_ELECTRICAL.md, SOFTWARE_SENSORS.md | ESP32 flashes over USB and PCA9685 powers correctly |
| 7. Bring up firmware safely | Test stand, stop, and raw trim commands before walking | src/esp/README.md | Serial monitor works and joints move in expected directions |
| 8. Calibrate servos | Tune per-joint raw limits, start values, angle ranges, and inversion flags | src/esp/legs/leg_data.h |
Each joint can move through a safe range without hitting mechanical stops |
| 9. Test motion slowly | Validate stand, walk, trot, gallop, flip, bow, sit, and paw behavior | src/esp/motion/gait.cpp, TROUBLESHOOTING.md |
Robot can stand reliably and execute controlled test motions |
| 10. Test controller | Configure WiFi and Python controller only after safe serial tests | SOFTWARE_WIFI.md, src/control/README.md | Controller connects and sends stop/behavior/movement commands |
| 11. Use simulation for iteration | Inspect gait logic and mesh assumptions before risky physical tests | SIMULATION.md | Team understands current PyBullet and MuJoCo limitations |
For a new team, the most important rule is to validate one subsystem at a time. Do not test full-body walking before power, calibration, and single-joint behavior are understood.
This section is written as a handoff guide for a team that did not build the original robot. It repeats some information from the detailed docs so the README can be used as a complete starting point.
- Clone the repository.
- Install Python 3.13.
- Install PlatformIO Core or the PlatformIO VS Code extension.
- Confirm that
pio --versionworks. - Confirm that the Python controller can start with
bash run.shorrun.bat. - Do not connect the LiPo battery yet.
The goal of this step is only to verify that the computer can build firmware and run the controller UI.
Print the physical robot parts from src/3D printing mesh/. The folders are organized by leg:
Front Left/Front Right/Back Left/Back Right/
The same folder also includes the top-level Main Body.stl, Board Holder.stl, Sensor Holder.stl, and TPU Foot.stl files. Each leg folder contains that leg's coxa, femur, tibia, spacer, and linkage/bar parts. The simulation-only meshes are separate and live in src/sim/sim_mesh/.
Label parts as soon as they are printed. Do not mix parts between legs. The firmware also uses TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, and BOTTOM_RIGHT, so a physical mix-up can become a software calibration problem later.
Before installing any servo:
- Check that each servo fits into its printed part.
- Check that screw holes are usable.
- Check that bearings sit correctly.
- Check that the tibia linkage can move without rubbing.
Build one complete leg before building the other three.
- Assemble the tibia with its bearing and foot.
- Assemble the femur around the servo.
- Attach the coxa section.
- Connect femur and tibia with the linkage.
- Move the leg by hand.
The leg should move smoothly without powered servos. If it binds by hand, it will bind under servo power.
After one leg is validated:
- Assemble the other three legs.
- Mount coxa servos into the body.
- Mount the PCA9685.
- Mount the buck converter.
- Mount the ESP32.
- Mount the ultrasonic sensor at the front.
- Mount the microphone if used.
- Mount the battery so it is secure but removable.
Do not power the robot from the battery yet.
Wire in this order:
- LiPo connector.
- Rocker switch.
- Servo power rail and PCA9685 servo power input.
- Buck converter input.
- Buck converter output.
- ESP32 power.
- PCA9685 logic power and I2C.
- Sensors.
- Servos.
Before connecting the ESP32 or sensors to the buck converter, measure the output with a multimeter. All grounds must be connected together: battery/servo ground, PCA9685 ground, buck converter ground, ESP32 ground, and sensor ground.
The firmware expects this PCA9685 channel mapping:
| Leg | Coxa | Femur | Tibia |
|---|---|---|---|
TOP_LEFT |
0 | 1 | 2 |
TOP_RIGHT |
4 | 5 | 6 |
BOTTOM_LEFT |
8 | 9 | 10 |
BOTTOM_RIGHT |
12 | 13 | 14 |
Channels 3, 7, 11, and 15 are unused by the current leg abstraction.
If a servo is plugged into the wrong channel, the robot may move the wrong joint even if the code is correct.
Put the robot on a stand so the legs cannot hit the table.
Then run:
pio run
pio run -t upload
pio device monitor -b 115200At this stage, use serial commands only. Do not use WiFi movement yet.
Calibration is in:
src/esp/legs/leg_data.h
Each joint has:
raw_minraw_maxraw_standraw_forwardraw_turnleftraw_turnrightraw_bowraw_sitangle_min_mdangle_max_mdinverted
Use the serial monitor:
lselects the next leg.nselects the next joint.pprints the selected leg, joint, and pulse.+increases raw PWM by 5.-decreases raw PWM by 5.rresets the selected joint.Rresets all joints on the selected leg.
For each joint:
- Select the leg and joint.
- Print the current value.
- Move slowly with
+and-. - Stop before mechanical binding.
- Record safe min and max raw values.
- Choose safe standing and motion-specific raw start values.
- Check whether direction needs
inverted = true. - Rebuild and upload after editing calibration.
Only after all 12 joints are calibrated should walking be tested.
Recommended order:
s: stop and stand.1: select walk.0: set speed to 0%.5: set speed to 50%.f: test forward motion briefly.q: test left turn briefly.e: test right turn briefly.s: stop.2: select trot only after walk and turns are stable.3: select gallop only after lower-speed gaits are stable.
If a leg moves in the wrong direction, stop and return to calibration. Do not compensate by forcing the gait constants first.
By default, ultrasonic support is compiled and microphone support is left disabled:
#define PLUTO_ENABLE_ULTRASONIC
// #define PLUTO_ENABLE_MICROPHONECurrent sensor templates:
- Ultrasonic:
SensorUltraSonic<5, 18> - Microphone, when re-enabled:
SensorMicrophone<26, 25, 33>
The ultrasonic sensor stops motion if the measured distance is below 35 cm. The microphone driver and update_microphone_control(now) helper remain in src/esp/main.cpp, but the feature flag and update call are currently commented out; re-enable them only after validating the energy threshold on the real microphone.
Only test WiFi control after serial movement is safe.
- Confirm
#define PLUTO_ENABLE_WIFIis present. - Add WiFi credentials with
PLUTO_SERVER.addAP(...). - Upload firmware.
- Run the Python controller.
- Press
Connect & Take Control. - Press
Stop Allbefore sending movement. - Watch telemetry for acknowledgements.
The controller should be treated as a convenience layer, not the first debugging tool.
+---------------------------------------------------------+
| Python Control Hub |
| NiceGUI UI, keyboard/gamepad, speech, telemetry, UDP |
+---------------------------+-----------------------------+
|
| WiFi / UDP port 4242
v
+---------------------------------------------------------+
| ESP32 Firmware |
| 20 ms gait loop, IK, PCA9685 output, sensors, UDP server |
+---------------------------+-----------------------------+
|
v
+---------------------------------------------------------+
| Hardware and Simulation Assets |
| 12 servos, 4 legs, ultrasonic, microphone, STL/MuJoCo |
+---------------------------------------------------------+
|
Runs on the user's computer.
|
Runs on the ESP32.
|
Keeps both sides aligned.
|
| Design choice | Reason |
|---|---|
| Keep gait and servo output on ESP32 | Servo updates and stop/stand behavior stay close to the hardware |
| Keep UI, speech, and simulation on the computer | These are heavier and easier to iterate in Python |
| Mirror message definitions in C++ and Python | The controller and robot share one compact command language |
| Keep simulation assets in the repo | Gait timing and actuator mapping can be inspected before hardware tests |
| Component | Qty | Role |
|---|---|---|
| 3D-printed body and leg parts | 1 set | Robot structure |
| DMS15-style 270-degree servos | 12 | Coxa, femur, and tibia actuation |
| PCA9685 PWM driver | 1 | 16-channel servo PWM over I2C |
| ESP32 development board | 1 | Main embedded controller |
| HC-SR04-style ultrasonic sensor | 1 | Front distance sensing |
| INMP441 I2S microphone | 1 | Audio energy sensing |
| 7.4V 2S LiPo battery | 1 | Main power source |
| LM2596 buck converter | 1 | Regulated electronics power |
| KCD1 rocker switch | 1 | Main power switching |
| Screws, bearings, servo horns, wiring | As needed | Mechanical and electrical assembly |
- PLA/PETG filament for rigid printed parts.
- TPU filament for
TPU Foot.stl, or rubber pads if substituting the printed foot. - M3 and M2.5 screws for the body, legs, and servo mounting.
- Ball bearings for tibia joints.
- Jumper wires for logic signals and thicker wires for servo current.
- Soldering equipment, heat-shrink tubing, electrical tape, and cable ties.
- Multimeter for voltage, polarity, and continuity checks.
- LiPo-safe charger and LiPo-safe storage bag.
The current physical 3D-printing CAD/STL files live in src/3D printing mesh/, grouped by leg:
src/3D printing mesh/
|-- Front Left/
|-- Front Right/
|-- Back Left/
`-- Back Right/
The physical print folder also includes Main Body.stl, Board Holder.stl, Sensor Holder.stl, and TPU Foot.stl. The simulation meshes live separately in src/sim/sim_mesh/ and include body.stl, per-leg coxa/femur/tibia STL files, and pluto.xml. See CAD_FILES.md.
| 1 Print parts |
2 Assemble legs |
3 Mount electronics |
4 Wire power and sensors |
5 Flash firmware |
6 Calibrate servos |
Detailed hardware docs:
| Topic | Note |
|---|---|
| Common ground | LiPo/servo ground, PCA9685 ground, ESP32 ground, buck converter ground, and sensor grounds must be connected together |
| Servo power | Servos draw much higher current than the ESP32 and sensors; plan the power paths separately |
| Buck converter | Adjust and measure the LM2596 output before connecting the ESP32 or sensors |
| PCA9685 | Provide both logic power and servo power, and connect SDA/SCL to the ESP32 |
| Ultrasonic ECHO | If ECHO outputs 5V, use a voltage divider or level shifter before the ESP32 GPIO |
| High-current wiring | Use appropriate wire thickness for LiPo, XT60, rocker switch, servo power rail, and PCA9685 servo power |
For a new team, read the code in this order:
| Step | Read | Why |
|---|---|---|
| 1 | src/esp/legs/leg_data.h |
Defines the physical servo calibration assumptions |
| 2 | src/esp/legs/leg.h and leg_joint.h |
Shows how servo channels and joint commands are abstracted |
| 3 | src/esp/motion/ik_solver.cpp |
Converts foot targets into coxa/femur/tibia angles |
| 4 | src/esp/motion/gait.cpp |
Generates stand, walk, trot, gallop, flip, bow, sit, and paw motion |
| 5 | src/esp/main.cpp |
Connects setup, loop timing, sensors, serial commands, and WiFi |
| 6 | src/comm/message.h |
Defines the shared C++ message format |
| 7 | src/control/pluto_server/message.py |
Mirrors the same format in Python |
| 8 | src/control/pluto_server/server.py |
Handles Python-side UDP connection, heartbeat, and receive loop |
| 9 | src/control/pluto_menu/controller.py |
Converts UI input into movement and behavior messages |
| 10 | src/control/sim_motion.py and src/sim/sim_main.cpp |
Shows the current simulation paths and their limitations |
PlatformIO environment:
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
build_unflags = -std=gnu++11
build_flags = -std=gnu++17PlatformIO project settings:
| Setting | Value |
|---|---|
src_dir |
src/esp |
include_dir |
src/ |
build_dir |
.build/ |
build_src_filter |
Excludes only paths under the firmware source root; src/sim is outside the ESP32 firmware build |
Important firmware files:
| File | Purpose |
|---|---|
src/esp/main.cpp |
setup, loop, feature flags, sensors, serial commands |
src/esp/legs/leg_data.h |
servo calibration values |
src/esp/legs/leg_joint.h |
calibrated servo abstraction |
src/esp/motion/gait.cpp |
gait and behavior motion logic |
src/esp/motion/ik_solver.cpp |
inverse kinematics |
src/esp/sensors/ultrasonic.h |
ultrasonic sensor abstraction |
src/esp/sensors/microphone.h |
I2S microphone abstraction |
src/esp/server/server.cpp |
UDP server and mDNS advertisement |
Python dependencies:
nicegui
numpy
debugpy
pybullet
vosk
sounddevice
zeroconf
Important controller files:
| File | Purpose |
|---|---|
src/control/main.py |
NiceGUI entry point |
src/control/pluto_menu/controller.py |
controller page, movement, behavior buttons, telemetry |
src/control/pluto_menu/simulation.py |
simulation page |
src/control/pluto_input/input_manager.py |
keyboard/gamepad movement vector |
src/control/pluto_server/message.py |
Python message definitions |
src/control/pluto_server/server.py |
UDP client, session, heartbeat, listener |
src/control/pluto_speech/speech.py |
Vosk speech worker |
Controller routes:
| Route | Purpose |
|---|---|
/ |
Home page with simulation and controller choices |
/sim |
Simulation controls |
/controller |
Robot command center |
Current speech grammar:
pluto sitpluto stoppluto give pawpluto flippluto bow
The shared UDP packet contains:
| Field | Size | Purpose |
|---|---|---|
crc32 |
4 bytes | Packet integrity |
session_token |
4 bytes | Session lock generated by ESP32 |
sequence_number |
4 bytes | Stale packet rejection and acknowledgements |
timestamp |
4 bytes | Sender-side local time |
message_count |
1 byte | Number of valid messages |
messages |
8 bytes each | Up to 64 packed messages |
Message families:
KIND_INFOKIND_MOVEKIND_SENSORKIND_BEHAVIOR
Implemented message examples:
| Message | Direction | Meaning |
|---|---|---|
MOVE_CONTROL_BEGIN_FOR |
Python -> ESP32 | Starts a control lease |
MOVE_BY |
Python -> ESP32 | Carries forward/back and left/right signed 16-bit directions; firmware maps the dominant non-deadzone axis to forward, backward, left turn, right turn, or stop |
MOVE_STOP_FOR |
Python -> ESP32 | Stop/stand request |
BEHAVIOR_SIT |
Python -> ESP32 | High-level sit request; current firmware maps it to stop/stand |
BEHAVIOR_FLIP |
Python -> ESP32 | High-level flip request; current firmware logs the request but does not yet trigger a flip motion over UDP |
BEHAVIOR_BOW |
Python -> ESP32 | High-level bow request; current firmware maps it to bow motion |
BEHAVIOR_GIVE_PAW |
Python -> ESP32 | High-level paw request; current firmware maps it to paw motion |
INFO_ACKNOWLEDGE |
ESP32 -> Python | Acknowledges a packet sequence number |
SENSOR_DISTANCE |
ESP32 -> Python | Sends ultrasonic distance in millimeters |
Useful references:
| Path | Files | Current state |
|---|---|---|
| Python PyBullet UI | src/control/pluto_menu/simulation.py, src/control/sim_motion.py |
Integrated in the UI, but visual mesh references need updating |
| MuJoCo C++ bridge | CMakeLists.txt, src/sim/sim_main.cpp, src/sim/sim_gait.*, src/sim/sim_leg.*, src/sim/sim_mesh/pluto.xml |
Uses current simulation meshes and simulation-side copies/adapters of the ESP leg and gait abstractions |
The MuJoCo path is more aligned with the current mesh set. The PyBullet path is still useful for UI/control experiments, but it needs a mesh update before it fully represents the current CAD assets.
The C++ MuJoCo bridge uses the top-level CMakeLists.txt, which sets PLUTO_MODEL_PATH to src/sim/sim_mesh/pluto.xml. Its keyboard handlers are intended to mirror the firmware test controls: F/B for forward/backward, Q/E for left/right turning, P for paw, O for flip, S for stop, and 1/2/3 for walk/trot/gallop. Mouse drag and scroll controls are wired through MuJoCo's camera helpers. Treat this bridge as development support until it has been rebuilt and validated on the current machine.
See SIMULATION.md.
Current defaults in src/esp/main.cpp:
#define PLUTO_ENABLE_WIFI
#define PLUTO_ENABLE_ULTRASONIC
// #define PLUTO_ENABLE_MICROPHONE| Feature | Default | Notes |
|---|---|---|
| WiFi/UDP | On | Configure valid access points before using controller networking |
| Ultrasonic | On | Stops motion when wall distance is below threshold |
| Microphone | Off | SensorMicrophone<26, 25, 33> and clap-control code remain available, but the feature flag and update call are commented out |
| Item | Current value |
|---|---|
| Ultrasonic template | SensorUltraSonic<5, 18> |
| Microphone template | SensorMicrophone<26, 25, 33> |
| Wall stop distance | 35 cm |
| Ultrasonic period | 150 ms |
| Microphone clap threshold | 30000000 in the commented clap-control block |
| Clap cooldown | 800 ms in the commented clap-control block |
Operating timing:
| Loop / event | Current timing |
|---|---|
| Gait update | 20 ms / 50 Hz |
| Ultrasonic read cycle | 150 ms |
| Ultrasonic wait after trigger | 10 ms |
| Microphone energy print cycle | 1000 ms in the commented clap-control block |
| Serial monitor baud | 115200 |
Servo calibration lives in:
src/esp/legs/leg_data.h
Each joint has:
raw_minraw_maxraw_standraw_forwardraw_turnleftraw_turnrightraw_bowraw_sitangle_min_mdangle_max_mdinverted
Tune these values on the physical robot before running full gaits.
Embedded gait code lives in:
src/esp/motion/gait.cpp
src/esp/motion/gait.h
Embedded IK code lives in:
src/esp/motion/ik_solver.cpp
src/esp/motion/ik_solver.h
Tune these areas carefully:
- Stand pose.
- Foot target geometry.
- Stride length.
- Lift height.
- Gait period.
- Walk/trot/gallop phase offsets.
- Per-leg symmetry.
- Bow and paw target poses.
Use:
pio device monitor -b 115200| Key | Action |
|---|---|
f |
Move forward |
b |
Move backward |
q |
Turn left |
e |
Turn right |
o |
Flip |
h |
Bow |
i |
Sit |
k |
Give paw |
s |
Stop and stand |
1 |
Select walk gait |
2 |
Select trot gait |
3 |
Select gallop gait |
0 |
Set speed to 0% |
5 |
Set speed to 50% |
9 |
Set speed to 100% |
u |
Toggle manual walk phase mode |
m |
Advance manual walk stage |
j |
Advance manual walk leg |
l |
Select next leg for trimming |
n |
Select next joint for trimming |
+ |
Increase selected joint raw PWM by 5 |
- |
Decrease selected joint raw PWM by 5 |
r |
Reset selected joint to starting pulse |
R |
Reset all joints on selected leg |
p |
Print selected leg, joint, and pulse |
2026sp-pluto/
|-- README.md
|-- ARCHIVES.md
|-- ASSEMBLY.md
|-- CAD_FILES.md
|-- HARDWARE_OVERVIEW.md
|-- ONGOING_WORK.md
|-- SIMULATION.md
|-- SOFTWARE_OVERVIEW.md
|-- SOFTWARE_SENSORS.md
|-- SOFTWARE_WIFI.md
|-- TROUBLESHOOTING.md
|-- WIRING_ELECTRICAL.md
|-- platformio.ini
|-- requirements.txt
|-- run.sh / run.bat
|-- images/
|-- scripts/
`-- src/
|-- 3D printing mesh/ # Physical 3D-printing STL files
|-- comm/ # Shared protocol definitions
|-- control/ # Python UI, input, speech, and controller client
|-- esp/ # ESP32 firmware
`-- sim/ # C++ MuJoCo simulation bridge and sim_mesh assets
|
|
See ONGOING_WORK.md.
Technical vocabulary
- ESP32: Microcontroller running the robot firmware.
- PCA9685: I2C PWM driver used to control the 12 servo channels.
- 12-DOF: Twelve degrees of freedom, with three actuated joints per leg.
- Coxa, femur, tibia: The hip-yaw, upper-leg, and lower-leg joints.
- IK: Inverse kinematics, converting foot targets into joint angles.
- Gait: Timed leg movement pattern such as walk, trot, or gallop.
- UDP: Lightweight network protocol used for controller-to-robot messages.
- CRC: Packet integrity check used by the shared communication protocol.
- NiceGUI: Python web UI framework used for the control hub.
- Vosk: Offline speech recognition engine.
- MuJoCo: Physics simulator used by
src/sim/sim_mesh/pluto.xmland the files undersrc/sim/. - PyBullet: Python simulation dependency used by the control stack.
Project objectives
- Reliable servo control: drive 12 calibrated joints safely through the PCA9685.
- Legged locomotion: support stand, stop, forward/backward movement, left/right turns, walk, trot, gallop, flip, bow, sit, and paw motion primitives.
- IK-based movement: generate joint angles from foot targets instead of fixed pulse sequences.
- Remote operation: use a shared UDP protocol for movement, behavior, info, acknowledgement, and sensor messages.
- Sensor reactions: stop near obstacles; microphone energy code exists for clap-triggered start/stop behavior but is currently disabled by feature flag and in the main loop.
- Simulation before hardware: keep mesh and simulation assets available for gait development.
- Clear documentation: keep hardware, wiring, firmware, controller, protocol, and troubleshooting notes separated.
Core technologies
- Hardware: ESP32, PCA9685, 12 DMS15-style servos, HC-SR04-style ultrasonic sensor, INMP441 I2S microphone, 2S LiPo, LM2596 buck converter.
- Firmware: C++17, Arduino framework, FreeRTOS, PlatformIO, Adafruit PWM Servo Driver, Adafruit BusIO.
- Control UI: Python 3.13, NiceGUI, keyboard/gamepad input, Vosk, sounddevice.
- Simulation: PyBullet in Python, external MuJoCo/GLFW for the C++ bridge, STL mesh assets.
- Communication: Custom C++/Python UDP packet format with session token, sequence number, timestamp, CRC, and packed messages.
ESP32 firmware responsibilities
- Initialize the PCA9685 servo driver.
- Drive all 12 joints through calibrated
LegJointobjects. - Convert IK outputs into constrained PWM pulses.
- Update gait motion every 20 ms.
- Handle serial commands for movement, turning, gait selection, speed changes, manual walk staging, and servo trimming.
- Read ultrasonic distance and stop when a wall is too close.
- Keep the microphone driver and clap detection logic available; both are currently disabled in
main.cpp. - Accept UDP commands through the Pluto server when connected to WiFi.
Python controller responsibilities
- Serve the NiceGUI app from
src/control/main.py. - Provide navigation between home, simulation, and controller pages.
- Read keyboard and gamepad input through
InputManager. - Send repeated
MOVE_BYmessages while movement input is non-zero; the ESP32 maps them to basic movement commands. - Send quick behavior commands for sit, flip, bow, give paw, and stop.
- Display telemetry for acknowledgements and distance readings.
- Start a Vosk speech worker on app startup.
- Start and stop simulation support from the UI.
Documentation index
Main system:
Hardware:
Software:
- ESP32 Firmware
- Python Controller
- Communication Protocol
- WiFi Protocol
- Software Sensors
- Simulation Notes
Key source files:
Archives and historical work
The repository currently keeps most history through Git, active source files, and ONGOING_WORK.md. If older experiments are added back for reference, they should go under an archives/ directory with a short README explaining what was tested, why it was replaced, and whether it still runs.
Recommended archive categories:
- ESP32 experiments: old gait tests, servo calibration sketches, sensor bring-up code, and behavior prototypes.
- Control UI experiments: early NiceGUI pages, keyboard/gamepad tests, and speech command prototypes.
- Simulation experiments: PyBullet and MuJoCo model tests, gait playback experiments, and old mesh-loading attempts.
- Communication experiments: older UDP packet formats, handshake tests, and protocol debugging scripts.
See ARCHIVES.md.
Ongoing work and next steps
Current focus:
- Hardware gait validation: test walk, trot, gallop, left/right turns, flip, bow, sit, paw, and stop on the physical robot.
- Servo calibration: refine PWM limits, standing and motion-specific starting pulses, inversion flags, and angle ranges.
- WiFi control: tune mapped UDP movement and behavior commands over mDNS-discovered connections.
- Behavior implementation: replace the current safe mapping for sit and the current UDP flip placeholder with dedicated calibrated motion sequences.
- Sensor-driven reactions: tune ultrasonic wall stopping and re-enable/tune microphone compilation and clap detection if needed.
- Simulation fidelity: improve physical accuracy for mass, friction, joint limits, and servo response.
Known issues:
- Some firmware behavior messages currently reuse safe existing motions instead of dedicated calibrated sequences.
- Some networks may block mDNS discovery, requiring controller-side fallback work.
- Gait constants need final physical measurement and tuning.
- MuJoCo and PyBullet support are useful for development, but not yet perfect models of the real robot.
Pluto was developed as part of EPFL's CS-358 Making Intelligent Things course by:
| Serhat Botan | Alexis Cazal | Neha Chakraborty | Myriam Lahoud |
| Sam Lee | Raphael Dib Nehme | Mariya Rakytyanska |
- EPFL course staff, TAs, and coaches.
- Open-source quadruped projects, including SpotMicro-style robots.
- Arduino, PlatformIO, FreeRTOS, Adafruit, NiceGUI, PyBullet, MuJoCo, GLFW, Vosk, and sounddevice documentation and examples.
AI tools were used to support:
- Debugging guidance and code review suggestions.
- Edge-case review for setup, communication, and hardware integration notes.
- Summaries of implementation details from the codebase.
AI assistance was not used to replace:
- Project goals or engineering decision-making.
- Hardware assembly, wiring, or physical testing.
- Experimental validation on the real robot.
- Team ownership of the system design and implementation.
- Writing the instructions and README.
Pluto is a working foundation for a small quadruped robotics platform. The repository already contains low-level actuation, IK, gait generation, sensing hooks, controller-side interaction, command transport, and simulation assets. The next major step is careful physical tuning: servo calibration, gait stability, behavior sequences, and validation on the real robot.


