Summary
This issue documents the end-to-end process for running the Unitree G1 EDU (23-DOF variant) with Brainco Revo2 five-finger dextrous hands under the Decision-Making Mode inference pipeline. The G1 23-DOF robot has no wrist pitch/yaw joints and uses forearm roll only. The Brainco Revo2 hands connect via /dev/ttyUSB1 (left) and /dev/ttyUSB2 (right), driven by a ROS2 stark_node.
Why a new robot type is needed
The existing g1_dex1 config assumes Dex_1 parallel grippers controlled via DDS. Brainco hands use Modbus RTU over RS-485, exposed through a ROS2 node (stark_node) in a Python 3.8 / ROS2 Foxy environment. Since unitree_deploy requires Python 3.10, direct import is impossible — a TCP bridge bridges the two environments.
New g1_brainco robot type
Action space: 14 arm DOFs (4 phantom wrist slots retained for SDK compatibility) + 12 hand DOFs = 26 total
- Hand DOF range: 0.0 = open, 1.0 = closed
- Finger order (both hands): thumb, thumb_aux, index, middle, ring, pinky
New files:
unitree_deploy/scripts/brainco_bridge.py — TCP bridge: subscribes to motor_status{,_r} (MotorStatus, uint8[6]), publishes to joint_commands_{left,right} (JointState), serves {"cmd":"get/set"} JSON on 127.0.0.1:9877
unitree_deploy/unitree_deploy/robot_devices/endeffector/brainco_hand.py — Brainco_DualHand_Controller: 12-DOF, connects to bridge via TCP
Modified files:
robot_devices/endeffector/configs.py — added BrancoDualHandConfig
robot_devices/endeffector/utils.py — added brainco_dual branch
robot/robot_configs.py — added G1_Brainco_RobotConfig (g1_brainco), brainco_dual_arm_default_factory, brainco_default_factory
robot/robot_utils.py — added g1_brainco to make_robot_config()
scripts/robot_client.py — added g1_brainco to INIT_POSE, ZERO_ACTION, CAM_KEY; inference server URL now reads from INFERENCE_SERVER_HOST / INFERENCE_SERVER_PORT env vars
scripts/evaluation/real_eval_server.py — server now binds to 0.0.0.0 by default (was 127.0.0.1); added --host / --port args
Startup sequence (three terminals on robot)
Terminal 1 — stark_node:
conda activate g1brainco
source ~/unitree_ros2/setup.sh
source ~/unitree-g1-brainco-hand/ros2_stark_ws/install/setup.bash
ros2 launch stark_bringup brainco_launch.py
Terminal 2 — TCP bridge:
conda activate g1brainco
source ~/unitree_ros2/setup.sh
source ~/unitree-g1-brainco-hand/ros2_stark_ws/install/setup.bash
python ~/unifolm-world-model-action/unitree_deploy/scripts/brainco_bridge.py
Terminal 3 — robot client:
conda activate unitree_deploy
cd ~/unifolm-world-model-action/unitree_deploy
export LD_PRELOAD=/home/unitree/miniconda3/envs/unitree_deploy/lib/libgomp.so.1
export INFERENCE_SERVER_HOST=<inference-server-ip>
python scripts/robot_client.py --robot_type g1_brainco --language_instruction "your task"
Fixes applied
| Issue |
Fix |
requires-python == 3.10.18 but robot has 3.10.12 |
Change to >=3.10 in pyproject.toml on robot |
pinocchio cannot allocate memory in static TLS block (aarch64) |
export LD_PRELOAD=.../libgomp.so.1 |
Robot type 'g1_brainco' is not available |
Add g1_brainco branch to make_robot_config() |
Server binds only to 127.0.0.1, robot cannot reach it |
Default to 0.0.0.0 in real_eval_server.py |
Robot client hardcodes HOST=127.0.0.1 |
Read INFERENCE_SERVER_HOST env var |
| Arms jerk on startup (INIT_POSE was zeros) |
Set INIT_POSE['g1_brainco'] to Ready Mode positions |
| Arms whirring (two competing init_pose sources) |
Align brainco_dual_arm_default_factory init_pose with INIT_POSE |
| Arms whirring (control loop resets to zeros) |
Init G1_29_ArmController.q_target = np.array(self.init_pose) instead of np.zeros(14) |
Known remaining issues
- Tensor size mismatch (26 vs 12): The inference server's normalizer raises
RuntimeError: The size of tensor a (26) must match the size of tensor b (12) when using a model checkpoint trained on a 12-DOF dataset. A g1_brainco 26-DOF trained checkpoint is needed.
- Minor forearm roll correction: The static
init_pose uses 0.0 for forearm roll, but the actual resting value may be slightly different (~0.035 left / ~-0.115 right), causing small ongoing corrections. A future fix would read the actual post-go_start() position and use that as q_target.
Reference implementation
Full implementation is available in the fork branch:
brownwa/unifolm-world-model-action → docs/brainco-setup-guide
Documentation is in unitree_deploy/README.md under section 2.1 Run G1 with Brainco Revo2 Five-Finger Hands.
Summary
This issue documents the end-to-end process for running the Unitree G1 EDU (23-DOF variant) with Brainco Revo2 five-finger dextrous hands under the Decision-Making Mode inference pipeline. The G1 23-DOF robot has no wrist pitch/yaw joints and uses forearm roll only. The Brainco Revo2 hands connect via
/dev/ttyUSB1(left) and/dev/ttyUSB2(right), driven by a ROS2stark_node.Why a new robot type is needed
The existing
g1_dex1config assumes Dex_1 parallel grippers controlled via DDS. Brainco hands use Modbus RTU over RS-485, exposed through a ROS2 node (stark_node) in a Python 3.8 / ROS2 Foxy environment. Sinceunitree_deployrequires Python 3.10, direct import is impossible — a TCP bridge bridges the two environments.New
g1_braincorobot typeAction space: 14 arm DOFs (4 phantom wrist slots retained for SDK compatibility) + 12 hand DOFs = 26 total
New files:
unitree_deploy/scripts/brainco_bridge.py— TCP bridge: subscribes tomotor_status{,_r}(MotorStatus, uint8[6]), publishes tojoint_commands_{left,right}(JointState), serves{"cmd":"get/set"}JSON on127.0.0.1:9877unitree_deploy/unitree_deploy/robot_devices/endeffector/brainco_hand.py—Brainco_DualHand_Controller: 12-DOF, connects to bridge via TCPModified files:
robot_devices/endeffector/configs.py— addedBrancoDualHandConfigrobot_devices/endeffector/utils.py— addedbrainco_dualbranchrobot/robot_configs.py— addedG1_Brainco_RobotConfig(g1_brainco),brainco_dual_arm_default_factory,brainco_default_factoryrobot/robot_utils.py— addedg1_braincotomake_robot_config()scripts/robot_client.py— addedg1_braincotoINIT_POSE,ZERO_ACTION,CAM_KEY; inference server URL now reads fromINFERENCE_SERVER_HOST/INFERENCE_SERVER_PORTenv varsscripts/evaluation/real_eval_server.py— server now binds to0.0.0.0by default (was127.0.0.1); added--host/--portargsStartup sequence (three terminals on robot)
Terminal 1 — stark_node:
Terminal 2 — TCP bridge:
Terminal 3 — robot client:
Fixes applied
requires-python == 3.10.18but robot has 3.10.12>=3.10inpyproject.tomlon robotcannot allocate memory in static TLS block(aarch64)export LD_PRELOAD=.../libgomp.so.1Robot type 'g1_brainco' is not availableg1_braincobranch tomake_robot_config()127.0.0.1, robot cannot reach it0.0.0.0inreal_eval_server.pyHOST=127.0.0.1INFERENCE_SERVER_HOSTenv varINIT_POSE['g1_brainco']to Ready Mode positionsbrainco_dual_arm_default_factoryinit_pose withINIT_POSEG1_29_ArmController.q_target = np.array(self.init_pose)instead ofnp.zeros(14)Known remaining issues
RuntimeError: The size of tensor a (26) must match the size of tensor b (12)when using a model checkpoint trained on a 12-DOF dataset. Ag1_brainco26-DOF trained checkpoint is needed.init_poseuses0.0for forearm roll, but the actual resting value may be slightly different (~0.035left /~-0.115right), causing small ongoing corrections. A future fix would read the actual post-go_start()position and use that asq_target.Reference implementation
Full implementation is available in the fork branch:
brownwa/unifolm-world-model-action→docs/brainco-setup-guideDocumentation is in
unitree_deploy/README.mdunder section 2.1 Run G1 with Brainco Revo2 Five-Finger Hands.