Part II: Teleoperation and data collection · §25 of 43

Never run both stacks on the same follower bus. There is no lock between them. Both would send MIT commands to motors 1–8 and the arm would follow whichever frame arrived last.

Going from → toDo this
ROS 2 → LeRobot1. Move the arms to the hanging pose (home / all zeros) with ROS. 2. Support the arms. 3. bash /root/scripts/openarm_stop.sh (motors off, then ROS stopped; a plain Ctrl+C can crash before the motors are switched off, §12.4). 4. Check ps aux | grep ros2_control_node in the container shows nothing. 5. Start LeRobot.
LeRobot → ROS 21. Lower the leader so the follower hangs. 2. Ctrl+C LeRobot (torque off → arms limp; support them). 3. Both arms powered, then bash /root/scripts/openarm_launch.sh real (zeus cabling built in, §22). On activation the arms go to zero in 10 s (patched driver, §4); from hanging, that’s a tiny move. 4. Identification wiggle (§12.2).

Handing a follower bus from one stack to the other. Never run both at once.

flowchart TB
  subgraph a["ROS 2 → LeRobot"]
    direction TB
    a1["Move arms to the hanging pose<br/>(home / all zeros) with ROS"] --> a2["Support the arms"] --> a3["Ctrl+C the ROS launch"] --> a4["Check no ros2_control_node<br/>is left in the container"] --> a5["Start LeRobot"]
  end
  subgraph b["LeRobot → ROS 2"]
    direction TB
    b1["Lower the leader so<br/>the follower hangs"] --> b2["Ctrl+C LeRobot<br/>(torque off: support the arms)"] --> b3["Launch ROS, real hardware,<br/>zeus CAN arguments"] --> b4["Activation moves the arms<br/>to zero in ~2 s"]
  end

A quick “is the bus free?” check from the host:

candump -n 20 -T 500 can0      # no output within 0.5 s means no one is commanding the left arm (zeus: can0 = left)

Ways to combine them (not built yet)

  • Replay LeRobot demos in RViz/MoveIt: load a dataset (LeRobotDataset) in the lerobot env, convert each frame with lerobot_to_ros() (§20), and send it as a JointTrajectory to the ROS mock’s /left|right_joint_trajectory_controller/joint_trajectory topic. The two Python envs differ (conda vs ROS Jazzy), so use a file in between (e.g. export to CSV/NumPy) or run rclpy from the ROS container on an exported file.
  • Live view of LeRobot teleop in RViz: a small bridge that publishes the follower observation as sensor_msgs/JointState (radians/metres) to a robot_state_publisher running without ros2_control. Read-only, so it doesn’t conflict on the bus as long as the bridge gets its data from LeRobot, not from CAN.
  • LeRobot policy through ROS: write a custom LeRobot Robot class whose send_action() publishes to the ROS trajectory controllers and get_observation() subscribes to /joint_states. Then ROS owns the bus, and LeRobot benefits from ROS-side safety (limits, MoveIt collision checks). This is the cleanest long-term integration, but it’s real work.