# Inspire RH56 dual-hand bring-up on Unitree G1 (PC2) Setup notes specific to **this** robot. Upstream README assumes a clean `unitree_sdk2` install in `/usr/local` and hard-codes `/dev/ttyUSB1`+`/dev/ttyUSB2`; neither holds here, so we adjust as below. Canonical (workstation): `…/yslootahtech/Project/G1/DFX_inspire_service` Deploy to PC2 `unitree@192.168.123.164:~/DFX_inspire_service` via `rsync`, then `./build.sh`. (wifi `10.255.254.86` is flaky; the eth0 IP `192.168.123.164` is the reliable path.) ## 0. Easiest way to drive everything — `manage.sh` ```bash ./manage.sh # status: service + devices + adapters ./manage.sh start|stop|restart ./manage.sh open|close|box|menu # fingers only ./manage.sh like|handshake|thumbup # arm + hand (ARM MOVES) ./manage.sh arm "shake hand" # built-in arm action; armlist to list ./manage.sh help ``` It keeps exactly **one** `inspire_g1` running (duplicates corrupt the RS-485 bus) and auto-starts the service for gesture commands. ## Architecture (unchanged from upstream) `hand_example`/your app → DDS topic `rt/inspire/cmd` (`MotorCmds_`, 12 motors: 0–5 right, 6–11 left) → **`inspire_g1`** → RS-485 (CH340 USB adapters, 115200 8N1, Inspire slave id 1) → hands. State flows back on `rt/inspire/state`. Finger order per hand: pinky, ring, middle, index, thumb-bend, thumb-rotation. `q` ∈ [0,1]: **0 = closed, 1 = open**. ## 1. Build (no system changes) `unitree_sdk2` in `/usr/local` is stale (no go2 IDL). `build.sh` links against the newer `~/unitree_sdk2` source tree instead: ```bash cd ~/DFX_inspire_service ./build.sh # -> build/inspire_g1, inspire_h1, hand_example ``` ## 2. Serial access + telling the two hands apart (one-time) Both adapters are CH340s reporting the **same** USB serial, so there is nothing intrinsic to key on. Two separate problems, two separate answers: **Access.** The udev rule grants `0666` to any CH340 tty — it does **not** create `/dev/inspire_*` symlinks any more (per-port symlinks broke whenever the adapters moved to a different hub socket). Only needed for the native path; the container runs privileged with `/dev` mounted and does not care: ```bash sudo cp ~/DFX_inspire_service/udev/99-inspire-hands.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules && sudo udevadm trigger ls -l /dev/ttyUSB* # two nodes, world-writable ``` **Identity.** `inspire_g1` resolves which tty is which hand, best first: 1. **RS-485 id** — `id 1 = right, id 2 = left`. Wiring-independent and permanent; immune to replugs, hub changes and enumeration order. Set it once per robot: ```bash docker exec inspire-hand ./build/hand_setid 1 2 ``` 2. `INSPIRE_RIGHT_PATH` / `INSPIRE_LEFT_PATH` — pin by physical USB socket (`run.sh` ships `1-2.2.1.1`, which is **this** robot's topology). 3. USB path order — the fallback, and the one that can silently swap the hands on unfamiliar hardware. On a new robot, do (1). Then (2) and (3) stop mattering. ## 3. Run + test ```bash cd ~/DFX_inspire_service/build ./inspire_g1 # Terminal 1 (no sudo needed after the udev rule) ./hand_example # Terminal 2 -> both hands open/close ~1 Hz ``` Stop with Ctrl+C in each. If a hand doesn't move: check 24 V power, RS-485 A/B polarity, and that the hand is at Inspire slave id 1. ## Record / replay panel (dashboard) The dashboard's **Record / replay** card runs `g1_record_replay.py` as a child process — the same script as the CLI, so a take made in the browser and one made from a terminal are the same file in `DataG1/`. It needs the recorder present on whatever host serves the dashboard: ```bash # where g1_record_replay.py lives (searched: ../Manual_Recorder, ~/Manual_Recorder) export RECORDER_DIR=~/Manual_Recorder # an interpreter that can import unitree_sdk2py (on a workstation: the g1_env conda python) export RECORDER_PY=python3 ./manage.sh dashboard # prints the resolved recorder path, or NOT FOUND ``` The panel starts a take, relays the in-take keys (`o c [ ] ; '`, `f`, and the saved shapes as keys 1-9), answers the recorder's preview/save prompts, and lists `DataG1/` for replay, rename and delete. Two things it works around, both load-bearing: - **Keys are paced ~150 ms apart.** The recorder reads them with `select()` + buffered `sys.stdin.read(1)`; a burst lands in Python's buffer, `select()` then reports nothing ready and every key after the first is stranded. Measured: 3 keys 10 ms apart → 2 arrived. - **Stop sends SIGINT, and the dashboard repairs SIGINT first.** A process backgrounded by a non-interactive shell inherits `SIGINT = SIG_IGN`, which survives `exec` — and `manage.sh` starts the dashboard exactly that way, so without the repair Stop would silently do nothing. The panel shows a warning if the repair did not hold. SIGINT (not SIGKILL) is what lets the recorder walk the arm home and release it. Replay and preview **move the arm** and both require an explicit confirmation. Starting a take disarms force-follow, since the bridge and the recorder would otherwise both publish `rt/inspire/cmd` every frame. ## If you move an adapter to a different USB socket The udev `KERNELS` value changes. Re-discover and update the rule: ```bash udevadm info -a -n /dev/ttyUSB0 | grep -m1 'KERNELS=="[0-9]' ``` ## Troubleshooting - **`/dev/inspire_*` missing / no CH340 in `lsusb`** → the hand USB-RS485 adapters are unplugged (or robot rebooted with them out). Re-plug them; udev recreates the names. - **Fingers don't move (thumb might)** → usually a latched actuator fault from running two `inspire_g1` at once. `inspire_g1` now runs `ClearError` + sets speed/force on startup; `./manage.sh restart` re-applies it. If still stuck, power-cycle the hands (24 V). - **Two `inspire_g1` instances** → they collide on the serial bus; `./manage.sh status` flags it, `restart` fixes it. Kill with `pkill -x inspire_g1` (never `pkill -f`). - **`rt/inspire/state` reads all-zeros** → cosmetic; CH340 adapters echo their TX so position read-back is unreliable. Does not affect commands sent to the hand.