
Serial bridge chips, udev rules, unbind and rebind, and the triage order that separates a driver problem from a dead servo on an SO-100 class arm in five minutes.
An arm that has stopped answering looks identical in every log: a timeout. The process cannot reach motor 1, the teleoperation loop stalls, and the arm freezes or sags. Nothing in that message says whether the USB bridge chip fell off the bus, whether a background daemon opened the port first, or whether one servo stopped talking. Three problems, three fixes, and exactly one of them involves a screwdriver.
This is the triage order for SO-100 class arms, written for Linux because that is where the arms end up. Where macOS differs it is called out. If you are still assembling, start at the SO-100 getting started guide and come back here the first time it goes quiet.
What you need to know
- •The node name is half a diagnosis: /dev/ttyUSB* means a driver such as ch341 claimed the chip, /dev/ttyACM* means the CDC-ACM path. LeRobot documents SO-101 boards as /dev/ttyACM0.
- •A port that opens but never answers is not a driver problem. open() succeeding proves the kernel and the bridge are fine.
- •On a stock Ubuntu desktop, brltty and ModemManager are the two daemons that quietly take the port from you.
- •tty numbers are assigned in bind order, not by socket. A udev rule keyed on the port path or the chip serial number is the fix.
- •Writing a device id to /sys/bus/usb/drivers/usb/unbind and then bind re-enumerates a wedged bridge without touching the cable.
- •Latency is a separate failure: the FTDI latency timer defaults to 16 ms and camera streams outrank your bulk serial traffic.
The chain between your Python process and a servo horn
Seven layers sit between a call to bus.read('Present_Position', 'shoulder_pan') and a horn moving, and each fails with its own symptom. Most wasted debugging time comes from applying a fix from one layer to a fault in another: reinstalling drivers when the jumper is wrong, or resoldering a cable when a screen reader has the port.
| Layer | What it is | How it fails | What you actually see |
|---|---|---|---|
| USB host controller and hub | The port and any hub in between | Port power, bad cable, a hub browning out under load | Absent from lsusb entirely |
| USB-serial bridge chip | CH340, CH343, CP2102 or FT232 | Wedges, stops answering control transfers | Listed by lsusb, no node, or writes stop landing |
| Kernel driver | ch341, cdc_acm, cp210x, ftdi_sio | Another driver binds it first | An attach, then a disconnect a second later |
| tty device node | /dev/ttyACM0 or /dev/ttyUSB0 | Number moved after replug, wrong group | No such file or directory, or Permission denied |
| Userspace claim | pyserial opening the node | Another process holds it | Device or resource busy on open |
| Half-duplex servo bus | One TTL line shared by every servo, 1 Mbps | Wrong baud, wrong jumper channel, servo power off | Port opens cleanly, no id answers |
| The servo | Feetech STS3215 on SO-100 and SO-101 | Duplicate id, dead 3-pin cable, overload cutout | Five ids answer, one does not |
Work down that table in order. Do not skip a row because you are sure about it. Arm not detected and servo not responding are separate pages here for exactly that reason: the boundary is whether a device node exists.
Read the node name first: ttyACM or ttyUSB
Linux exposes USB serial adapters through two unrelated paths. The usb-serial framework covers chips needing a vendor-specific protocol, uses major number 188 and names them /dev/ttyUSB0 upward. The CDC-ACM class driver covers chips that describe themselves as standard USB communications devices and names them /dev/ttyACM0 upward. Which one you get is a property of the chip, not of your distribution.
| Bridge chip | Linux driver | Node | Worth knowing |
|---|---|---|---|
| CH340 / CH341 | in-kernel ch341 | /dev/ttyUSB* | id_table: 1a86:5523, 1a86:7522, 1a86:7523, 2184:0057, 4348:5523, 9986:7523. Source comment: supported range is 46 to 3000000 bps. |
| CH343 / CH9102 | cdc_acm (enumerates as CDC) | /dev/ttyACM* | Enumerates as a standard CDC device, so no driver install on Linux or a Raspberry Pi. |
| CP2102 / CP2104 | cp210x | /dev/ttyUSB* | Silicon Labs part, common on nearby ESP32 boards. |
| FT232R / FT232H | ftdi_sio | /dev/ttyUSB* | The only family with a tunable latency timer in sysfs. |
| PL2303 | pl2303 | /dev/ttyUSB* | In-kernel driver, no install needed. Clone chips are common at this price point. |
# 1. is the bridge on the bus at all?
lsusb
lsusb -t # tree view: shows which driver claimed which interface
# 2. what did the kernel decide to do with it?
sudo dmesg -w # leave running, then unplug and replug the arm
# 3. which node appeared, and what are the stable aliases?
ls -l /dev/ttyACM* /dev/ttyUSB* 2>/dev/null
ls -l /dev/serial/by-id/ /dev/serial/by-path/On a healthy CH340 you want a dmesg pair like ch341-uart converter detected followed by ch341-uart converter now attached to ttyUSB0. A CDC device gives a line naming ttyACM0. An attach followed by a disconnect a second later means the chip is fine and userspace is taking it: skip to the traps.
The same board appears twice: /dev/tty.usbmodem* and /dev/cu.usbmodem*. The tty variant waits for carrier detect before open() returns, which for a servo adapter can mean never. The cu variant opens immediately. lerobot-find-port globs /dev/tty*, so it always hands you the tty name. If a connect hangs with no error, swap the prefix to cu.
Five minute triage: driver problem or servo problem
The useful boundary: can a process open the port. If it can, the kernel, the host cable and the bridge chip all work, and everything after that is the servo bus.
- 1Confirm the bridge enumerates
If the chip is not in
lsusb, no driver work helps. Try a different cable first: charge-only USB cables look identical to good ones and are the most common cause of a device that never appears.bashlsusb | grep -iE '1a86|0403|10c4|067b' lsusb -t # look for the Driver= field on the interface line - 2Confirm a node exists and nobody else holds it
Wrong group is a permissions problem. Busy means another process got there first.
bashls -l /dev/ttyACM0 stat -c '%G' /dev/ttyACM0 # which group owns it fuser -v /dev/ttyACM0 # who has it open (or: lsof /dev/ttyACM0) - 3Open the port with nothing but pyserial
Take LeRobot out of the picture. If this succeeds, every remaining failure is on the servo side.
pythonimport serial s = serial.Serial("/dev/ttyACM0", 1_000_000, timeout=1) print("opened:", s.is_open, s.name) s.close() - 4Ask the bus which motors exist, at every baud rate
LeRobot ships a scanner that broadcast-pings at each supported baud and reports every id that answered. The list runs 4800 through 1000000, and 1000000 is the Feetech default.
pythonfrom lerobot.motors.feetech import FeetechMotorsBus # returns {baudrate: [motor ids that answered]} print(FeetechMotorsBus.scan_port("/dev/ttyACM0")) - 5Read the error text literally
LeRobot and the Feetech SDK use distinct strings for distinct faults. Worth memorising.
textCould not connect on port '/dev/ttyACM0'. -> kernel refused: node, permission, or busy [TxRxResult] There is no status packet! -> nobody answered: baud, power, wiring, id [TxRxResult] Incorrect status packet! -> something answered but the framing was wrong [TxRxResult] Port is in use! -> the SDK already has the port open elsewhere

