# AGENTS.md — SaltyLab Agent Onboarding You're working on **SaltyLab**, a self-balancing two-wheeled indoor robot. Read this entire file before touching anything. ## Project Overview A hoverboard-based balancing robot with two compute layers: 1. **ESP32-S3 BALANCE** — runs the PID balance loop. Safety-critical, operates independently of the Orin. 2. **ESP32-S3 IO** — handles I/O: motor commands to VESCs, sensor polling, CAN/UART comms. 3. **Orin Nano Super** — AI brain. ROS2, SLAM, person tracking. Sends velocity commands to ESP32-S3 BALANCE via UART. Not safety-critical. ``` Orin Nano Super (speed+steer via UART) ←→ ELRS RC (kill switch) │ ▼ ESP32-S3 BALANCE (IMU, PID balance loop) │ ▼ CAN / UART ESP32-S3 IO → VESC 68 (left) + VESC 56 (right) ``` ## ⚠️ SAFETY — READ THIS OR PEOPLE GET HURT This is not a toy. 8" hub motors + 36V battery can crush fingers, break toes, and launch the frame. Every firmware change must preserve these invariants: 1. **Motors NEVER spin on power-on.** Requires deliberate arming: hold button 3s while upright. 2. **Tilt cutoff at ±25°** — motors to zero, require manual re-arm. No retry, no recovery. 3. **Hardware watchdog (50ms)** — if firmware hangs, motors cut. 4. **RC kill switch** — dedicated ELRS channel, checked every loop iteration. Always overrides. 5. **Orin UART timeout (200ms)** — if Orin disconnects, motors cut. 6. **Speed hard cap** — firmware limit, start at 10%. Increase only after proven stable. 7. **Never test untethered** until PID is stable for 5+ minutes on a tether. **If you break any of these, you are removed from the project.** ## Repository Layout ``` esp32/ # ESP32-S3 firmware (ESP-IDF) ├── balance/ # ESP32-S3 BALANCE — PID loop, IMU, safety └── io/ # ESP32-S3 IO — VESC CAN, sensors, comms cad/ # OpenSCAD parametric parts (16 files) ├── dimensions.scad # ALL measurements live here — single source of truth ├── assembly.scad # Full robot assembly visualization ├── motor_mount_plate.scad ├── battery_shelf.scad ├── esp32_balance_mount.scad # Vibration-isolated ESP32-S3 BALANCE mount ├── jetson_shelf.scad ├── esc_mount.scad ├── sensor_tower_top.scad ├── lidar_standoff.scad ├── realsense_bracket.scad ├── bumper.scad # TPU bumpers (front + rear) ├── handle.scad ├── kill_switch_mount.scad ├── tether_anchor.scad ├── led_diffuser_ring.scad └── esp32c3_mount.scad ui/ # Web UI (Three.js + WebSerial) └── index.html # 3D board visualization, real-time IMU data SALTYLAB.md # Master design doc — architecture, wiring, build phases SALTYLAB-DETAILED.md # Power budget, weight budget, detailed schematics PLATFORM.md # Hardware platform reference ``` ## Hardware Quick Reference ### ESP32-S3 BALANCE | Spec | Value | |------|-------| | MCU | ESP32-S3 (dual-core Xtensa LX7, 240MHz, 512KB SRAM, 8MB flash) | | Primary IMU | MPU6000 (SPI) | | Role | PID balance loop, tilt cutoff, arming | | Comms to Orin | UART (velocity commands in, telemetry out) | | Flash | `idf.py -p /dev/ttyUSB0 flash` | ### ESP32-S3 IO | Spec | Value | |------|-------| | MCU | ESP32-S3 | | Role | VESC CAN driver, sensor polling, peripheral I/O | | VESC IDs | 68 (left), 56 (right) | | Motor bus | CAN 1Mbit/s | | Flash | `idf.py -p /dev/ttyUSB1 flash` | ### UART Assignments (ESP32-S3 BALANCE) | UART | Connected To | Baud | |------|-------------|------| | UART0 | Orin Nano Super | 115200 | | UART1 | ESP32-S3 IO | 115200 | | UART2 | ELRS Receiver | 420000 (CRSF) | ### Motor/ESC - 2× 8" pneumatic hub motors (36V, hoverboard type) - 2× VESC motor controllers (CAN IDs 68, 56) - VESC CAN protocol: standard SET_DUTY / SET_CURRENT / SET_RPM - Speed range: -1.0 to +1.0 (duty cycle) ### Physical Dimensions (from `cad/dimensions.scad`) | Part | Key Measurement | |------|----------------| | ESP32-S3 BALANCE board | ~55×28mm (DevKit form factor) | | ESP32-S3 IO board | ~55×28mm (DevKit form factor) | | Hub motor body | Ø200mm (~8") | | Motor axle | Ø12mm, 45mm long | | Orin Nano Super | 100×79mm, M2.5 holes at 86×58mm | | RealSense D435i | 90×25×25mm, 1/4-20 tripod mount | | RPLIDAR A1 | Ø70×41mm, 4× M2.5 on Ø67mm circle | | Kill switch hole | Ø22mm panel mount | | Battery pack | ~180×80×40mm | | VESC (each) | ~70×50×15mm | | 2020 extrusion | 20mm square, M5 center bore | | Frame width | ~350mm (axle to axle) | | Frame height | ~500-550mm total | | Target weight | <8kg (current estimate: 7.4kg) | ### 3D Printed Parts (16 files in `cad/`) | Part | Material | Infill | |------|----------|--------| | motor_mount_plate (350×150×6mm) | PETG | 80% | | battery_shelf | PETG | 60% | | esc_mount | PETG | 40% | | jetson_shelf | PETG | 40% | | sensor_tower_top | ASA | 80% | | lidar_standoff (Ø80×80mm) | ASA | 40% | | realsense_bracket | PETG | 60% | | esp32_balance_mount (vibration isolated) | TPU+PETG | — | | bumper front + rear (350×50×30mm) | TPU | 30% | | handle | PETG | 80% | | kill_switch_mount | PETG | 80% | | tether_anchor | PETG | 100% | | led_diffuser_ring (Ø120×15mm) | Clear PETG | 30% | | esp32c3_mount | PETG | 40% | ## Firmware Architecture ### Critical Lessons Learned (DON'T REPEAT THESE) 1. **NEVER auto-run untested code on_boot** — we bricked the NSPanel 3x doing this. Test manually first. 2. **`-(int)0 == 0`** — checking `if (-result)` to detect errors doesn't work when result is 0. Always use explicit error codes. 3. **USB CDC needs RX primed in init** — without it, the OUT endpoint never starts listening. 4. **Watchdog must be fed every loop iteration** — if balance loop stalls, motors must cut within 50ms. 5. **Never change PID and speed limit in the same test** — one variable at a time. ### Build & Flash (ESP32-S3) ```bash # Balance board cd esp32/balance/ idf.py build && idf.py -p /dev/ttyUSB0 flash monitor # IO board cd esp32/io/ idf.py build && idf.py -p /dev/ttyUSB1 flash monitor ``` Dev machine: mbpm4 (seb@192.168.87.40) ## Current Status & Known Issues ### Working - IMU streaming (50Hz JSON: `{"ax":...,"ay":...,"az":...,"gx":...,"gy":...,"gz":...}`) - VESC CAN communication (IDs 68, 56) - LED status patterns - Web UI with WebSerial + Three.js 3D visualization ### In Progress - PID balance loop tuning - ELRS CRSF receiver integration - Orin UART integration ### TODO (Priority Order) 1. Tune PID balance loop on ESP32-S3 BALANCE 2. Implement complementary filter (pitch angle) 3. Wire ELRS receiver, implement CRSF parser 4. Bench test (VESCs disconnected, verify PID output) 5. First tethered balance test at 10% speed 6. Orin UART integration 7. LED subsystem (ESP32-S3 IO) ## Communication Protocols ### Orin → ESP32-S3 BALANCE (UART0, 50Hz) ```c struct { uint8_t header=0xAA; int16_t speed; int16_t steer; uint8_t mode; uint8_t checksum; }; // mode: 0=idle, 1=balance, 2=follow, 3=RC ``` ### ESP32-S3 IO → VESC (CAN, loop rate) - Standard VESC CAN protocol (SET_DUTY / SET_CURRENT / SET_RPM) - Node IDs: VESC 68 (left), VESC 56 (right) ### ESP32-S3 BALANCE → Orin Telemetry (UART0 TX, 50Hz) ``` T:12.3,P:45,L:100,R:-80,S:3\n // T=tilt°, P=PID output, L/R=motor commands, S=state (0-3) ``` ### ESP32-S3 → USB (50Hz JSON, debug/tuning) ```json {"ax":123,"ay":-456,"az":16384,"gx":10,"gy":-5,"gz":3,"t":250,"p":0} // Raw IMU values (int16), t=temp×10, p=PID output ``` ## LED Subsystem (ESP32-S3 IO) ESP32-S3 IO eavesdrops on BALANCE→Orin telemetry (listen-only). No extra UART needed. | State | Pattern | Color | |-------|---------|-------| | Disarmed | Slow breathe | White | | Arming | Fast blink | Yellow | | Armed idle | Solid | Green | | Turning | Sweep direction | Orange | | Braking | Flash rear | Red | | Fault | Triple flash | Red | | RC lost | Alternating flash | Red/Blue | ## Printing (Bambu Lab) - **X1C** (192.168.87.190) — for structural PETG/ASA parts - **A1** (192.168.86.161) — for TPU bumpers and prototypes - LAN access codes and MQTT details in main workspace MEMORY.md - STL export from OpenSCAD, slice in Bambu Studio ## Rules for Agents 1. **Read SALTYLAB.md fully** before making any design decisions 2. **Never remove safety checks** from firmware — add more if needed 3. **All measurements go in `cad/dimensions.scad`** — single source of truth 4. **Test firmware on bench before any motor test** — VESCs disconnected, verify outputs on serial 5. **One variable at a time** — don't change PID and speed limit in the same test 6. **Document what you change** — update this file if you add pins, change protocols, or discover hardware quirks 7. **Ask before wiring changes** — wrong connections can fry an ESP32 or VESC