ConvolutedLadybug.comLeRobot Experiments

Integration · Post 6 of 6

Streaming joint states to ROS 2

A dependency-free publisher that a ROS 2 node wraps as a subprocess.

Robotics courses often mean ROS 2, so we also wanted the SO-101's live joint positions available as a sensor_msgs/JointState stream.

The constraint

On our machine LeRobot needs Python 3.13, while ROS 2 Jazzy is built for Python 3.12. That means rclpy cannot be imported from LeRobot's environment, and LeRobot cannot easily be imported from ROS's.

The approach: a process boundary

  1. examples/so101_ros/publish_joint_states.py connects to the follower with SO101Follower and prints newline-delimited JSON on stdout: joint positions in radians, keyed by the URDF's joint names. It has no ROS dependency at all.
  2. A ROS 2 node in a separate workspace runs that script as a subprocess through uv run and republishes each line as a JointState.
uv run python examples/so101_ros/publish_joint_states.py --port /dev/ttyACM0

Because the interface is plain JSON lines, either side can be tested on its own, and neither environment has to be compatible with the other's Python version. Joint order follows the SO-101 convention (shoulder_pan, shoulder_lift, elbow_flex, wrist_flex, wrist_roll, gripper), and the names match assets/so101/so101_new_calib.urdf so tools such as RViz can display the arm directly.

The ROS 2 node itself lives in a separate workspace and is not part of the LeRobot tree, so this post covers only the LeRobot side.