VnRobo
Về chúng tôiBảng giáBlogLiên hệ
🇺🇸ENĐăng nhậpDùng thử miễn phí
🇺🇸EN
VnRobo logo

Hạ tầng AI cho robot công nghiệp thế hệ mới.

Sản phẩm

  • Tính năng
  • Bảng giá
  • Kiến thức
  • Dịch vụ

Công ty

  • Về chúng tôi
  • Blog
  • Liên hệ

Pháp lý

  • Chính sách bảo mật
  • Điều khoản sử dụng

© 2026 VnRobo. Bảo lưu mọi quyền.

Được tạo với♥tại Việt Nam
VnRobo
Về chúng tôiBảng giáBlogLiên hệ
🇺🇸ENĐăng nhậpDùng thử miễn phí
🇺🇸EN
  1. Trang chủ
  2. Blog
  3. Dựng G1 MuJoCo qua DDS low-level
simulationunitree-g1mujocoddssdk2simulationhumanoidtelemetry

Dựng G1 MuJoCo qua DDS low-level

Bring-up Unitree G1 trong MuJoCo bằng SDK2 DDS: robot, scene, domain, interface, joystick và các topic low-level thật.

Nguyễn Anh Tuấn13 tháng 6, 202614 phút đọc
Dựng G1 MuJoCo qua DDS low-level

Vì sao bài này bắt đầu từ DDS low-level?

Khi mới học mô phỏng humanoid, rất dễ bắt đầu bằng một ví dụ ROS chung chung: load URDF, publish /joint_states, subscribe một topic command, rồi xem robot nhúc nhích trong viewer. Cách đó tốt để học middleware, nhưng chưa đủ nếu mục tiêu của bạn là viết controller có thể đi từ sim sang Unitree G1 thật. Unitree G1 không chỉ là một model MJCF có nhiều khớp. Nó có stack giao tiếp riêng dựa trên Unitree SDK2 DDS, với các message như LowCmd, LowState, SportModeState và, riêng G1, IMUState trên topic rt/secondary_imu.

Bài 1 trong series này dựng nền tảng đó. Chúng ta sẽ bám vào repo chính thức unitreerobotics/unitree_mujoco, đặc biệt là readme.md, simulate/config.yaml, simulate_python/config.py, simulate/src/main.cc và simulate_python/unitree_mujoco.py. Mục tiêu không phải là viết controller đi bộ ngay. Mục tiêu là hiểu simulator được nối vào DDS như thế nào, chọn đúng robot, robot_scene, domain_id, interface, bật/tắt use_joystick, và biết topic nào là dữ liệu thật để các bài sau ghi MCAP, mở Foxglove, plot tín hiệu và thiết kế PD/WBID.

Nếu bạn mới hoàn toàn với MuJoCo, nên đọc trước Bắt đầu với MuJoCo. Nếu bạn quan tâm đường dài sim-to-real cho G1, bài GR00T-VisualSim2Real cho G1 cho bức tranh rộng hơn về training policy và deploy lên humanoid.

Sơ đồ chức năng Unitree MuJoCo nối MuJoCo simulator với SDK2, ROS2 và Python DDS -- nguồn: unitreerobotics/unitree_mujoco
Sơ đồ chức năng Unitree MuJoCo nối MuJoCo simulator với SDK2, ROS2 và Python DDS -- nguồn: unitreerobotics/unitree_mujoco

Roadmap series

Series G1 MuJoCo: điều khiển, Foxglove và PlotJuggler gồm 6 bài, đi từ bring-up simulator đến controller và sim-to-real:

Phần Bài Nội dung chính
1 Dựng G1 MuJoCo qua DDS low-level Cấu hình Unitree MuJoCo, DDS domain/interface, joystick và topic/message thật
2 Ghi MCAP và mở trong Foxglove Ghi LowState, SportModeState, IMU và command thành log có thể replay
3 PlotJuggler cho tín hiệu G1 Vẽ joint position, velocity, torque, IMU, command tracking và delay
4 WBID và PD cho G1 trong MuJoCo Thiết kế vòng điều khiển low-level, gain, saturation và kiểm tra ổn định
5 Upper-body IK cho G1 Điều khiển tay/thân trên, mapping joint index và kiểm tra command an toàn
6 Checklist sim-to-real Những điểm phải khớp giữa sim và robot thật: domain, network, gain, timing, safety

