Skip to main content

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 of bar_ros2: piano MJCF assets, a scene-composition launch, the piano-specific messages, and a pianist_policy package that subclasses bar_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.

HeaderPurpose
mit_state.hppbar::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.hppBidirectional name ↔ index map, used to validate incoming MITCommand joint_names against a controller's claimed order.
rt_utils.hppRT-safe primitives: monotonic_ns(), all_finite(...), clamp(...). No allocations.
loaned_interface_helpers.hppbar::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.

MessageUsed by
MITCommandbar_policyRemotePolicyController. The on-wire command format.
ControlModemode_manager/control_mode telemetry.
StandbyStateStandbyController/standby_controller/state (is_finished gate for the START_LOCOMOTION / START_REMOTE intents).
SafetyStatusevery 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:

xacro 3-way hardware selector

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-joint can_id from URDF params.
  • Register via pluginlib against hardware_interface::SystemInterface.

bar_robstride additionally:

  • Reads per-joint model (one of rs-00..rs-06 — drives the MIT-mode scaling limits), direction (±1), lower_limit / upper_limit (joint-frame rad clipping at command time), and torque_limit / current_limit (Nm / A — opt-in firmware writes guarded by the hardware-level write_firmware_limits param) from URDF <param> children.
  • Reads a system-level calibration_file (YAML, {joint_name: {homing_offset, direction, id}}) at on_configure and applies the standard convention at the bus boundary:
    read:  joint_pos    = direction * (raw_motor_pos - homing_offset)
    write: raw_motor_pos = direction * joint_pos + homing_offset
    # velocity / effort: direction only, no offset
    Empty path = identity calibration (still applies direction). The YAML keeps the same per-joint keys as T-K-233/Lite-Lowlevel-Python's JSON output, so values move between the two stacks unchanged.
  • Publishes a bar_msgs/SafetyStatus on /safety_status (TRANSIENT_LOCAL, per-bus source field) with bit-flags for BUS_OFF / RX_TIMEOUT / TX_QUEUE_OVERRUN / MOTOR_FAULT / TEMPERATURE_LIMIT / INVALID_FRAME. mode_manager subscribes and auto-falls to DAMPING on any non-OK level.

Ships three CLI executables alongside the plugin:

ExecutablePurpose
robstride_pingOne-shot GetDeviceId ping at a specific id. Read-only.
robstride_discoverScan an id range across one bus and print everyone that answers. Read-only, no Enable.
mit_slider_guiTk-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.

PluginStateSource
bar/ZeroTorqueControllerstartup, safer fault fallbackzero_torque_controller.cpp
bar/DampingControllercompliant fail-safedamping_controller.cpp
bar/StandbyControllerpose interpolation + gain rampstandby_controller.cpp
bar/RLPolicyControllerin-process ONNX (locomotion)rl_policy_controller.cpp
bar/RemotePolicyControllerthin executor for bar_policyremote_policy_controller.cpp
mode_manager exeFSM orchestratormode_manager.cpp

See Controllers reference.

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:

ModuleRole
onnx_policy.OnnxPolicyRunnerThin onnxruntime.InferenceSession wrapper; float32 (1, N) -> (1, M).
policy_metadata.PolicyMetadataTyped 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.ObservationManagerConcatenates configured ObservationTerm slices into the policy's flat input vector. Owns the reference-provider lifecycle (reset / step).
term_buildersRegistry 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.TrackingReferenceProviderLoads 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 + ActionMapperDecode 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/:

PackageBuild typeWhat it ships
pianist_assetsament_cmakePiano MJCF (piano.xml) installed under <share>/pianist_assets/mjcf/.
pianist_bringupament_cmakemujoco.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_msgsament_cmakePianoKeyState 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_policyament_pythonpiano_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):

LaunchDeployment sideHardware pathSelected xacro args
real.launch.pyRobot onboard computerbar_robstride / bar_sito over SocketCAN (+ EtherCAT for Prime)use_fake_hardware:=false use_sim:=false
mujoco.launch.pySingle-machine sim/devmujoco_ros2_control/MujocoSystem inside mujoco_simuse_sim:=true
calibrate.launch.pySingle-machine (robot benchtop)bar_robstride with identity calibration + calibrate_robot observeruse_fake_hardware:=false use_sim:=false
viz.launch.pyOperator workstation (host)DDS-consumer only; no controller_manager, no hardwaren/a — only subscribes

The first two launches:

  1. Expand the robot xacro.
  2. Start either ros2_control_node (real) or mujoco_sim (sim, with MujocoRos2ControlPlugin loaded as a pluginlib physics plugin).
  3. Start robot_state_publisher.
  4. Spawn joint_state_broadcaster (active) + zero_torque_controller (active) + the four remaining mode controllers (inactive).
  5. Start mode_manager (when enable_mode_manager:=true).
  6. Start joy_node (when enable_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:

ExecutablePurpose
calibrate_robotCalibration 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_vizLive 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_vizSame 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.