Walker TienKung Dex Real-Robot Workflow: Collect -> Convert -> Train -> Evaluate -> Deploy¶
Robot: Walker TienKung Dex ✅ Verified on hardware (Nov 2026): the full loop works with BrainCo 13D (right arm 7 + right hand 6, 30 fps) real-robot data. The steps are the same for both hands; only the hand variant /
JOINT_CONFIG/ conversion config differ.
Contents¶
- 0. Workflow overview
- 1. Prerequisite: real-robot data collection
- 2. Start the container (ubt_IL)
- 3. Data conversion (HDF5 -> LeRobot)
- 4. Policy training
- 5. Policy evaluation (offline MSE)
- 6. Model deployment (real robot)
- 7. FAQ
0. Workflow overview¶
Real-robot collection (trajectory.hdf5)
│ right arm 7 + right hand 13 (or BrainCo 6)
▼
scripts/convert/tienkung_dex/convert.sh ──► dataset/tienkung_dex_real_<task>
▼
scripts/train/tienkung_dex/train.sh ──► model/tienkung_dex_real_<task>_act
▼
scripts/eval/eval_policy.py
▼
robot_ready.sh --real → rollout.sh ──► real-robot execution
Training and deployment are essentially identical to simulation, using the same scripts with slightly different dataset configuration.
1. Prerequisite: real-robot data collection¶
Data is currently collected with the Thinker Studio teleoperation platform (see the official documentation), which can export LeRobot v3.0 datasets directly. You can also place collected real-robot HDF5 data under /ubt_IL/dataset/hdf5/ (or any SRC_ROOT) and proceed to conversion.
Verified real-robot dataset: tienkung_dex_real_pick_apple_brainco_13d (32 episodes / 16,956 frames / 30 fps; both observation.state and action are 13D = right arm 7 + BrainCo hand 6; head camera observation.images.head 640×360 h264). The first sim2real validation reused a simulation-trained policy; once enough real data was available (≥30 episodes), training switched to real data directly. To mix simulation and real data, collect more first, then merge and convert.
2. Start the container (ubt_IL)¶
cd ubt_IL/docker
bash run.sh build # first time (an x86 workstation build is sufficient)
bash run.sh start
bash run.sh check
bash run.sh bash
To run the policy directly on the robot (Thor) without a container, see On-robot deployment.
3. Data conversion (HDF5 -> LeRobot)¶
Verified path: BrainCo 13D real-robot data (config configs/tienkung_dex_13d_1RGB_brainco_real.json; 13D = right arm 7 + BrainCo hand 6 closure values 0~1; head camera observation.images.head 640×360, monocular RGB):
# Real-robot BrainCo 13D conversion (real HDF5 has no depth; this is expected)
CONFIG=/ubt_IL/scripts/convert/tienkung_dex/configs/tienkung_dex_13d_1RGB_brainco_real.json \
SRC_ROOT=/ubt_IL/dataset/hdf5/tienkung_dex_real \
REPO_ID=tienkung_dex_real_pick_apple_brainco_13d \
TASK_NAME=pick_the_apple \
FPS=30 \
bash /ubt_IL/scripts/convert/tienkung_dex/convert.sh --overwrite
FPS=30matches the real recording rate (the verified dataset is 30 fps).TRIM_STATIONARYcan be enabled as needed for real data: the recording rate matches the main loop and there are no simulation-style duplicate frames spread across the motion, so trimming long stationary ready-pose segments at the start and end is beneficial.
Other hand variants:
| Hand / dataset | Conversion config | Dimension |
|---|---|---|
| Inspire r13 (pico6, 6D 0~1) | tienkung_dex_real_pick_r13_1RGB.json |
13D |
| Inspire 20D (13D rad) | tienkung_dex_20d_1RGB_inspire.json |
20D |
| BrainCo (6D 0~1) | tienkung_dex_13d_1RGB_brainco_real.json |
13D (verified) |
Visualize after conversion:
HF_HUB_OFFLINE=1 lerobot-dataset-viz \
--repo-id tienkung_dex_real_pick_apple_brainco_13d \
--episode-index 0 \
--root /ubt_IL/dataset/tienkung_dex_real_pick_apple_brainco_13d
4. Policy training¶
Verified path: BrainCo 13D real-robot training (config train_config_tienkung_dex_real_pick_apple_brainco_13d.json, dataset tienkung_dex_real_pick_apple_brainco_13d, output tienkung_dex_real_pick_apple_brainco_13d_act):
CONFIG_PATH=/ubt_IL/scripts/train/tienkung_dex/configs/train_config_tienkung_dex_real_pick_apple_brainco_13d.json \
OUTPUT_DIR=/ubt_IL/model/tienkung_dex_real_pick_apple_brainco_13d_act \
bash /ubt_IL/scripts/train/tienkung_dex/train.sh
train.shdefaultsCONFIG_PATHto the simulation config; real-robot training must override it explicitly.train_config_tienkung_dex_real_pick_apple.jsonis a copy of the simulation config that only changesdataset.repo_id/dataset.root/policy.repo_id/output_dir/job_name; if the realREPO_IDdiffers from the conversion, update those fields. When real lighting differs strongly from simulation, addimage_transforms(color jitter / random affine) by referring totrain_config_tienkung_pro_real_pick_place.json.
Everything else is identical to simulation (see Simulation workflow · Training).
5. Policy evaluation (offline MSE)¶
/lerobot/.venv/bin/python /ubt_IL/scripts/eval/eval_policy.py \
--policy-path /ubt_IL/model/tienkung_dex_real_pick_apple_brainco_13d_act/checkpoints/last/pretrained_model \
--dataset-path /ubt_IL/dataset/tienkung_dex_real_pick_apple_brainco_13d \
--episodes 5 --inference-freq 1 --plot \
--plot-dir /ubt_IL/scripts/eval/output/eval_tienkung_dex_real_pick_apple_brainco_13d_act \
--output /ubt_IL/scripts/eval/output/eval_tienkung_dex_real_pick_apple_brainco_13d_act/results.json \
--device cuda
With small real datasets, inspect per-dimension curves for plausibility (hand jitter, wrist overshoot) instead of relying only on the total MSE.
6. Model deployment (real robot)¶
Prerequisites¶
- Enter topic-control mode: use the remote to switch to the half-body policy (toggle right while standing).
- Legs are owned by motion control:
robot_ready.pynever publishes to/leg/cmd; Bridge2 publishes only/arm/cmdand hand commands. - Hand variant matches:
--variant inspire|braincomust match the physical hand (HAND_VARIANT/JOINT_CONFIGmust follow). - Emergency stop available: Bridge2 monitors
is_estopon/power/board/key_statusand stops publishing when true. - Inspire r13 uses 6→13 expansion:
JOINT_CONFIG=tienkung_dex_inspire_13automatically derivesHAND_ACTION_MODE=pico6— the policy outputs a 6D normalized 0~1right_hand(0=open, 1=closed), and Bridge2 expands it into 13 0.1° codes withpico6_to_inspire13(); state echoes the same 6D command (open loop). Passing 13D is rejected as a dimension mismatch; passing rad moves the fingers barely at all.
First run: ready pose only¶
Hardware uses READY_POSE (not the simulation READY_POSE_TARGET, which is a gravity feed-forward compensation that makes command equal actual; using it on hardware leaves the robot about 13 cm lower).
The READY_POSE was validated against the simulation view during real-robot integration. With arms hanging naturally, the hands are at 0.80 m, below the table (0.987 m), so the motion must be staged in four segments (READY_CLEARANCE_POSE → READY_FOREARM_POSE → READY_UPPER_VERTICAL_POSE → final, 5 s each by default).
Deployment: short duration and small limits first¶
Verified path: BrainCo 13D real-robot deployment (JOINT_CONFIG=tienkung_dex_brainco_13, policy 13D = right arm 7 + BrainCo hand 6 values 0~1; FPS matches the dataset, 30):
# Bridge2 is started by rollout.sh (--robot.bridge_enabled=true); do not start it manually.
# Any leftover dex bridge process on the same host is killed before startup.
ROS_DOMAIN_ID=0 \
POLICY_PATH=/ubt_IL/model/tienkung_dex_real_pick_apple_brainco_13d_act/checkpoints/last/pretrained_model \
JOINT_CONFIG=tienkung_dex_brainco_13 \
CAMERA_PORT=5558 IMAGE_ENABLED=1 \
FPS=30 \
DURATION=10 MAX_RELATIVE_TARGET=0.01 \
bash /ubt_IL/scripts/deploy/tienkung_dex/rollout.sh
Demo:
Hand representations¶
| Mode | JOINT_CONFIG |
Policy-side right_hand |
Conversion |
|---|---|---|---|
| BrainCo 13D | tienkung_dex_brainco_13 |
6D closure 0~1 | Action code = round(1 + v*999); state v = (code-1)/999 |
| Inspire 20D | tienkung_dex_20 |
13D joint angles in rad | Bridge2 converts each channel with inspire_rad_to_code to 0.1° codes |
| Inspire r13 | tienkung_dex_inspire_13 |
6D normalized 0~1 | pico6_to_inspire13() expands to 13 codes |
When bypassing the plugin and feeding
right_handdirectly to Bridge2:brainco/pico6must be 0~1;rad13must be 13D rad. Passing 0~1 torad13is interpreted as rad and the fingers barely move.
On-robot deployment (Thor / Jazzy, no container)¶
The path above runs the policy in an x86 workstation container while the robot only executes. To run the policy directly on the robot's ARM board (NVIDIA Thor, Ubuntu 24.04 + ROS 2 Jazzy + Python 3.12), use the host scripts in On-robot deployment, with no Docker. Data conversion and training are still recommended on a workstation/container; the robot only performs deployment.
Warm-started inference service¶
ROS_DOMAIN_ID=0 \
POLICY_PATH=/ubt_IL/model/tienkung_dex_real_pick_apple_brainco_13d_act/checkpoints/last/pretrained_model \
JOINT_CONFIG=tienkung_dex_brainco_13 CAMERA_PORT=5558 IMAGE_ENABLED=1 FPS=30 \
bash /ubt_IL/scripts/deploy/tienkung_dex/inference_server.sh
bash /ubt_IL/scripts/deploy/tienkung_dex/inference_client.sh status # wait for READY
bash /ubt_IL/scripts/deploy/tienkung_dex/inference_client.sh start
bash /ubt_IL/scripts/deploy/tienkung_dex/inference_client.sh stop
bash /ubt_IL/scripts/deploy/tienkung_dex/inference_client.sh shutdown
Dex does not support
load(model hot-swap): the server's robot reconstruction path hard-codes--robot.type=walkerwithout--robot.cameras, which rebuilds a camera-less robot for Dex. To change models,shutdownandlaunchagain.
7. FAQ¶
| Symptom | Check |
|---|---|
| Topics are visible but no data arrives | Cross-distro type hash mismatch: compare ros2 topic info -v on both sides; or fall back to running Bridge2 on the compute host |
/arm/cmd is published but the robot does not move |
Is topic-control mode active (remote half-body/full-body policy + toggle up)? Is is_estop true on /power/board/key_status? |
| Arm motion is too large / starts too abruptly | Is MAX_RELATIVE_TARGET set? Is DURATION still the short first-run value? |
| Pose is lower than expected | The simulation READY_POSE_TARGET was used; hardware should use robot_ready.sh --real (READY_POSE) |
| Hand variant is clearly wrong | HAND_VARIANT / JOINT_CONFIG does not match the physical hand (Inspire 13 channels ↔ BrainCo 6 motors) |
Inspire hand does not move / log reports right_hand dimension mismatch |
r13 models need JOINT_CONFIG=tienkung_dex_inspire_13 (pico6, 6D actions 0~1); only the older 20D model uses tienkung_dex_20 (rad13, 13D rad actions) |
| Thumb abduction differs from the ready pose | Channel 10 of the pico6 expansion is determined by thumb_roll (all zeros → code 1750); the first policy frame overrides the ready-pose code 850 from robot_ready |
| No camera image | Does Bridge2 run with image_enabled=true? Is the port 5558? Does /ob_camera_head/color/image_raw publish? |
| Bridge2 exits immediately | "Channel table mismatch" means the plugin and Bridge2 constant copies have drifted; "message package missing" means /opt/tienkung_dex_msgs_ws was not sourced |
| Robot still moves slightly after emergency stop | Bridge2 only stops publishing new commands; previously sent targets remain in the drives. Press the physical emergency stop |