Bạn có thể xem bài này như "đường ống nước" của toàn series. Nếu DDS topic sai, domain sai hoặc scene không đúng G1, mọi dashboard đẹp ở các bài sau đều chỉ là dữ liệu sai được vẽ đẹp.

Unitree MuJoCo khác gì một simulator ROS thông thường?

README của repo mô tả unitree_mujoco là simulator phát triển dựa trên Unitree SDK2 và MuJoCo. Nó không chỉ expose một API Python để đọc qpos, qvel. Nó dựng một bridge để chương trình viết bằng unitree_sdk2, unitree_ros2 hoặc unitree_sdk2_python có thể nói chuyện với simulator theo cùng phong cách như nói chuyện với robot thật. Repo có hai entry point:

Thư mục Vai trò Khi nào dùng
simulate Simulator C++ dựa trên unitree_sdk2 và MuJoCo Nên dùng khi muốn sát stack SDK2, hiệu năng tốt và topic G1 đầy đủ
simulate_python Simulator Python dựa trên unitree_sdk2_python và package mujoco Nên dùng để đọc code nhanh, thử nghiệm, debug và teaching
unitree_robots MJCF robot và scene được simulator load Chọn g1/scene.xml, g1/scene_23dof.xml hoặc g1/scene_29dof.xml
example Ví dụ điều khiển từ C++, Python và ROS2 Dùng để hiểu pattern sim-to-real của Unitree

Điểm quan trọng nhất: phiên bản hiện tại của README nói simulator chủ yếu hỗ trợ low-level development, dùng để kiểm chứng controller trước khi đi sang robot thật. Điều đó có nghĩa là bạn nên tư duy theo LowCmd và LowState, không phải theo một API high-level giả lập kiểu "go to x,y,z". Với G1, README cũng ghi rõ G1/H1-2 dùng unitree_hg IDL cho low-level communication, trong khi Go2/B2/H1/B2w/Go2w dùng unitree_go IDL. Nếu bạn copy test program của Go2 và chạy thẳng cho G1, bạn có thể load được scene nhưng message type không khớp.

Cấu hình C++: simulate/config.yaml

File C++ config hiện có các trường tối thiểu sau:

robot: "go2"  # Robot name, "go2", "b2", "b2w", "h1", "go2w", "g1", "h2"
robot_scene: "scene.xml" # Robot scene, /unitree_robots/[robot]/scene.xml

domain_id: 1
interface: "lo"

use_joystick: 0
joystick_type: "xbox"
joystick_device: "/dev/input/js0"
joystick_bits: 16

print_scene_information: 1
enable_elastic_band: 0

Để dựng G1 trên máy local, cấu hình beginner nên bắt đầu như sau:

robot: "g1"
robot_scene: "scene.xml"
domain_id: 1
interface: "lo"

use_joystick: 0
joystick_type: "xbox"
joystick_device: "/dev/input/js0"
joystick_bits: 16

print_scene_information: 1
enable_elastic_band: 0

robot quyết định thư mục dưới unitree_robots. Nếu đặt robot: "g1", simulator sẽ tìm scene trong unitree_robots/g1/. robot_scene là file scene trong thư mục đó. Với G1, repo có scene.xml, scene_23dof.xml và scene_29dof.xml. scene.xml hiện include model g1_29dof.xml, thêm ground plane, height field, cầu thang và vật cản. Nếu bạn muốn thử mapping 23DOF, dùng scene_23dof.xml. Nếu muốn full 29DOF như nhiều bài upper-body/control, dùng scene_29dof.xml hoặc scene mặc định.

