Trong bài 4, chúng ta đã đi qua lớp C++ deployment: ONNX, TensorRT, ZMQ output và vòng điều khiển thời gian thực. Bài 5 nối lớp đó với phần rất quan trọng cho VLA: teleoperation bằng PICO VR và pipeline ghi dữ liệu LeRobot. Nếu bài 4 trả lời câu hỏi "policy chạy trên robot như thế nào?", bài này trả lời câu hỏi "ta thu demo người điều khiển như thế nào để sau đó fine-tune VLA?".
Điểm hay của SONIC là VLA không cần học trực tiếp toàn bộ low-level joint control từ đầu. Tài liệu VLA workflow của NVlabs mô tả cách VLA dự đoán latent motion token 64 chiều cùng 7 khớp tay trái và 7 khớp tay phải, còn SONIC decoder xử lý điều khiển toàn thân ở 50 Hz. Vì vậy, dữ liệu teleop không chỉ là video và joint state. Nó còn là cầu nối giữa ngôn ngữ task, camera ego-view, trạng thái robot, motion token, SMPL pose, planner command và action tay.
Nguồn kỹ thuật nên mở song song:
| Nguồn | Dùng để kiểm tra gì |
|---|---|
| Data Collection for VLA | Lệnh tmux/manual, cổng 5555/5556/5557, camera viewer, output LeRobot |
| VLA Workflow | Cách dữ liệu teleop đi tới fine-tune Isaac-GR00T N1.7 và latent action interface |
install_pico.sh |
Cài .venv_teleop, Python 3.10, gear_sonic[teleop], XRoboToolkit |
pico_manager_thread_server.py |
POSE/PLANNER/VR_3PT, combo PICO, ZMQ port 5556/5557 |
launch_data_collection.py |
Launcher tmux dùng --input-type zmq_manager |
run_data_exporter.py |
Ghi frame, nhận camera, SMPL pose, robot state và tạo dataset |
| GR00T LeRobot v2 data format | Layout data/, videos/, meta/info.json, modality.json, tasks.jsonl |
Nếu bạn mới đọc series, nên quay lại bài 1 về kiến trúc SONIC để hiểu encoder-decoder-token, rồi đọc bài 3 về dữ liệu và training để thấy vì sao SMPL, motion library và token state cùng xuất hiện trong pipeline này. Ngoài series, hai bài LeRobot cho humanoid G1 và fine-tune VLA hai tay là bối cảnh tốt cho phần dataset.