The traps that eat a day
brltty takes your CH340
A braille display uses the same CH340 chip as half the hobby robotics world, so brltty ships a udev rule matching USB id 1a86:7523 and claims the device on sight. Ubuntu bug 1990357 records exactly that against brltty 6.4-4ubuntu3 on Ubuntu 22.04.1 and Linux Mint 21: lsusb lists the chip as 1a86:7523, nothing appears in /dev, and the device comes back as /dev/ttyUSB0 once brltty is removed. The report is still Confirmed.
# is brltty even here?
dpkg -l | grep -i brltty
systemctl status brltty-udev.service
# surgical: find the rule matching your chip and comment that one line out.
# on Ubuntu the generated file is 85-brltty.rules; the ids are lowercase hex
grep -n "1a86" /usr/lib/udev/rules.d/85-brltty.rules
# blunt: stop udev from starting brltty at all
sudo systemctl mask brltty-udev.service
sudo systemctl stop brltty-udev.service
sudo udevadm control --reloadThis failure produces no error. The chip attaches, goes away, and every later command reports the less helpful No such file or directory. If ls /dev/ttyUSB* shows the node for a second after replug and then it vanishes, stop reading driver documentation and look at brltty.
ModemManager probes anything that looks like a port
ModemManager opens new serial ports to check for a modem behind them. The probe holds the port for a few seconds after every plug event, so the first lerobot-calibrate after connecting fails and the same command twenty seconds later works. Two details decide whether the fix takes: the property goes on the usb subsystem, not tty, because a device-level tag belongs on the device, and the ModemManager documentation asks for a 78-mm- or 79-mm- filename, because its own candidate rules are the 80-mm- file.
# /etc/udev/rules.d/79-mm-lerobot-ignore.rules
# the 79- prefix is not cosmetic: rules files from every directory are sorted
# together by filename, and ModemManager's candidate rules are the 80-mm- file
ACTION!="add|change|move|bind", GOTO="mm_lerobot_end"
# substitute the ids you actually saw in lsusb
SUBSYSTEM=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", ENV{ID_MM_DEVICE_IGNORE}="1"
LABEL="mm_lerobot_end"
# then
sudo udevadm control --reload
sudo udevadm trigger --subsystem-match=usb
# confirm the property landed where ModemManager will read it
udevadm info -p /sys/class/tty/ttyACM0 | grep ID_MMPermissions, and why chmod 666 keeps coming back
Serial nodes are group-owned: dialout on Debian and Ubuntu, uucp on Arch. The LeRobot SO-101 guide suggests sudo chmod 666 /dev/ttyACM0, which works until the next replug, because udev recreates the node with the default mode. Group membership is durable and needs a fresh login.
ls -l /dev/ttyACM0
# crw-rw---- 1 root dialout 166, 0 Aug 24 09:14 /dev/ttyACM0
sudo usermod -aG "$(stat -c '%G' /dev/ttyACM0)" "$USER"
# log out and back in, or start a subshell with the new group:
newgrp dialout
# verify without replugging anything
id -nG | tr ' ' '\n' | grep -E 'dialout|uucp'The jumper that is not a software problem at all
The LeRobot SO-101 guide states it plainly: on a Waveshare controller board, set the two jumpers on the B channel (USB). Waveshare's Bus Servo Adapter (A) wiki explains the other position: A routes the bus to the UART header pins for a host like a Pi Zero or an ESP32, B routes it to the USB connector. On A the bridge still enumerates, you still get /dev/ttyACM0, the port still opens, and not one servo answers.
The second trap is the power rail. The SO-100 and SO-101 run Feetech STS3215 bus servos at 7.4 V. Koch v1.1 uses Dynamixel XL330 and XL430 parts on 5 V and 12 V rails, and LeKiwi is a 7.4 V arm on a 12 V base, the layout that invites the wrong barrel jack. Feeding 12 V into STS3215 servos destroys them, and the same Waveshare adapter wiki says the input voltage must match the servo voltage.
udev rules: stop chasing /dev/ttyACM0
tty numbers are handed out in bind order, which follows probe timing. With a leader-follower pair the leader is ttyACM0 today and ttyACM1 tomorrow, and hardcoded numbers mean driving the wrong arm at least once. A rule in the udev rule language fixes the name.
| Key | What the man page says | Example |
|---|---|---|
| SUBSYSTEM | Match the subsystem of the event device | SUBSYSTEM=="tty" |
| KERNEL | Match the name of the event device | KERNEL=="ttyACM[0-9]*" |
| ATTRS{...} | Search the devpath upwards for a device with matching sysfs attribute values | ATTRS{idVendor}=="1a86" |
| KERNELS | Search the devpath upwards for a matching device name | KERNELS=="1-1.2" (on USB that name is the physical port path) |
| SYMLINK | The name of a symlink targeting the node | SYMLINK+="robot_follower" |
| MODE, GROUP | The permissions for the device node | MODE="0660", GROUP="dialout" |
| ENV{key} | Match against a device property value | ENV{ID_MM_DEVICE_IGNORE}="1" |
| == vs = vs += | Compare for equality; assign; add to a key holding a list | SYMLINK+= appends, SYMLINK= replaces |
- 1Walk the device tree and pick a discriminator
The walk prints one block per ancestor device. You want a value unique to this arm and stable across reboots:
ATTRS{serial}if the chip reports one, otherwise the parent kernel name, which encodes the socket.bashudevadm info --name=/dev/ttyACM0 --attribute-walk | head -60 # the short version of the same question udevadm info --name=/dev/ttyACM0 --query=property | grep -E 'ID_VENDOR_ID|ID_MODEL_ID|ID_SERIAL|ID_PATH' - 2Write the rule
Put it in
/etc/udev/rules.d/, but know what that buys you. Rules files from every directory are sorted and processed together in lexicographic order by filename, so a99-file runs last wherever it lives. The/etc/precedence applies only to a file with the same name as one under/usr/lib/: that replaces a packaged rule, it does not outrank one.bash# /etc/udev/rules.d/99-lerobot-arms.rules # by serial number: survives moving the cable to any socket, # but only works if the bridge chip actually reports one SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{serial}=="58FD017123", \ SYMLINK+="robot_follower", MODE="0660", GROUP="dialout" # by physical port: works with chips that report no serial at all, # but breaks the moment you move the plug SUBSYSTEM=="tty", KERNELS=="1-1.2", \ SYMLINK+="robot_leader", MODE="0660", GROUP="dialout" - 3Reload, trigger, verify
Reloading alone changes nothing for devices already plugged in. The trigger replays the events, so the rule applies without a replug.
bashsudo udevadm control --reload sudo udevadm trigger --subsystem-match=tty ls -l /dev/robot_* # if the symlink does not appear, watch the events live udevadm monitor --udev --property --subsystem-match=tty - 4Use the stable name everywhere
Once the symlink exists, no LeRobot command needs a number again.
bashlerobot-calibrate \ --robot.type=so101_follower \ --robot.port=/dev/robot_follower \ --robot.id=my_follower_arm
The stock 60-serial.rules creates /dev/serial/by-id/ and /dev/serial/by-path/, but only for nodes matching ttyUSB[0-9]*|ttyACM[0-9]*, and it skips the by-id link when ID_SERIAL is empty. That is why two identical adapters can produce one by-id entry: the chips report no serial, so udev cannot tell them apart. by-path comes from topology and always exists.
Unbind and rebind: resetting a wedged bridge without touching the cable
Sometimes the chip is present, the node is present, and writes go nowhere: the bridge has wedged. Manual driver binding through sysfs has existed since kernel 2.6.13-rc3 and gives two levels of reset short of pulling the cable.
PORT=/dev/ttyACM0
TTY=$(basename "$PORT")
# derive the ids instead of guessing them
IFACE=$(basename "$(readlink -f /sys/class/tty/$TTY/device)")
DRV=$(basename "$(readlink -f /sys/class/tty/$TTY/device/driver)")
DEV=${IFACE%%:*}
echo "interface=$IFACE driver=$DRV device=$DEV"
# e.g. interface=1-1.4:1.0 driver=cdc_acm device=1-1.4
# level 1: drop only the interface the tty hangs off, leave the device enumerated
echo -n "$IFACE" | sudo tee /sys/bus/usb/drivers/$DRV/unbind
echo -n "$IFACE" | sudo tee /sys/bus/usb/drivers/$DRV/bind
# level 2: re-enumerate the whole device, equivalent to a replug
echo -n "$DEV" | sudo tee /sys/bus/usb/drivers/usb/unbind
sleep 2
echo -n "$DEV" | sudo tee /sys/bus/usb/drivers/usb/bindLevel 2 re-runs udev, so symlinks and permissions come back as configured. That is why the udev rule comes before the rebind script. Level 3 cuts port power with uhubctl, with one caveat: on Raspberry Pi the ports are ganged. On the B+, 2B and 3B, ports 2 to 5 are controlled by port 2, and on the Pi 5 all four form one group. Cutting power to the arm cuts power to the cameras too.
- Recovers a wedged bridge in about two seconds with no physical access.
- Re-runs udev on level 2, so symlinks, group ownership and ModemManager exclusions come back.
- Scriptable: N consecutive receive timeouts triggers one rebind, then a reconnect.
- Costs nothing to try and cannot damage the servos.
- Needs root. On a locked-down machine you are back to pulling the cable.
- Does not power-cycle the chip. A genuinely stuck bridge needs uhubctl or a real replug.
- The path 1-1.4 identifies a socket, not a device. Move the cable and the script rebinds the wrong thing.
- It hides the cause. A rebind every twenty minutes means a cable, hub or power problem.
When it really is the servo
Everything past a successful open is the half-duplex TTL bus, where the two Feetech SDK strings from step 5 earn their keep. There is no status packet means nothing replied within the timeout: power, baud or wiring. Incorrect status packet means something replied with wrong framing, which points at a baud mismatch or electrical noise, not at absence. Opposite fixes.
| What you see | Most likely layer | Next thing to run |
|---|---|---|
| lsusb does not list the bridge | Cable, hub or port power | Swap the cable first, then lsusb -t |
| lsusb lists it, no node appears | Another driver claimed it | dmesg | tail -30, then check brltty |
| Permission denied on the node | Group membership | ls -l on the node, then usermod -aG |
| Port opens, no id answers at any baud | Jumper channel, servo power, wiring | FeetechMotorsBus.scan_port() |
| Ids answer at 115200 but not 1000000 | Motors set up for another project | Re-run lerobot-setup-motors |
| Five ids answer, one is silent | That servo, its 3-pin cable, or a duplicate id | Move the known-good cable to the silent joint |
| Ids answer, positions are nonsense | Calibration, not connectivity | lerobot-calibrate for that arm id |
| Works, then dies under load | Rail sag or the servo overload cutout | Measure the rail while the arm lifts |
The last row is worth doing by hand: move a known-good 3-pin cable to the silent joint. If the joint answers, the cable was the fault; if not, the servo is. Both outcomes have a page, joint stops early and arm twitches then sags. A missing camera is a different subsystem again, at camera not detected.