domain_id là DDS domain. README khuyến nghị phân biệt simulator với robot thật, vì robot thật thường dùng domain mặc định 0. Đây là quy tắc an toàn rất quan trọng. Khi làm local simulation, dùng domain_id: 1 giúp giảm khả năng controller thử nghiệm vô tình nói chuyện với robot thật trên cùng mạng. Khi deploy thật, bạn mới chuyển domain/interface theo network thật và theo checklist safety.

interface là network interface mà DDS dùng. Trong simulation local, đặt "lo" để dùng loopback. Controller chạy trong terminal khác cũng phải khởi tạo SDK2 với cùng domain và interface, ví dụ C++ dùng ChannelFactory::Instance()->Init(1, "lo"), Python dùng ChannelFactoryInitialize(1, "lo"). Nếu simulator dùng domain_id: 1, interface: "lo" nhưng script điều khiển dùng domain 0 hoặc interface eth0, hai tiến trình sẽ không thấy nhau.

use_joystick điều khiển việc simulator mô phỏng Unitree wireless controller bằng tay cầm Xbox hoặc Switch. Nếu không cắm tay cầm, đặt 0. README nói rõ khi không có gamepad thì use_joystick hoặc USE_JOYSTICK cần đặt 0. Đây là lỗi beginner gặp rất nhiều: simulator khởi động rồi treo hoặc báo lỗi joystick vì cấu hình mặc định trong một số file Python bật joystick.

Cấu hình Python: simulate_python/config.py

File Python tương đương:

ROBOT = "go2"
ROBOT_SCENE = "../unitree_robots/" + ROBOT + "/scene.xml"
DOMAIN_ID = 1
INTERFACE = "lo"

USE_JOYSTICK = 1
JOYSTICK_TYPE = "xbox"
JOYSTICK_DEVICE = 0

PRINT_SCENE_INFORMATION = True
ENABLE_ELASTIC_BAND = False

SIMULATE_DT = 0.005
VIEWER_DT = 0.02

Cho G1 beginner, sửa thành:

ROBOT = "g1"
ROBOT_SCENE = "../unitree_robots/" + ROBOT + "/scene.xml"
DOMAIN_ID = 1
INTERFACE = "lo"

USE_JOYSTICK = 0
JOYSTICK_TYPE = "xbox"
JOYSTICK_DEVICE = 0

PRINT_SCENE_INFORMATION = True
ENABLE_ELASTIC_BAND = False

SIMULATE_DT = 0.005
VIEWER_DT = 0.02

SIMULATE_DT là timestep MuJoCo. Trong source Python, mj_model.opt.timestep = config.SIMULATE_DT, sau đó thread physics gọi mujoco.mj_step(mj_model, mj_data) đều đặn. Comment trong config nhắc rằng timestep cần lớn hơn thời gian chạy một lần viewer.sync() nếu bạn không muốn viewer làm nghẽn mô phỏng. VIEWER_DT = 0.02 nghĩa là viewer sync khoảng 50 FPS. Khi mới học, đừng giảm timestep quá mạnh chỉ vì muốn "500 Hz đẹp hơn". Nếu máy không kịp render hoặc Python thread bị giật, telemetry bạn ghi ở bài 2 và bài 3 sẽ phản ánh timing xấu.

PRINT_SCENE_INFORMATION = True rất nên bật ở lần chạy đầu. Bridge sẽ in link, joint, actuator và sensor index. Với G1, danh sách này giúp bạn nối từ tên joint trong MJCF sang index trong LowCmd.motor_cmd và LowState.motor_state. Repo cũng có unitree_robots/g1/g1_joint_index_dds.md, liệt kê thứ tự motor cho bản 23DOF và 29DOF. Ví dụ bản 29DOF bắt đầu từ L_LEG_HIP_PITCH, L_LEG_HIP_ROLL, L_LEG_HIP_YAW, L_LEG_KNEE, rồi đến ankle, chân phải, waist và hai tay. Đừng hardcode index theo trí nhớ nếu bạn đổi scene.

Entry point C++ làm gì?

