SO-101 Rig Migration: Mac โ Raspberry Pi 5
Fixing a root cause instead of routing around it โ plus a debugging arc that found the opposite fix from the one assumed going in
The problem that started this
Symptom: cameras worked fine standalone, but crashed during an actual rollout with "camera too slow."
Root cause, found after ~2 hours: the USB hub wasn't communicating reliably under real load. Switching ports helped but remained unstable.
Underlying reason: the Mac has no native USB-A ports โ both cameras and the arm's serial controller all funneled through external hubs, competing for shared bandwidth. Not a camera problem. A Mac-connectivity problem.
The decision: migrate to a Pi 5 (4GB)
Why this genuinely fixes the root cause, not just works around it: the Pi 5 has 4 native USB ports (2ร USB 3.0, 2ร USB 2.0) โ enough for main camera, gripper camera, and the arm's serial controller to each get a direct, dedicated port, no hub aggregation at all. This removes the actual bottleneck rather than routing around it.
Bonus, not yet acted on: the Pi 5 also has the AI Camera (IMX500) โ a CSI camera, entirely separate from the USB bus. Headroom for a future third camera angle with zero impact on the USB situation.
Pre-migration state
- โ New wide-angle cameras installed
- โ New 3D-printed wrist camera holder (official LeRobot design)
- โ New 3D-printed overhead/main camera holder (official XLeRobot design, repurposed for this stationary rig) โ positioned between the two SO-101 arms
- โ Cameras individually tested โ both work perfectly in isolation
- โ A separate, earlier USB hub short-circuit incident already diagnosed and resolved
Migration day: the real debugging arc
1. Motor 5 intermittently missing from the bus scan
Traced to a marginal physical connector, confirmed by its inconsistent appear/disappear pattern โ not a fixed hardware fault.
2. The servo controller needed the hub, not a direct port โ the opposite of the camera fix
Moving the servo controller from a direct Pi USB port to the USB hub stabilized the connection completely. Direct Pi USB ports proved unstable for this specific low-bandwidth serial connection โ the opposite arrangement from what the camera situation would suggest.
3. "Overload error" on shutdown โ benign, not a fault
Cycled across different motors (4, then 5, then 2, then 1) on different runs. Diagnosed as the expected, safe protective response: whichever joint happens to be under gravitational load at the exact moment torque disables triggers the error. Confirmed benign by the pattern itself (a different motor each time, not one recurring failure point) and by three consecutive fully clean, error-free active teleoperation runs (18s, 31s, 35s, all at a rock-solid 59.73Hz).
4. Recalibrated both arms as a precaution
Genuinely healthy ranges on every joint โ no signs of mechanical damage from the overload scares.
5. The real scare: the follower arm went completely dead
After all the software fixes above, the follower arm showed zero response and zero torque โ didn't move, didn't even resist gravity.
Root cause: the follower's separate power supply (an old laptop adapter, chosen for its higher current) had been physically unplugged โ left disconnected during the day's cable reorganization while moving the rig to the Pi 5. The data/USB connection was fine the entire time, which is exactly why software reported zero errors โ the arm simply had no power to move anything.
Fix: reconnect the power cable. Confirmed with a full clean 52-second teleoperation test โ completely stable, zero issues.
Audio side quest: episode-boundary cues
spd-say initially failed with No such file or directory. Root cause, worked through in order:
speech-dispatcherwasn't installed at all โ installed viaapt- Default audio backend is PulseAudio, which wasn't installed โ
espeak-ng "test"(bypassing speech-dispatcher entirely) also produced no sound, isolating the problem to ALSA output routing rather than speech-dispatcher itself - The Pi 5 has two independent HDMI audio devices (
card 0: vc4hdmi0,card 1: vc4hdmi1) โ one per micro-HDMI port. Only the port with a display actually attached outputs audio. speaker-test -D plughw:0,0produced audible sound โ card 0 is the correct, connected devicespeaker-test -D plughw:1,0failed with error-524โ expected, since nothing is plugged into that port
sudo apt install -y speech-dispatcher espeak-ng
Set card 0 as the ALSA default in ~/.asoundrc:
defaults.pcm.card 0
defaults.ctl.card 0
Confirmed working:
espeak-ng "test"
spd-say "test"
speaker-test on each one โ the two ports are independent devices, not a single fallback pair.Working configuration
Follower arm: /dev/ttyUSB1 (via USB hub)
Leader arm: /dev/ttyUSB0 (via USB hub)
Main camera (overhead): /dev/video2, direct USB 3.0 port
Gripper camera (wrist): /dev/video0, direct USB 3.0 port
Port numbers can shift on reboot/reconnect โ always reconfirm with lerobot-find-port and v4l2-ctl --list-devices rather than assuming these persist.
Result: migration validated
Successfully completed a real recording session (episode 0, 12.8s, clean 29.93Hz) using both cameras on dedicated USB 3.0 ports and the arm controller via the hub. The original goal โ proving the Pi 5's native ports would fix the Mac's USB-hub camera instability โ is confirmed.
What's next
A full validation dataset was recorded on this rig and transferred to the ML Desktop for training โ full details, including a real gotcha in how lerobot-record names its output folder, are in ML Desktop: LeRobot Setup & First Pi 5 Dataset.