Packages
The control stack is split across two sibling repos that are
cloned side-by-side under bar_ws/src/:
T-K-233/bar_ros2— 12 packages making up the unified low-level control surface (URDF, controllers, hardware plugins, the generic ONNX policy runner, both Lite and Prime bringups). No task-specific code.T-K-233/pianist_ros2— 4 packages implementing the piano playing task on top ofbar_ros2: piano MJCF assets, a scene-composition launch, the piano-specific messages, and apianist_policypackage that subclassesbar_policy's generic runner.
The split is deliberate. bar_ros2 is robot/control infrastructure
that every task reuses; pianist_ros2 is the first concrete task
following the pattern. New task families (locomotion-task, dexterous
grasping, etc.) follow the same shape — a sibling repo depending on
bar_msgs + bar_policy, with its own bringup launch + policy
runner subclass.
The rest of this page walks each package in turn. The
pianist_ros2 packages
get their own section near the bottom.
Per-package details (bar_ros2)
bar_common
Header-only POD types and real-time helpers. Every C++ package in the workspace depends on this.
| Header | Purpose |
|---|---|
mit_state.hpp | bar::MITState — canonical observation struct (joint pos/vel/effort, IMU quat in (w, x, y, z) order, body-frame gyro/accel, last_action). Code-level schema, not a published topic. |
joint_index_map.hpp | Bidirectional name ↔ index map, used to validate incoming MITCommand joint_names against a controller's claimed order. |
rt_utils.hpp | RT-safe primitives: monotonic_ns(), all_finite(...), clamp(...). No allocations. |
loaned_interface_helpers.hpp | bar::set_cmd(...) and bar::get_state(...) — discard wrappers for Jazzy's [[nodiscard]] bool set_value() and get_optional<T>() migration. Centralizes the Kilted migration to one file. |
bar_msgs
Custom ROS 2 interfaces. Once a trained policy depends on one, it is frozen.
| Message | Used by |
|---|---|
MITCommand | bar_policy → RemotePolicyController. The on-wire command format. |
ControlMode | mode_manager → /control_mode telemetry. |
StandbyState | StandbyController → /standby_controller/state (is_finished gate for the START_LOCOMOTION / START_REMOTE intents). |
SafetyStatus | every hardware plugin / controller → /safety_status. Per-bus source field; bitmask in flags. |
See Messages reference for full schemas, including
pianist_msgs/PianoKeyState from the piano sibling repo.
bar_description_lite / bar_description_prime
URDF / xacro / meshes / <ros2_control> blocks.
Layout (Lite shown):
bar_description_lite/
├── urdf/
│ ├── lite.urdf.xacro # top-level kinematics (mode-aware: 14 or 17 joints)
│ └── lite.ros2_control.xacro # 3-way plugin selector + per-joint static config
├── meshes/ *.stl # visual meshes
├── mjcf/ lite.xml # MuJoCo physics model
├── config/ view_lite.rviz # RViz config
└── launch/ view_lite.launch.py # standalone visualization
The xacro selects between three hardware backends via args:
The real-hardware path emits two <ros2_control> blocks (LiteLeftArm
carrying CAN ids 11..17, LiteRightArm carrying 21..27, with bus names
provided per-machine via the hardware_config YAML); sim and mock
collapse into one combined block. Per-joint static config (can_id,
model, direction, lower_limit, upper_limit, torque_limit,
current_limit) is emitted as <param> children only on the
real-hardware path. Values mirror
T-K-233/Lite-Lowlevel-Python's
configs/bimanual.yaml; see
Hardware specifications → Joint table
for the canonical values.
bar_drivers/bar_socketcan
Reusable SocketCAN bus library. Owns the kernel-facing CAN socket lifecycle, a
dedicated I/O thread, and lock-free buffers. Per-actuator-family plugins
(bar_robstride, bar_sito) consume its synchronous read_state() /
write_command() API.
Pattern reference: mirrors the soem_ros2 / cleardrive_ros2 split from
legged_control2.
bar_devices/bar_robstride and bar_devices/bar_sito
Per-actuator-family hardware_interface::SystemInterface plugins.
bar_robstride for Lite (and Prime's auxiliary joints if added later);
bar_sito for Prime's Sito side.
Both:
- Export the standard 3 state interfaces (
<joint>/position,<joint>/velocity,<joint>/effort). - Export the 5 MIT-mode command interfaces (
position,velocity,effort,stiffness,damping). - Read
can_interface(system-level) and per-jointcan_idfrom URDF params. - Register via
pluginlibagainsthardware_interface::SystemInterface.
bar_robstride additionally:
- Reads per-joint
model(one ofrs-00..rs-06— drives the MIT-mode scaling limits),direction(±1),lower_limit/upper_limit(joint-frame rad clipping at command time), andtorque_limit/current_limit(Nm / A — opt-in firmware writes guarded by the hardware-levelwrite_firmware_limitsparam) from URDF<param>children. - Reads a system-level
calibration_file(YAML,{joint_name: {homing_offset, direction, id}}) aton_configureand applies the standard convention at the bus boundary:Empty path = identity calibration (still appliesread: joint_pos = direction * (raw_motor_pos - homing_offset)
write: raw_motor_pos = direction * joint_pos + homing_offset
# velocity / effort: direction only, no offsetdirection). The YAML keeps the same per-joint keys asT-K-233/Lite-Lowlevel-Python's JSON output, so values move between the two stacks unchanged. - Publishes a
bar_msgs/SafetyStatuson/safety_status(TRANSIENT_LOCAL, per-bus source field) with bit-flags forBUS_OFF/RX_TIMEOUT/TX_QUEUE_OVERRUN/MOTOR_FAULT/TEMPERATURE_LIMIT/INVALID_FRAME.mode_managersubscribes and auto-falls to DAMPING on any non-OK level.
Ships three CLI executables alongside the plugin:
| Executable | Purpose |
|---|---|
robstride_ping | One-shot GetDeviceId ping at a specific id. Read-only. |
robstride_discover | Scan an id range across one bus and print everyone that answers. Read-only, no Enable. |
mit_slider_gui | Tk-Qt slider window publishing Float64MultiArray to forward_command_controller/MultiInterfaceForwardCommandController; manual command of position/velocity/effort/stiffness/damping per joint. |
bar_controllers
Five mode-FSM controllers + the standalone mode_manager executable.
| Plugin | State | Source |
|---|---|---|
bar/ZeroTorqueController | startup, safer fault fallback | zero_torque_controller.cpp |
bar/DampingController | compliant fail-safe | damping_controller.cpp |
bar/StandbyController | pose interpolation + gain ramp | standby_controller.cpp |
bar/RLPolicyController | in-process ONNX (locomotion) | rl_policy_controller.cpp |
bar/RemotePolicyController | thin executor for bar_policy | remote_policy_controller.cpp |
mode_manager exe | FSM orchestrator | mode_manager.cpp |
bar_cli
The unified verb/noun CLI surface (bar bus ping, bar bus discover,
bar motor slider, bar viz rerun, bar viz viser). An ament_python
package that thin-wraps the underlying executables shipped by
bar_robstride and bar_bringup_lite. Invoke as bar <verb> <noun> …
once install/setup.bash is sourced. (pixi run bar … is the
workspace-level shortcut — see
Workspace shortcuts with pixi.)
bar_policy
An ament_python package that runs ONNX policies out-of-process and
publishes MITCommand over DDS to bar::RemotePolicyController. The
remote_policy_runner node composes six subsystems:
| Module | Role |
|---|---|
onnx_policy.OnnxPolicyRunner | Thin onnxruntime.InferenceSession wrapper; float32 (1, N) -> (1, M). |
policy_metadata.PolicyMetadata | Typed access to the 13 self-describing fields baked into the ONNX custom_metadata_map (joint names, gains, default pose, observation term names, dataset id, ...). |
observation_manager.ObservationManager | Concatenates configured ObservationTerm slices into the policy's flat input vector. Owns the reference-provider lifecycle (reset / step). |
term_builders | Registry mapping observation-term names from the ONNX (joint_pos, joint_vel, actions, imu_*, motion_body_pos_b, motion_body_ori_b, …) to ObservationTerm instances. |
reference.tracking.TrackingReferenceProvider | Loads a LeRobot motion file (HF Hub or local) and serves per-frame teleop targets (body positions / orientations, joint references) on the cadence the manager steps it. |
action_decoder.PolicyActionDecoder + ActionMapper | Decode target = default + scale * action per action joint, then assemble a full-articulation MITCommand with undriven joints pinned to position=0 and the same K/D the policy ships. |
The ObservationManager (and every ObservationTerm) mirrors the C++
side structurally — same term names, same scaling convention
out = (q - default) * scale, same flat-ndarray observation contract.
A policy debugged in Python promotes to the C++ RLPolicyController tier
without observation-indexing drift.
See Policy runner for the full ONNX metadata schema, launch args, and the dataset-resolution order.
Task-specific packages live in sibling repos
bar_ros2 deliberately does not carry task-specific code. The piano
playing task lives in
T-K-233/pianist_ros2,
cloned side-by-side under bar_ws/src/:
| Package | Build type | What it ships |
|---|---|---|
pianist_assets | ament_cmake | Piano MJCF (piano.xml) installed under <share>/pianist_assets/mjcf/. |
pianist_bringup | ament_cmake | mujoco.launch.py — composes a _runtime_lite_piano.xml scene next to lite.xml (so MuJoCo's meshdir="../meshes/" resolves), then delegates to bar_bringup_lite/mujoco.launch.py with scene:=_runtime_lite_piano. Also spawns piano_state_bridge so /piano/key_state exists on the sim path. |
pianist_msgs | ament_cmake | PianoKeyState msg, published on /piano/key_state (measured) and /piano/target_keys (song goal — re-published each tick by the piano runner so a bag recording is enough for offline F1). |
pianist_policy | ament_python | piano_policy_runner console script + piano_policy.launch.py (the runner is a bar_policy.RemotePolicyRunner subclass that adds the PianoCompositeProvider — song lookahead + live key-state). Also ships piano_state_bridge (sim → bool) and midi_keyboard_driver (real USB-MIDI → bool), with a matching midi_keyboard_driver.launch.py. |
pianist_ros2 depends on bar_policy for the generic ONNX-runner
infrastructure and on bar_msgs/MITCommand for the wire contract with
RemotePolicyController. From bar_ros2's point of view a piano policy
looks like any other out-of-process Python policy publishing
MITCommand — no piano-specific hooks land in the generic packages.
New task families follow the same pattern: depend on bar_msgs +
bar_policy, register a task-specific runner subclass, ship a
bringup launch that composes onto bar_bringup_lite/mujoco.launch.py
or …/real.launch.py.
bar_bringup_lite / bar_bringup_prime
The "main()" of the project. Each robot ships parallel launches
(franka_ros2-style per-robot bringup — there is no top-level
bringup.launch.py). For Lite, four total — three that boot a
control plane and one host-side viewer (see
Concepts → Architecture → Deployment topology
for which side runs which):
| Launch | Deployment side | Hardware path | Selected xacro args |
|---|---|---|---|
real.launch.py | Robot onboard computer | bar_robstride / bar_sito over SocketCAN (+ EtherCAT for Prime) | use_fake_hardware:=false use_sim:=false |
mujoco.launch.py | Single-machine sim/dev | mujoco_ros2_control/MujocoSystem inside mujoco_sim | use_sim:=true |
calibrate.launch.py | Single-machine (robot benchtop) | bar_robstride with identity calibration + calibrate_robot observer | use_fake_hardware:=false use_sim:=false |
viz.launch.py | Operator workstation (host) | DDS-consumer only; no controller_manager, no hardware | n/a — only subscribes |
The first two launches:
- Expand the robot xacro.
- Start either
ros2_control_node(real) ormujoco_sim(sim, withMujocoRos2ControlPluginloaded as a pluginlib physics plugin). - Start
robot_state_publisher. - Spawn
joint_state_broadcaster(active) +zero_torque_controller(active) + the four remaining mode controllers (inactive). - Start
mode_manager(whenenable_mode_manager:=true). - Start
joy_node(whenenable_gamepad:=true, which is the default).
bar_bringup_lite/config/sim_overrides.yaml adds use_sim_time:=true
on top of bar_lite_controllers.yaml for the MuJoCo path.
bar_bringup_lite/config/calibration.yaml is the per-physical-robot
zero offset that bar_robstride applies at the bus boundary on the
real path (regenerate via ros2 launch bar_bringup_lite calibrate.launch.py — see the
Launch args
page). bar_bringup_lite/config/lite_hardware.yaml is the per-machine
bus + joint mapping consumed by xacro. bar_bringup_prime/config/ethercat.yaml
configures the IgH master for Prime real-hardware bringup.
bar_bringup_lite also ships three operator-facing Python nodes.
The viewers are normally launched on the host side via
ros2 launch bar_bringup_lite viz.launch.py (which wraps them and
selects between them via viewer:=viser|rerun); direct
ros2 run bar_bringup_lite rerun_viz / viser_viz is the
single-machine sim/dev shortcut:
| Executable | Purpose |
|---|---|
calibrate_robot | Calibration observer — subscribes /robot_description + /lite/joint_states, tracks per-joint (min, max), writes the homing-offset YAML on Ctrl+C. Driven by calibrate.launch.py (ros2 launch bar_bringup_lite calibrate.launch.py). |
rerun_viz | Live URDF visualization in the native rerun viewer — auto-spawned subprocess by default, or --connect host:port to feed a remote one. Subscribes /robot_description (latched) and a --joint-state-topic (default /lite/joint_states); logs per-joint Transform3D to /tf at the tick rate. rerun-sdk ships in the workspace env. |
viser_viz | Same subscriptions and resolution pattern, rendering into a browser at http://<host>:<port> (default 0.0.0.0:8080) via viser. Friendlier for headless robot machines and SSH workflows. viser, yourdfpy, and scipy ship in the workspace env. |
External (vcs-imported, not in this repo)
Pinned in bar.repos and fetched via
vcs import --input src/bar_ros2/bar.repos src — see the
installation page.