Trong simulate/src/main.cc, chương trình đọc config, nhận override command line, resolve scene tương đối thành đường dẫn dưới unitree_robots/<robot>/<scene>, rồi chạy hai luồng chính: physics/viewer và Unitree SDK2 bridge. Hàm helper trong param.h cho phép override nhanh:

./unitree_mujoco -r g1 -s scene.xml -i 1 -n lo

Các flag có ý nghĩa:

Flag Tương đương config Ví dụ
-r robot -r g1
-s robot_scene -s scene.xml
-i domain_id -i 1
-n interface -n lo

Điểm đáng chú ý trong main.cc là thread bridge gọi:

unitree::robot::ChannelFactory::Instance()->Init(
  param::config.domain_id,
  param::config.interface
);

Sau đó source chọn bridge theo robot. Nếu robot == "g1", nó tạo G1Bridge; nếu không, ví dụ Go2, nó tạo bridge khác. Đây là lý do robot: "g1" không chỉ ảnh hưởng tới mesh hoặc scene. Nó còn quyết định message wrapper và topic phụ như secondary IMU.

Thread physics gọi mj_step(m, d) để tiến mô phỏng. Bridge đọc sensor từ mj_data_->sensordata, publish state ra DDS, đồng thời nhận LowCmd từ DDS rồi ghi vào mj_data_->ctrl. Công thức low-level trong bridge có dạng:

ctrl[i] = tau
        + kp * (q_des - q_measured)
        + kd * (dq_des - dq_measured)

Vì vậy LowCmd không chỉ là torque thuần. Bạn có thể gửi feedforward torque tau, target position q, target velocity dq, và gain kp, kd. Bài 4 sẽ đi sâu vào chuyện chọn gain và saturation. Ở bài này, bạn chỉ cần nhớ: simulator đang thực thi command theo semantics low-level của Unitree SDK2, không phải topic ROS tự chế.

Entry point Python làm gì?

simulate_python/unitree_mujoco.py giúp beginner đọc luồng dễ hơn. File này load model bằng:

mj_model = mujoco.MjModel.from_xml_path(config.ROBOT_SCENE)
mj_data = mujoco.MjData(mj_model)

Sau đó launch viewer, đặt timestep, rồi tạo hai thread:

viewer_thread = Thread(target=PhysicsViewerThread)
sim_thread = Thread(target=SimulationThread)

Trong SimulationThread, code gọi:

ChannelFactoryInitialize(config.DOMAIN_ID, config.INTERFACE)
unitree = UnitreeSdk2Bridge(mj_model, mj_data)

Nếu USE_JOYSTICK bật, bridge setup joystick. Nếu PRINT_SCENE_INFORMATION bật, bridge in scene information. Vòng lặp chính lock dữ liệu, gọi mujoco.mj_step, rồi sleep phần thời gian còn lại để bám timestep. Viewer thread lock cùng mutex, gọi viewer.sync(), rồi sleep theo VIEWER_DT. Đây là thiết kế tối giản nhưng đủ để hiểu timing: physics và viewer không phải cùng một việc. Khi telemetry bị giật, hãy kiểm tra cả timestep physics lẫn viewer sync.

Một ghi chú quan trọng về phiên bản: README liệt kê IMUState tại rt/secondary_imu cho G1. Trong source C++ hiện tại, G1Bridge tạo IMUState_t("rt/secondary_imu") và publish quaternion, RPY, gyroscope, accelerometer từ sensor secondary_imu_*. Trong source Python bridge mà bài này kiểm tra, các topic low/high/wireless thể hiện rõ hơn, còn secondary IMU có thể phụ thuộc phiên bản branch. Nếu bài sau bạn cần rt/secondary_imu đầy đủ để ghi MCAP, hãy ưu tiên C++ simulator hoặc kiểm tra Python bridge của checkout đang dùng.

Những topic và message thật cần nhớ

README chính thức liệt kê các Unitree SDK2 message simulator hỗ trợ:

