VLA · Phase 10 · Humanoid VLA Systems
10.7 — Integrating a VLA with a Unitree G1 Humanoid
An integration case study on the Unitree G1.
This lesson assembles the course into a plan for putting a VLA on a G1: decide what the policy may control, map joints by name, check readiness every run and roll out in gated stages. The robot-specific interface is for you to take from Unitree’s documentation.
Claims about papers, models and textbooks carry numbered references, listed with what each one is cited for in the References. Code marked “Toy model” is our own illustration, not the cited system.
Learning objectives
After this lesson, you should be able to:
- Scope the policy to a safe subset of joints.
- Build a command that holds everything not commanded.
- Write a go/no-go readiness check.
- Plan a staged rollout with explicit gates.
- Decide which joints the policy commands
- Map policy slots to joints by name
- Run the readiness checks
- Roll out: simulation, supported, slow, normal
- Return to an earlier stage when a gate fails
The one-sentence idea
Give the policy the least authority that does the job, and prove each stage.
Hold by default; gate every stage.
A case study built from this course
This lesson walks through integrating a VLA with a Unitree G1 humanoid, using only what the earlier lessons established. A caution first: I could not reach Unitree's own SDK repositories while preparing this course, so I do not describe its message formats or function names. What follows is a plan and the code around it; you must fill in the robot-specific interface from Unitree's documentation for your unit and software version.
What is known from listings and distributors: the G1's degrees of freedom depend on the configuration. A base configuration of 23 DoF is listed (two 6-DoF legs, two 5-DoF arms and a 1-DoF waist); other variants are listed at 29, 41 or 43 DoF, the latter with dexterous hands; confirm the count for your unit.[1] Development (EDU) versions are described as providing the Unitree SDK2 with C++ and Python interfaces and ROS 2 over Cyclone DDS, and third-party ROS 2 bridges exist.[1]
Step 1: decide what the policy controls
Following lessons 10.4 to 10.6, the safest first design lets the policy command only the arms (and hands, if fitted) and leaves balance and walking to the robot's own lower-body control. The policy's action layout then has fewer numbers, and the adapter (lesson 9.2) fills the rest with “hold” values. Joints are addressed by name in your own configuration so that the policy's slots can't silently map to the wrong motor:
joints = ["left_hip", "left_knee", "right_hip", "right_knee", "waist",
"left_shoulder", "left_elbow", "right_shoulder", "right_elbow"] # a simplified list, not the real G1 joint table
policy_slots = {"left_shoulder": 0, "left_elbow": 1, "right_shoulder": 2, "right_elbow": 3}
current = {j: 0.0 for j in joints}
current.update({"left_shoulder": 0.2, "left_elbow": 0.9, "right_shoulder": -0.1, "right_elbow": 0.8})
policy_action = [0.25, 0.85, -0.05, 0.75] # target angles from the adapter, in policy slot order
command = dict(current) # start from "hold everything where it is"
for joint, slot in policy_slots.items():
command[joint] = policy_action[slot]
changed = [j for j in joints if command[j] != current[j]]
print("joints the policy changes:", changed)
print("joints held:", [j for j in joints if j not in changed])Output:
joints the policy changes: ['left_shoulder', 'left_elbow', 'right_shoulder', 'right_elbow'] joints held: ['left_hip', 'left_knee', 'right_hip', 'right_knee', 'waist']
Toy model. The joint list is invented and far shorter than a real G1’s. In practice the names and order come from the robot’s documentation or its URDF description, and are verified, not assumed.
The principle: start from “hold current”, then overwrite only what the policy is allowed to command. A bug in the policy can then only move the joints you let it move.
Step 2: a go/no-go checklist before every run
Turn the safety lessons into a script that must say “go” before the policy is allowed to move anything. Each line corresponds to an earlier lesson:
checks = {
"camera frames arriving and fresh (< 0.2 s old)": (0.08, lambda v: v < 0.2),
"joint states arriving (< 0.1 s old)": (0.02, lambda v: v < 0.1),
"calibration file newer than last camera bump": (True, lambda v: v is True),
"action definition matches the checkpoint (ee_delta_v1)": ("ee_delta_v1", lambda v: v == "ee_delta_v1"),
"emergency stop released and tested today": (True, lambda v: v is True),
"policy heartbeat": (0.15, lambda v: v < 0.5),
"battery above 30 percent": (22, lambda v: v >= 30),
}
failed = [name for name, (value, ok) in checks.items() if not ok(value)]
for name in checks: print(("FAIL " if name in failed else "ok ") + name)
print("decision:", "NO-GO" if failed else "GO")Output:
ok camera frames arriving and fresh (< 0.2 s old) ok joint states arriving (< 0.1 s old) ok calibration file newer than last camera bump ok action definition matches the checkpoint (ee_delta_v1) ok emergency stop released and tested today ok policy heartbeat FAIL battery above 30 percent decision: NO-GO
Here the battery check fails, so the answer is no-go. This is boring on purpose: it catches the stale camera, the wrong checkpoint, the stopped heartbeat that lessons 9.1 to 9.7 warned about, before a humanoid is standing in front of a person.
Step 3: roll out in stages with gates
Never go from “it loaded” to “it works in the room”. Use stages, each with an exit criterion decided in advance, and go back a stage when a criterion fails. A small gate model:
stages = [
("simulation", "success rate >= 0.9 over 100 seeded scenes"),
("robot supported, no motion", "commands logged for 10 minutes, none rejected by limits"),
("slow motion, arms only", "no safety monitor trips in 20 trials at 25% speed"),
("normal speed, arms only", "success rate >= 0.8 over 50 trials (lesson 8.2)"),
]
results = {"simulation": True, "robot supported, no motion": True, "slow motion, arms only": False, "normal speed, arms only": None}
current = None
for name, gate in stages:
outcome = results[name]
if outcome:
print(f"PASS {name:<28} gate: {gate}")
else:
current = name
print(f"STOP {name:<28} gate not met: {gate}")
break
print("stay at or return to:", current)Output:
PASS simulation gate: success rate >= 0.9 over 100 seeded scenes PASS robot supported, no motion gate: commands logged for 10 minutes, none rejected by limits STOP slow motion, arms only gate not met: no safety monitor trips in 20 trials at 25% speed stay at or return to: slow motion, arms only
The third gate failed, so the work stops there: fix, retest, and only then move on. Each stage costs little compared with an accident.
Where the hard parts are
Most of the work in a real integration is not the model: it is calibration (lesson 9.3), the action adapter and its tests (9.2), timing (8.6, 9.1), limits and collisions (9.4), and the safety layer and fallbacks (9.7). A humanoid adds balance (10.5, 10.6). If a step in this plan seems tedious, it is because it stands in for an accident you do not have to have.
Common misconceptions
Misconception 1 “The G1 has one DoF count.”
Correction It depends on the configuration (23, 29, 41, 43 are listed).
Misconception 2 “Let the policy command everything.”
Correction Start with the arms and hold the rest.
Misconception 3 “Passing in simulation is enough to go to the robot.”
Correction Each stage has its own gate.
Engineering takeaways
- Check your unit’s configuration.
- Command a subset; hold the rest.
- Go/no-go before every run.
- Gated stages.
Least authority, most checks.
Knowledge check
Try each question first, then reveal the explanation.
Question 1
Why start the command from “hold current” values?
Explanation
A policy bug can then only move the joints you allow it to.
Question 2
A gate fails at the slow-motion stage. What next?
Explanation
Stay at or return to that stage, fix and retest.
Question 3
Where must the robot-specific message formats come from?
Explanation
Unitree’s documentation for your unit and software version.
References
Numbered notes in the text, like [1], point here. Each entry says what it is cited for and how that was checked. Papers and books can be revised or misread, so follow the links before relying on a detail. Code marked “Toy model” is our own illustration of an idea, not the code of the cited system.
(n.d.). Unitree G1 humanoid: specifications and software interfaces. www.unitree.com
Cited for:
- The number of degrees of freedom depends on the configuration: a base configuration with 23 DoF is listed (two 6-DoF legs, two 5-DoF arms and a 1-DoF waist), higher configurations are listed at 29, 41 or 43 DoF (with dexterous hands), and the count for a given unit should be confirmed against its configuration. Checked against the publication record.
- Development (EDU) versions are described as providing the Unitree SDK2 with C++ and Python interfaces and ROS 2 over Cyclone DDS; third-party ROS 2 bridges exist. Checked against the publication record.
What’s next
That completes the humanoid systems. The last phase looks at advanced research directions.