# 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 <left-hand-port> 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.