The latency layer nobody measures
A link can be connected and still be too slow for a control loop. Three places add delay before a line of your code runs, and none report an error.
| Source | Default | How to check | When it matters |
|---|---|---|---|
| FTDI latency timer | 16 ms (ftdi_sio sets priv->latency = 16 when it cannot read the device value) | cat /sys/bus/usb-serial/devices/ttyUSB0/latency_timer | FTDI only. A status packet is a few bytes, so 16 ms of buffering dwarfs the transaction |
| USB autosuspend | power/control is 'on' for non-hubs, autosuspend_delay_ms is 2000 | cat /sys/bus/usb/devices/1-1.4/power/control | Power tools and some distro profiles flip it to 'auto', adding a resume cost after an idle gap |
| Camera bandwidth | On a high speed bus, 80 percent of the frame is reserved for periodic transfers (kernel USB docs) | Move a camera to a different root hub and re-test | Bridges use bulk transfers and get the leftovers. Two cameras plus an arm on one controller is where jitter starts |
Inference has to sit next to the servos for anything fast. The control loop here is 20 ms per action step for ACT, 152 ms for GR00T N1.7, 165 ms for GR00T N1.5, 245 ms for SmolVLA and 485 ms for Pi0.5. Public-internet round trips on top turn a working policy into a hesitant one: remote inference is viable for slow pick-and-place, not for fast reactive motion, and no serial tuning changes that. See inference latency.
On an FTDI adapter one write removes the buffering. It does not survive a replug, so it belongs in your udev rule file or your startup script. CH340, CH343 and CDC-ACM devices have no equivalent knob.
# FTDI only: drop the receive buffering from 16 ms to 1 ms
cat /sys/bus/usb-serial/devices/ttyUSB0/latency_timer
echo 1 | sudo tee /sys/bus/usb-serial/devices/ttyUSB0/latency_timer
# keep the device awake regardless of power policy
echo on | sudo tee /sys/bus/usb/devices/1-1.4/power/control
# if you need to see the actual traffic, usbmon is the tcpdump of USB
sudo modprobe usbmon
ls /sys/kernel/debug/usb/usbmon
sudo cat /sys/kernel/debug/usb/usbmon/1u > /tmp/bus1.monTwo ways to get an arm answering again
Everything above, on every machine that touches an arm. The commands are short; the ownership is the cost. You maintain the udev rules, daemon exclusions and rebind scripts, and re-derive them on each new Pi, laptop or container image.
# lerobot 0.6.1, released 2026-08-03
pip install 'lerobot[core_scripts,feetech]'
lerobot-find-port # unplug when asked, it prints the node
sudo usermod -aG dialout "$USER" # then log out and back in
# after writing /etc/udev/rules.d/99-lerobot-arms.rules
sudo udevadm control --reload
sudo udevadm trigger --subsystem-match=tty
lerobot-calibrate \
--robot.type=so101_follower \
--robot.port=/dev/robot_follower \
--robot.id=my_follower_arm- Full control, no accounts, works offline.
- You own the per-distro differences: dialout against uucp, brltty on Ubuntu, whatever the next image ships.
- Deciding whether the arm itself is broken means finding a second arm.
- Documented at the LeRobot SO-101 guide; nothing here is hidden.
The platform removes one question, not the whole problem: is the arm broken, or is the setup broken. The live arm is a physical SO-100 you drive from the browser with no signup, queue-based, so you can compare a known-good arm against your bench. The desktop client records LeRobot datasets straight from a teleop session, episodes, camera streams and joint states, once the link is healthy.
- The fix index carries one page per failure mode, so a diagnosis is a page rather than a forum thread.
- The same operations reach a terminal at the CLI and AI agents at the MCP server.
- The client guide and the robots docs cover the supported arms.
- Once the link is stable, record a first dataset; the directory shows what others captured.
Nothing here installs a udev rule, unbinds a driver, or masks brltty for you. The kernel side of this article is yours and stays yours. What is removed is the comparison problem: a known-good arm next to your broken one without owning two.
A checklist you can run in ninety seconds
Save this next to your robot scripts. It answers, in order, the six questions that separate the layers.
#!/usr/bin/env bash
# triage.sh - ninety second check for an SO-100 class arm on Linux
PORT="${1:-/dev/ttyACM0}"
echo "== bridge on the bus?"
lsusb | grep -iE '1a86|0403|10c4|067b' || echo " no known USB-serial bridge in lsusb"
echo "== which driver claimed which interface?"
lsusb -t
echo "== node and permissions"
ls -l "$PORT" 2>&1
echo "== stable aliases"
ls -l /dev/serial/by-id/ /dev/serial/by-path/ 2>&1
echo "== who is holding the port?"
fuser -v "$PORT" 2>&1
echo "== known port thieves"
systemctl is-active brltty-udev.service ModemManager.service 2>&1
echo "== last twenty kernel lines"
dmesg | tail -20For the setup around this, the complete SO-100 setup guide covers assembly through calibration and first policy run, and the data collection guide picks up once the link stops dropping. SO-100 against SO-101 compares the two arms, and the SO-100 LeRobot page covers the software side.