Message Topic thường dùng Ý nghĩa
LowCmd rt/lowcmd Lệnh điều khiển motor: q, dq, tau, kp, kd, mode
LowState rt/lowstate Trạng thái motor: position, velocity, estimated torque, IMU thân
SportModeState rt/sportmodestate Vị trí và vận tốc robot, hữu ích để phân tích controller trong sim
IMUState rt/secondary_imu IMU phụ cho G1, gồm quaternion/RPY/gyro/accelerometer
WirelessController rt/wirelesscontroller Trạng thái tay cầm mô phỏng khi bật joystick

Có hai điểm dễ nhầm. Thứ nhất, SportModeState trong robot thật có thể không đọc được sau khi tắt built-in motion control service, nhưng simulator vẫn giữ message này để bạn phân tích vị trí/vận tốc controller. Vì vậy dùng SportModeState trong dashboard là hợp lý cho debugging sim, nhưng đừng xây controller thật phụ thuộc tuyệt đối vào nó nếu mode robot thật không cung cấp.

Thứ hai, G1 dùng unitree_hg IDL cho low-level. Nếu bạn viết Python subscriber/publisher, import phải đúng family message. Repo Python bridge tự chọn unitree_hg khi số motor lớn hơn ngưỡng Go IDL, nhưng test script tự viết của bạn vẫn có thể sai. Triệu chứng thường là topic có vẻ đúng tên nhưng callback không chạy, hoặc DDS discovery thấy participant mà deserialize không được.

Bring-up từng bước cho beginner

Luồng thao tác nên đi chậm và kiểm tra sau mỗi bước:

  1. Cài dependency theo README: unitree_sdk2 cho C++, unitree_sdk2_python cho Python, MuJoCo và joystick package nếu cần. Nếu Python báo không tìm thấy CycloneDDS, làm theo hướng dẫn của unitree_sdk2_python để đặt CYCLONEDDS_HOME hoặc CMAKE_PREFIX_PATH.
  2. Chọn một simulator trước. Nếu mục tiêu là học source nhanh, chạy simulate_python. Nếu mục tiêu là topic G1 sát SDK2 nhất, build và chạy simulate.
  3. Sửa config sang robot: "g1", robot_scene: "scene.xml", domain_id: 1, interface: "lo", use_joystick: 0.
  4. Chạy simulator và xác nhận viewer mở G1. Nếu bật print_scene_information, lưu ý số actuator và sensor được in ra.
  5. Mở terminal thứ hai, chạy một subscriber hoặc test program với đúng domain/interface. Đừng dùng domain 0 nếu simulator đang ở 1.
  6. Chỉ sau khi đọc được LowState, hãy thử publish LowCmd nhỏ, ví dụ gain thấp hoặc torque kiểm tra từng joint trong môi trường an toàn.

Sơ đồ tối giản:

Terminal A
  unitree_mujoco / unitree_mujoco.py
  DOMAIN_ID=1, INTERFACE=lo
        |
        | DDS topics
        v
Terminal B
  logger / controller / Foxglove bridge
  DOMAIN_ID=1, INTERFACE=lo

rt/lowcmd          controller -> simulator
rt/lowstate        simulator -> logger/controller
rt/sportmodestate  simulator -> logger
rt/secondary_imu   G1 bridge  -> logger/controller

Nếu không thấy dữ liệu, checklist đầu tiên là: cùng domain chưa, cùng interface chưa, message IDL đúng chưa, joystick có đang bị bật khi không cắm tay cầm không, scene có đúng G1 không, và controller có publish vào rt/lowcmd chứ không phải topic ROS tự đặt tên không.

Chọn robot_scene cho bài sau

unitree_robots/g1/scene.xml hiện include g1_29dof.xml và thêm nhiều terrain: ground plane, obstacle, stairs, rough boxes và height field. Điều này tốt cho thử nghiệm locomotion và telemetry vì SportModeState, IMU và joint load thay đổi rõ khi robot tương tác với mặt đất không phẳng. Nhưng khi bạn mới kiểm tra PD joint-level, scene phức tạp có thể làm khó debug. Một workflow hợp lý:

