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.
Manual recorder — record and replay without the dashboard
The backup plan. Same script the web dashboard runs (g1_record_replay.py), driven from a
terminal instead. Use it when Docker, the dashboard or the network are unavailable — or
when you just prefer a shell.
Run it
./record.sh doctor # what's available here, what's missing
./record.sh record --output wave --seconds 20 # a take
./record.sh replay --input wave --speed 0.5 # play it back
record.sh finds a working runtime for you, best first:
$RECORDER_PY— if you named a python- any python that can already import
unitree_sdk2py(e.g. condag1_env) - the vendored SDK on
PYTHONPATH - the
inspire-handcontainer, which has the SDK baked in
./record.sh --how prints which one it picked without running anything. Every other flag
is passed straight through to the recorder, and the interface is inserted automatically
(eth0 on the robot, enp3s0 elsewhere — override with IFACE=).
Prefer the raw command? It is exactly:
python3 g1_record_replay.py enp3s0 record --output wave --seconds 20
docker exec -it -w /opt/recorder inspire-hand python3 g1_record_replay.py eth0 replay --input wave
Where takes land: in DataG1/ next to wherever you run it. Through the container that
is /opt/recorder/DataG1 = ~/hand_data/DataG1 on the robot — the same library the
dashboard lists, so a CLI take shows up there and vice versa.
A take, start to finish
- Arms hold for ~3 s, then go limp — have hold of the arm before that.
- Move the arm by hand. Fingers cannot be posed by hand, so drive them with the keys.
- At the end it offers a preview (⚠ the arm replays what you just did) and then a save.
Ctrl-Cstops a take early and still saves — the arm goes home and releases first.
Keys during a take: o/c both open/close · [ ] right · ; ' left ·
1-9 saved dashboard shapes · f arm/disarm follow.
During replay: p or space pauses and resumes; the arm holds the frame it stopped on.
Whether the keys do anything depends on --fingers:
| mode | keys | what gets recorded |
|---|---|---|
touch (default) |
ignored — the hand holds the rest pose | how far a finger closes, from how hard you press it |
position |
drive the hand for real | the finger's measured angle |
external |
ignored — the recorder sends no hand commands | the angle the :8088 sliders reach |
follow |
only f and the shape keys |
the angle you push a finger into |
Flags worth knowing
--seconds (20) · --no-arm / --no-hand · --thresh (12 g touch) · --span (120 g =
fully closed) · --speed · --waist recorded|lock · --home arm_home.jsonl ·
--shapes <dashboard URL>|none · --thumb-rot closed|open. Full list: --help.
Carrying it elsewhere
./bundle.sh # -> manual_recorder_<date>.tar.gz (~2 MB)
./bundle.sh --with-takes # include DataG1/*.jsonl
Unpack anywhere, cd manual_recorder/recorder, ./record.sh doctor.
The one thing a tarball cannot carry is the compiled DDS bindings. On a machine with neither the conda env nor the container, install them once:
pip install "cyclonedds==0.10.2" # needs CycloneDDS 0.10.x + its headers present
pip install -e ../vendor/unitree_sdk2_python
Cautions
- Running this from the workstation drives the real robot over
enp3s0. - Do not run it while the dashboard has a take going — two writers on the same topics.
- The hand needs
inspire_g1running on the robot (the container). Arm-only takes (--no-hand) do not.