One page per failure mode
Arm not detected, servo not responding, joint stops early, policy freezes mid-motion. One page each: the symptom, the cause, and the command that settles it.
Open the fix indexThe port opens but no motor answers. Is the driver broken?▾
Almost certainly not. A successful open() means the kernel, the host cable and the bridge chip all work. Check three things in order: the controller board jumpers (the LeRobot guide requires the B channel on Waveshare boards), the servo power supply, and the baud rate. FeetechMotorsBus.scan_port() broadcast-pings from 4800 up to 1000000 and reports which ids answered where.
Do I need the WCH vendor driver on Linux?▾
Usually not. CH340 and CH341 have an in-kernel driver, and CH343 class parts are CDC compliant, so cdc-acm handles them with no install. The vendor driver ch343ser_linux exists for what the class driver cannot reach, and its README names those: hardware flow control and GPIO. That same README tells you to rmmod cdc-acm first, and the driver then creates nodes named /dev/ttyCH343USBx, which renames your port and breaks any config hardcoding ttyACM0.
Why does the port number change between reboots?▾
tty numbers are assigned in bind order, which depends on probe timing rather than on the socket. The fix is a udev rule keyed on something stable: ATTRS{serial} if your chip reports one, KERNELS on the parent USB path if it does not. systemd's stock 60-serial.rules already gives you /dev/serial/by-path/ for free, and that link exists even for chips with no serial.
Is it safe to unbind and rebind while a policy is running?▾
No. Every open file descriptor on that port dies, so stop the control loop first, rebind, then reconnect. The rebind is host-side and touches nothing on the servo bus, so the arm keeps whatever torque state it was last commanded into: an arm mid-motion stops where it is rather than going limp. Clear the workspace first.
Can AY-Robots fix a USB problem on my machine?▾
No. udev rules, driver binding and daemon conflicts are local to your operating system and you fix them there. The platform only narrows the search: /fix documents each failure mode on its own page, and /live is a known-good physical SO-100 in the browser with no signup, so you can separate a broken arm from a broken setup without owning two.
Sources
- LeRobot documentation: SO-101 ports, jumpers, permissions and calibration
- LeRobot source: MotorsBus.scan_port, the Feetech scan baud rates and the connect error text
- FEETECH servo Python SDK: the TxRxResult result strings
- Linux kernel documentation: USB serial devices and major number 188
- Linux kernel source: ch341 driver id_table and supported bit rates
- Linux kernel source: ftdi_sio and the 16 ms latency timer default
- Linux kernel documentation: USB bandwidth reserved for periodic transfers
- Linux kernel documentation: USB power management, power/control and autosuspend_delay_ms
- man 7 udev: rule keys, operators and how rules files are ordered
- systemd 60-serial.rules: source of /dev/serial/by-id and by-path
- LWN: Manual driver binding and unbinding
- Ubuntu bug 1990357: brltty claims CH340 serial devices
- ModemManager: port and device detection, udev tags and rule file naming
- WCH ch343ser_linux vendor VCP driver
- Waveshare wiki: Bus Servo Adapter (A), jumper channels A and B and input voltage
Sources
- LeRobot documentation: SO-101 ports, jumpers, permissions and calibration
- LeRobot source: MotorsBus.scan_port, the Feetech scan baud rates and the connect error text
- FEETECH servo Python SDK: the TxRxResult result strings
- Linux kernel documentation: USB serial devices and major number 188
- Linux kernel source: ch341 driver id_table and supported bit rates
- Linux kernel source: ftdi_sio and the 16 ms latency timer default
- Linux kernel documentation: USB bandwidth reserved for periodic transfers
- Linux kernel documentation: USB power management, power/control and autosuspend_delay_ms
- man 7 udev: rule keys, operators and how rules files are ordered
- systemd 60-serial.rules: source of /dev/serial/by-id and by-path
- LWN: Manual driver binding and unbinding
- Ubuntu bug 1990357: brltty claims CH340 serial devices
- ModemManager: port and device detection, udev tags and rule file naming
- WCH ch343ser_linux vendor VCP driver
- Waveshare wiki: Bus Servo Adapter (A), jumper channels A and B and input voltage
Ready for high-quality robotics data?
AY-Robots connects your robots to skilled operators worldwide.
Get Started