Dashboard (web/hand_web.py) - Record/replay panel driving g1_record_replay.py as a pty child: take library (replay/download/duplicate/rename/delete/upload/delete-all), pause & resume, and in-take key buttons that grey out in the --fingers modes the recorder ignores (measured: in touch mode the keys change nothing at all). - /api/restart is container-aware: it kills inspire_g1 and lets the supervisor relaunch it. It used to run manage.sh, which started a SECOND inspire_g1 beside the supervised one - two writers on one RS-485 bus - and never returned. - Shape/combo libraries take a .bak on every write, with an undo button. Both files are rewritten in full, so deleting the last entry was unrecoverable. recorder/ - The recorder lives in this project now: one source of truth for the CLI and the dashboard, with pause/resume added to replay. - record.sh picks a runtime by itself (a python with the SDK, the vendored SDK, or the inspire-hand container). bundle.sh packs a ~340KB portable kit. tools/ - preflight.sh: read-only readiness report for a new robot (hardware, docker, build prerequisites, per-robot settings) ending in an install-path verdict. - fetch_deps.sh: stage build dependencies, verifying the libs are aarch64. - export_ui.py: regenerate an embedding app's vendored copy of the UI. docker/ - build_image.sh resolves its dependencies from several layouts: deps/ inside the project, /usr/local, a source install prefix, or a ROS2 colcon workspace (where the idl headers live when /usr/local has none). - web/ is copied in the last layer, so dashboard edits skip the C++ rebuild. - restart=always, and start.sh always builds so an edit cannot silently run a stale image. deps/unitree_sdk2 is vendored so a robot that has never seen the SDK can build. Docs: README quickstart + embedding notes, SETUP_G1 corrected (that udev rule stopped creating /dev/inspire_* symlinks a while ago), ROBOT_README describing a live install.
121 lines
6.0 KiB
Markdown
121 lines
6.0 KiB
Markdown
# 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.
|