Mục tiêu Scene gợi ý
Kiểm tra DDS, topic, subscriber scene.xml
Kiểm tra joint index 23DOF scene_23dof.xml
Làm upper-body 29DOF scene_29dof.xml hoặc scene.xml
Plot tín hiệu trên terrain scene.xml

Height-field texture được scene G1 dùng cho terrain -- nguồn: unitreerobotics/unitree_mujoco
Height-field texture được scene G1 dùng cho terrain -- nguồn: unitreerobotics/unitree_mujoco

Với series này, mặc định chúng ta dùng g1 và scene.xml để có full telemetry. Khi bài 5 đi vào upper-body IK, ta sẽ quay lại joint index 29DOF và cách map command cho vai, khuỷu, cổ tay.

Kết luận

Điều quan trọng nhất của bài 1 là thay đổi cách nhìn: G1 MuJoCo không phải là một robot 3D đứng trong viewer, mà là một endpoint DDS low-level giả lập robot thật. robot và robot_scene chọn đúng model; domain_id và interface quyết định các tiến trình có nhìn thấy nhau; use_joystick quyết định có publish wireless controller giả lập hay không; LowCmd, LowState, SportModeState và IMUState là các message bạn sẽ ghi, vẽ và điều khiển trong các bài sau.

Ở bài 2, chúng ta sẽ dùng chính các topic này để ghi MCAP và mở trong Foxglove. Đến bài 3, cùng dữ liệu đó sẽ được đưa vào PlotJuggler để kiểm tra tracking, delay và nhiễu. Khi nền DDS đã đúng, phần visualization và control phía sau sẽ đỡ mơ hồ hơn rất nhiều.

Bài viết liên quan

  • Ghi MCAP và mở trong Foxglove
  • PlotJuggler cho tín hiệu G1
  • WBID và PD cho G1 trong MuJoCo
NT

Nguyễn Anh Tuấn

Robotics & AI Engineer. Building VnRobo — sharing knowledge about robot learning, VLA models, and automation.

Khám phá VnRobo

Fleet MonitoringROS 2 IntegrationAMR Solutions
g1-mujoco-telemetry-control — Phần 1/6
Biến LAFAN1 G1 thành MCAP Foxglove →

Bài viết liên quan

NEWTutorial
Biến LAFAN1 G1 thành MCAP Foxglove
unitree-g1mujocofoxglovePhần 2
simulation

Biến LAFAN1 G1 thành MCAP Foxglove

Mổ xẻ script LAFAN1_MCAP_WORKING.py để đổi CSV pose G1 thành /tf MCAP và phát lại kinematics trong Foxglove.

13/6/202614 phút đọc
NT
NEWTutorial
Debug MPC/WBID G1 bằng PlotJuggler
unitree-g1mujocoplotjugglerPhần 3
locomotion

Debug MPC/WBID G1 bằng PlotJuggler

Dùng PlotJuggler để đọc SRBD, MPC, feet reference và wbid_solve_time trong stack G1 MuJoCo MPC/WBID.

13/6/202616 phút đọc
NT
NEWTutorial
Checklist sim2real cho controller G1
unitree-g1mujocosim2realPhần 6
humanoid

Checklist sim2real cho controller G1

Checklist chuyển controller G1 từ MuJoCo sang robot thật: DDS, ROS_DOMAIN_ID, OpenWBT, log .pkl, PlotJuggler và torque guard.

13/6/202615 phút đọc
NT
VnRobo logo

Hạ tầng AI cho robot công nghiệp thế hệ mới.

Sản phẩm

  • Tính năng
  • Bảng giá
  • Kiến thức
  • Dịch vụ

Công ty

  • Về chúng tôi
  • Blog
  • Liên hệ

Pháp lý

  • Chính sách bảo mật
  • Điều khoản sử dụng

© 2026 VnRobo. Bảo lưu mọi quyền.

Được tạo với♥tại Việt Nam