1. Bức tranh tổng thể: teleop không chỉ là remote control
Với robot công nghiệp truyền thống, "teleop" thường có nghĩa là người điều khiển gửi vận tốc hoặc pose target trực tiếp. Với SONIC, teleop có thêm một mục tiêu: tạo ra demonstration có thể học được. Một phiên thu dữ liệu tốt phải đồng bộ ít nhất bốn dòng dữ liệu:
| Dòng dữ liệu | Nguồn | Đi vào dataset để làm gì |
|---|---|---|
| Camera ego-view | Camera server hoặc MuJoCo image publisher, port 5555 |
Observation hình ảnh cho VLA |
| PICO/SMPL/VR pose | pico_manager_thread_server.py, port 5556 |
Action intent, SMPL pose, wrist/hand target, stream mode |
| Robot state | C++ deploy g1_debug, port 5557 |
Proprioception, action WBC, root orientation, token state |
| Task prompt | CLI --task-prompt |
Annotation ngôn ngữ trong tasks.jsonl và parquet |
Điểm quan trọng là PICO không điều khiển motor trực tiếp. PICO gửi pose, planner command hoặc VR 3-point target qua ZMQ. C++ deploy chạy với --input-type zmq_manager, nhận command đó, đưa qua SONIC policy/planner và phát motor command. Song song, data exporter cũng subscribe các topic ZMQ để ghi lại cùng một thời điểm robot nhìn gì, robot đang ở trạng thái nào, người đang ra lệnh gì và SONIC đang tạo action gì.
Một sơ đồ tối giản:
PICO headset + controllers
|
v
pico_manager_thread_server.py -- PUB tcp://*:5556
| topics: pose, planner, manager_state
+---------------------> C++ deploy --input-type zmq_manager
| publishes g1_debug + robot_config on 5557
v
run_data_exporter.py <--------- camera server on 5555
|
v
LeRobot v2.1 dataset: parquet + MP4 + meta/*.json
Trong workflow này, --input-type zmq_manager là lựa chọn đúng cho thu dữ liệu VLA vì nó nói với C++ deployment rằng input không đến từ keyboard hay gamepad cục bộ, mà đến từ một "manager" ZMQ biết chuyển mode giữa POSE, PLANNER và VR_3PT. Nếu bạn chạy sai input type, PICO streamer có thể vẫn publish dữ liệu, exporter vẫn ghi được một số tín hiệu, nhưng policy không nhận command đúng đường.
2. install_pico.sh: môi trường teleop riêng cho PICO
Script install_scripts/install_pico.sh không chỉ pip install một package. Nó dựng một môi trường riêng .venv_teleop để PICO teleop có đúng Python, native SDK và dependency. Luồng chính của script là:
- Phát hiện kiến trúc bằng
uname -m, ví dụx86_64trên workstation hoặcaarch64trên Jetson Orin. - Cài
uvnếu máy chưa có. - Cài Python 3.10 do
uvquản lý, có development headers. - Xóa
.venv_teleopcũ, tạo venv mới với promptgear_sonic_teleop. - Cài
gear_sonic[teleop]. - Cài
cmake,pybind11,setuptools, rồi cài XRoboToolkit SDK. - Nếu chạy trên
aarch64và thiếu native library, script buildPXREARobotSDKtừ branchorin. - Trên desktop hoặc máy không phải onboard Jetson user
unitree, script cài thêmgear_sonic[sim]vàunitree_sdk2_pythonđể hỗ trợ sim bridge.
Đây là một chi tiết beginner hay bỏ qua: PICO teleop cần XRoboToolkit native SDK, không chỉ Python wheel. Nếu bạn thấy lỗi import xrobotoolkit_sdk, hoặc PICO không có body data, hãy kiểm tra lại script cài teleop trước khi debug policy. Lệnh chuẩn từ repo root là:
bash install_scripts/install_pico.sh
source .venv_teleop/bin/activate
python gear_sonic/scripts/pico_manager_thread_server.py --manager
Trên robot thật, PICO, workstation và G1 thường phải ở cùng mạng ổn định. Tài liệu data collection nhắc đến IP mặc định của G1 là 192.168.123.164 khi workstation kết nối camera server trên robot. Với teleop, độ trễ mạng không chỉ làm video chậm; nó còn làm command pose và dataset mất đồng bộ.
3. Ba cổng ZMQ cần nhớ: 5555, 5556, 5557
Trong stack này, ba port mặc định có vai trò rất rõ:
| Port | Producer | Consumer | Nội dung |
|---|---|---|---|
5555 |
Camera server hoặc run_sim_loop.py --enable-image-publish |
run_data_exporter.py, run_camera_viewer.py |
Frame camera, thường có ego_view, có thể thêm wrist camera |
5556 |
pico_manager_thread_server.py |
C++ deploy và data exporter | Topic pose, planner, manager_state |
5557 |
C++ deploy zmq_output_handler |
data exporter, PICO planner feedback | Topic g1_debug, robot_config |
run_data_exporter.py ghi rõ nó không phụ thuộc ROS 2: robot state đến từ topic g1_debug trên port 5557, SMPL pose đến từ topic pose trên port 5556, và camera đến qua ComposedCameraClientSensor. Robot config được đọc từ topic robot_config trên cùng socket state. Nếu exporter không nhận được robot_config, nó không biết cấu hình robot đủ chắc để ghi metadata đúng.
Trong simulation, docs dùng:
python gear_sonic/scripts/run_sim_loop.py \
--enable-image-publish --enable-offscreen --camera-port 5555
Trong deployment, pane C++ thường chạy:
cd gear_sonic_deploy
./deploy.sh --input-type zmq_manager sim
Với robot thật và camera server trên G1:
python gear_sonic/scripts/run_data_exporter.py \
--task-prompt "pick up the cup" \
--camera-host 192.168.123.164 \
--camera-port 5555
Nếu chỉ muốn xem camera trước khi record, dùng:
python gear_sonic/scripts/run_camera_viewer.py \
--camera-host localhost \
--camera-port 5555
Camera viewer không ghi dataset LeRobot. Nó hiển thị các stream camera trong một cửa sổ OpenCV, phím R để start/stop record MP4 thô và Q để thoát. Đây là bước rất nên làm trước khi thu demo thật: kiểm tra exposure, góc nhìn ego-view, độ rung, frame drop và việc wrist camera có thực sự xuất hiện hay không.
4. PICO manager: POSE, PLANNER và VR_3PT
Trong pico_manager_thread_server.py, StreamMode có các giá trị:
| Mode | Giá trị | Ý nghĩa thực tế |
|---|---|---|
OFF |
0 | Không stream command điều khiển policy |
POSE |
1 | Stream full SMPL/body pose từ PICO để SONIC tracking |
PLANNER |
2 | Dùng joystick/controller để điều khiển locomotion planner |
PLANNER_FROZEN_UPPER_BODY |
3 | Planner di chuyển thân dưới, giữ mục tiêu upper body đã chốt |
POSE_PAUSE |
4 | Tạm pause pose khi giữ left menu, thả ra quay lại POSE |
PLANNER_VR_3PT |
5 | Planner cho locomotion, VR 3 điểm cho upper body |
Tên user thường gọi "VR_3PT" thực chất là PLANNER_VR_3PT: robot vẫn cần planner để đi, còn upper body nhận ba keypoint VR. Code _process_3pt_pose() lấy Root/Pelvis, Left Wrist, Right Wrist và Neck từ SMPL joints, chuyển từ Unity frame sang robot frame, áp offset rotation, rồi trả về ba keypoint không tính root. Ghi chú trong code nói Neck được dùng thay Head vì ổn định hơn cho hướng thân trên.
Các combo PICO quan trọng:
| Combo | Tác dụng |
|---|---|
A+B+X+Y |
Khi đang OFF, start policy vào PLANNER; khi đang chạy, emergency stop về OFF |
A+X |
Toggle giữa POSE và PLANNER theo chain chính |
B+Y |
Toggle giữa POSE và PLANNER_FROZEN_UPPER_BODY |
| Left axis click | Vào hoặc thoát PLANNER_VR_3PT từ mode planner hiện tại |
| Left menu hold | Trong POSE, chuyển sang POSE_PAUSE; thả ra quay lại POSE |
| Left Grip + A | Toggle recording episode |
| Left Grip + B | Đánh dấu abort/discard episode |
Trong planner loop còn có combo A+B để tăng locomotion mode và X+Y để giảm locomotion mode. Danh sách mode gồm IDLE, SLOW_WALK, WALK, RUN, các tư thế kneel/lie/crawl, boxing, hook, jump, stealth walk và injured walk. Joystick điều khiển hướng, vận tốc, facing và height. Từ góc nhìn operator, đây là cách tách việc: controller PICO vừa là công tắc mode, vừa là nguồn pose, vừa là remote locomotion.

5. Thu dữ liệu bằng launcher tmux
launch_data_collection.py là script tiện nhất khi bạn muốn chạy cả stack trong một session tmux. Config mặc định của nó rất đáng chú ý: deploy_input_type là zmq_manager, pico_manager bật, camera_viewer bật và camera_port là 5555. Với simulation:
python gear_sonic/scripts/launch_data_collection.py --sim
Launcher sẽ tạo session sonic_data_collection, mở pane cho C++ deploy, PICO manager, data exporter, camera viewer, và nếu --sim được truyền thì thêm window MuJoCo simulator. Tài liệu chính thức cũng nhắc rằng launcher tự dùng .venv_data_collection khi dependency chưa có trong Python hiện tại, nên bạn không nhất thiết phải activate venv trước.
Với robot thật:
python gear_sonic/scripts/launch_data_collection.py \
--camera-host 192.168.123.164 \
--task-prompt "pick up the cup"
Nếu có wrist camera:
python gear_sonic/scripts/launch_data_collection.py \
--camera-host 192.168.123.164 \
--task-prompt "pick up the cup" \
--record-wrist-cameras
Một điều thực tế: C++ deploy pane có thể chờ xác nhận trước khi robot bắt đầu control. Đừng record ngay khi tmux vừa mở. Hãy đợi C++ báo init xong, kiểm tra camera viewer có frame, PICO manager thấy body data, rồi mới dùng Left Grip + A để mở episode. Nếu cần detach session, dùng Ctrl+b rồi d; reattach bằng tmux attach -t sonic_data_collection.
6. run_data_exporter.py: một frame LeRobot gồm những gì?
Exporter chạy ở --data-collection-frequency 50 Hz mặc định. Mỗi vòng, nó poll state ZMQ, poll PICO ZMQ, kiểm tra recording command, đọc camera và thêm một frame vào Gr00tDataExporter.
Các field quan trọng:
| Field | Nguồn | Ý nghĩa |
|---|---|---|
observation.images.ego_view |
Camera | Ảnh chính cho VLA |
observation.state |
Robot model + g1_debug |
Cấu hình toàn robot, gồm body và hands |
observation.eef_state |
Forward kinematics | Pose hai wrist, mỗi bên position + quaternion |
action.wbc |
Last action từ SONIC/C++ | Action toàn thân sau WBC |
action.motion_token |
token_state nếu có |
Latent motion token 64 chiều |
teleop.smpl_joints |
PICO pose mode | 24 joints x 3, flatten thành 72 |
teleop.smpl_pose |
PICO pose mode | SMPL body pose 63 chiều |
teleop.stream_mode |
manager_state |
Biết frame thuộc POSE, PLANNER hay VR_3PT |
teleop.left_hand_joints, teleop.right_hand_joints |
PICO trigger/grip hoặc planner message | Action tay 7 chiều mỗi bên |
teleop.vr_3pt_position, teleop.vr_3pt_orientation |
VR_3PT | Ba keypoint và orientation cho upper body |
Exporter chỉ dùng SMPL pose khi stream mode là POSE hoặc POSE_PAUSE và message chưa stale. Code có ngưỡng khoảng 100 ms cho SMPL pose; nếu quá cũ, nó ghi zeros để tránh đưa pose lỗi vào dataset. Với PLANNER_VR_3PT, exporter dùng planner message và có ngưỡng khoảng 200 ms. Đây là lý do sau khi thu xong cần chạy process_dataset.py: frame stale hoặc episode bị abort vẫn có thể nằm trên đĩa.
Recording có hai lớp điều khiển. Từ PICO, Left Grip + A toggle record; Left Grip + B đánh dấu discard. Từ keyboard ZMQ, key c toggle recording và key x discard episode qua port 5580. Trong thực tế, PICO combo tiện hơn vì operator không cần rời tay khỏi controller.
7. Output LeRobot v2.1: parquet, MP4 và metadata
Tài liệu data collection nói dataset được lưu dưới <root-output-dir>/<dataset-name>/, mặc định là outputs/<timestamp>/. Layout thực tế theo LeRobot v2.1/GR00T LeRobot gồm dữ liệu bảng, video và metadata:
outputs/my_dataset/
├── data/
│ └── chunk-000/
│ ├── episode_000000.parquet
│ └── episode_000001.parquet
├── videos/
│ └── chunk-000/
│ └── observation.images.ego_view/
│ ├── episode_000000.mp4
│ └── episode_000001.mp4
└── meta/
├── info.json
├── modality.json
├── episodes.jsonl
└── tasks.jsonl
Một số docs của exporter cũ có thể hiển thị dạng data/train-00000.parquet, nhưng tài liệu GR00T LeRobot v2 và code process_dataset.py đều xử lý pattern chunked data/chunk-{episode_chunk:03d}/episode_{episode_index:06d}.parquet và video videos/.../episode_*.mp4. Khi đọc dataset, hãy tin meta/info.json vì nó chứa path pattern và feature schema.
Ý nghĩa từng file:
| File | Vai trò |
|---|---|
episode_*.parquet |
Mỗi row là một frame: state, action, timestamp, episode index, task index, annotation |
episode_*.mp4 |
Video camera đã encode, thường H264/MP4 cho ego view và optional wrist cameras |
meta/info.json |
FPS, feature schema, tổng frame/episode, path template, script_config, có thể có discarded_episode_indices |
meta/modality.json |
Metadata GR00T-specific để tách các mảng state/action thành field có nghĩa |
meta/tasks.jsonl |
Mapping task_index sang mô tả ngôn ngữ, ví dụ "pick up the cup" |
meta/episodes.jsonl |
Metadata từng episode: độ dài, task, index |
modality.json đặc biệt quan trọng với GR00T. Trong LeRobot chuẩn, state/action có thể là mảng nối. GR00T cần biết đoạn nào là left leg, right leg, wrist pose, root orientation, motion token, SMPL pose, planner movement, VR 3-point orientation. File features_sonic_vla.py tạo modality config này từ RobotModel, không hardcode mọi thứ bằng tay. Nếu bạn copy parquet mà quên meta/modality.json, training có thể load file nhưng hiểu sai ý nghĩa từng dimension.
8. process_dataset.py: dọn trước khi fine-tune
Sau khi thu demo, đừng đưa ngay vào fine-tune. process_dataset.py làm ba việc chính:
- Xóa episode bị đánh dấu discard trong
meta/info.json. - Xóa frame có
teleop.smpl_posetoàn zeros và các frame "đứng hình" ngay trước đó. - Gộp nhiều session thành một dataset, nếu
script_configkhớp.
Lệnh dọn một dataset và ghi ra thư mục mới:
python gear_sonic/scripts/process_dataset.py \
--dataset-path outputs/my_dataset \
--output-path outputs/my_dataset_cleaned
Nếu muốn xử lý tại chỗ:
python gear_sonic/scripts/process_dataset.py \
--dataset-path outputs/my_dataset
Nếu thu bằng VR_3PT, cần chú ý cảnh báo trong docs: teleop.smpl_pose sẽ toàn zeros vì VR_3PT dùng raw VR positions/orientations thay vì SMPL body parameters. Nếu vẫn bật stale SMPL cleaning, script có thể drop toàn bộ frame. Khi đó dùng:
python gear_sonic/scripts/process_dataset.py \
--dataset-path outputs/my_dataset \
--output-path outputs/my_dataset_cleaned \
--no-remove-stale-smpl
Để merge nhiều session:
python gear_sonic/scripts/process_dataset.py \
--dataset-path outputs/session1 outputs/session2 outputs/session3 \
--output-path outputs/merged_dataset
Script kiểm tra script_config để tránh merge dữ liệu thu bằng cấu hình robot khác nhau. Đây là guard quan trọng: VLA rất dễ học sai nếu cùng một observation.state dimension nhưng ý nghĩa robot, camera hoặc wrist setup thay đổi.
9. Checklist cho một buổi thu demo sạch
Trước khi bấm record, hãy đi theo checklist này:
| Bước | Kiểm tra |
|---|---|
| Cài teleop | install_pico.sh chạy xong, .venv_teleop import được xrobotoolkit_sdk |
| Camera | run_camera_viewer.py thấy frame trên port 5555, không tối, không lệch góc |
| Deployment | deploy.sh --input-type zmq_manager init xong, C++ publish robot_config |
| PICO | pico_manager_thread_server.py --manager thấy body data, combo mode hoạt động |
| Prompt | --task-prompt mô tả đúng task, không dùng "demo" cho dữ liệu thật |
| Recording | Left Grip + A start/stop, Left Grip + B discard khi fail |
| Post-process | Chạy process_dataset.py, chú ý --no-remove-stale-smpl với VR_3PT |
Với beginner, lỗi phổ biến nhất không phải model kém mà là dataset lệch đồng bộ: camera ghi từ máy khác, PICO publish được nhưng C++ không chạy zmq_manager, exporter thiếu robot_config, hoặc task prompt dùng sai cho nhiều task khác nhau. Hãy coi mỗi episode như một sample supervised learning: input, action và annotation phải cùng kể một câu chuyện.
Kết luận
Bài 5 biến SONIC từ một controller thành một pipeline dữ liệu VLA hoàn chỉnh. install_pico.sh dựng môi trường teleop; pico_manager_thread_server.py biến PICO thành manager nhiều mode; launch_data_collection.py ghép C++ deploy, PICO, exporter và camera viewer trong tmux; run_data_exporter.py đồng bộ camera, robot state và teleop signal; process_dataset.py dọn dataset trước fine-tune.
Nếu nhớ một câu, hãy nhớ câu này: --input-type zmq_manager là cổng vào cho PICO manager, còn LeRobot v2.1 là hợp đồng đầu ra cho VLA. Khi cả hai đầu hợp đồng đúng, bạn có thể thu dữ liệu thao tác toàn thân để VLA học "làm gì", trong khi SONIC tiếp tục chịu trách nhiệm "di chuyển cơ thể như thế nào".
Trong bài cuối, chúng ta sẽ rời pipeline teleop-data và nhìn sang MotionBricks: lớp latent generative motion bổ sung cho hệ sinh thái SONIC.



