# H2 ROS 2 LowCmd bridge This C++17 package connects Unitree `unitree_hg` ROS 2 messages to the Python controller through H2BR shared memory. It receives ROS `/lowstate` (native DDS `rt/lowstate`), mirrors `/wbc_racket_command`, and publishes ROS `/lowcmd` (native DDS `rt/lowcmd`) at 1000 Hz. Read [setup](../docs/SETUP.md) and complete site-specific hardware commissioning before a live launch. The bridge executable is a hardware control component; compilation or a passing contract check does not authorize robot operation. ## Build and inspect on Linux From the repository root, with ROS 2 Humble, the installed `unitree_hg` workspace, and the selected network interface configured: ```bash export H2_IF=enp4s0f0 export UNITREE_ROS2_ROOT="$HOME/unitree_ros2" source scripts/source_unitree_env.sh "$H2_IF" colcon build --packages-select cpp_lowcmd_bridge \ --cmake-args -DCMAKE_BUILD_TYPE=Release source install/setup.bash ros2 run cpp_lowcmd_bridge h2_lowcmd_shm_bridge --print-contract ros2 run cpp_lowcmd_bridge h2_lowcmd_shm_bridge \ --validate-network-only --network-interface "$H2_IF" ``` The expected contract is: ```text H2BR magic=0x48324252 version=7 bytes=1544 state_dof=31 lowcmd_slots=35 mode_pr=0 joint_map=unitree_sdk2@e02dc701-h2-cpp cmd_q=raw_learned_desired ros_lowstate=/lowstate ros_lowcmd=/lowcmd publish_hz=1000 ``` Network validation checks the local RMW/interface/CycloneDDS configuration only. The native LowState inspector and physical identity check are separate commissioning gates. Build outputs belong in `build/`, `install/`, and `log/` and are not release source. ## Admission and shared memory Live startup requires `--network-interface`. The interface must be UP and match `CYCLONEDDS_URI` generated by the environment script. The bridge publishes no command until it has valid H2 LowState in PR mode, no discovered competing LowCmd publisher, and a finite Python H2BR command carrying the validated MotionSwitcher attestation bit. It never changes AB mode into PR mode. The 1544-byte H2BR v7 interface uses 31 state/command slots and the audited C++ motor map. Waist IDs 12/13/14 are yaw/roll/pitch; wrist IDs are roll/pitch/yaw. Policy ordering is different and is mapped by name in the Python H2 contract. Shared-memory locks protect coherent snapshots and allow only one Python command writer. The production shared-memory name is `h2_pingpong_bridge`. The simulator uses a separate name and ownership contract; it must not attach to production memory. Before a runner starts, the [no-runner validation script](../scripts/validate_bridge_no_runner.sh) checks healthy/disarmed state, LowState update rate, exclusive ROS endpoint ownership, and absence of actual LowCmd samples. ## Command and stop semantics `cmd_q` contains the raw learned desired joint target. Immediately before each 1000 Hz publication, the bridge projects that target into the PD-effort interval computed from the latest valid joint position/velocity and actuator effort limits. This allows an out-of-range desired target to request saturated effort without first rewriting it to a mechanical angle boundary. Measured state and captured stop-hold poses remain subject to mechanical limits. Python supplies zero desired velocity and zero feed-forward torque. It also sets both raw head targets to zero; the effort-projected target can differ from zero when required by the actuator limit. Do not independently change action scaling, motor ordering, gains, or projection semantics. L1 latches the policy-start request. R1 latches stop and captures the last legal measured pose. The 100 ms watchdog, motor faults, invalid commands, competing publishers, and non-PR/invalid state also latch faults. Stale or invalid LowState inhibits LowCmd output; the bridge does not compute a hold from stale state. A recovered input does not clear a latch. Exit and restart through the full admission procedure. The bridge must stop last among the control processes during normal shutdown. Stale production shared memory is handled only with the guarded `--replace-stale-shm` path after verifying that no bridge process owns it. Software stop is not a substitute for an independent hardware emergency stop.