kassam 2a78f1b609 Record/replay studio, manual recorder kit, and new-robot install tooling
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.
2026-08-28 20:27:28 +04:00
..
2026-08-04 16:09:17 +04:00
2026-08-04 16:09:17 +04:00
2026-08-04 16:09:17 +04:00
2026-08-04 16:09:17 +04:00

Dockerized Inspire hand service + dashboard

One self-contained image (aarch64) running inspire_g1 + hand_bridge + the Flask dashboard. Build ON the robot (it pulls unitree_sdk2 + CycloneDDS from the host).

Build (once, on the robot)

cd ~/DFX_inspire_service
./docker/build_image.sh          # assembles context + docker build -> inspire-hand:latest

Run

./docker/run.sh                  # or: docker compose -f docker/docker-compose.yml up -d
# dashboard -> http://<robot-ip>:8088
./docker/stop.sh

run.sh first stops any host-native inspire_g1/hand_bridge — running both the container and the host service would put two writers on the RS-485 bus and corrupt the hands.

Why these flags

  • --network host — CycloneDDS (rt/inspire/cmd) and the arm actions (rt/arm_sdk, reaching the G1 motion controller on eth0) need the host network.
  • --privileged -v /dev:/dev — the CH340 ttyUSB* adapters re-enumerate on the hub; mounting /dev + privileged lets the in-container dynamic detection find them.
  • IFACE=eth0 — interface for arm actions (must be eth0). Fingers work on either.

Record / replay in the image

The dashboard's Record/Replay card runs g1_record_replay.py as a child process, so the image needs the script and a python that can import unitree_sdk2py. build_image.sh pulls both into the context:

RECORDER_SRC=~/Manual_Recorder \
UNITREE_SDK2PY_DIR=~/unitree_sdk2_python \
  ./docker/build_image.sh

Neither is required — leave them out and the image still builds, with the panel reporting "recorder not found". start.sh prints which case you got.

  • That layer pip-installs (cyclonedds, unitree_sdk2py), so the robot needs network at BUILD time. Offline alternative: copy the robot's own python site-packages into the image the same way build_image.sh copies the C libs.
  • Takes are a host volumerun.sh mounts $HAND_DATA_DIR/DataG1 to /opt/recorder/DataG1, because run.sh starts with docker rm -f and would otherwise destroy every recording ever made.
  • arm_home.jsonl is seeded on first start, not baked into DataG1/ — the bind-mount would hide a baked copy, and without it every take skips its return-to-home. It is hidden from the takes list and cannot be deleted from the panel: it is a home pose, not a take.

Notes

  • Sudo/udev not needed inside the container (privileged).
  • Saved poses/combos persist via -v $HAND_DATA_DIR:/opt/hand/data + HAND_DATA (set by run.sh). They used to live in the image and were wiped by every rebuild.