teleop_bridge_node is a bridge, never a driver. It owns no servo bus, no port
and no camera. It reads the topic your leader driver publishes, sends those
values to the other machine, and publishes them onto the topic your follower
driver subscribes to.
How it fits
You run the same node on both machines, told which side it is on.Install ROS 2
You need a working ROS 2 installation first. If you do not have one, follow the official installation guide.Install the bridge
Two steps, because the two halves come from different package systems. The node itself is a pip package. ROS needs a package of its own beforeros2 run and
ros2 launch can find it.
The node
Install it into the interpreter ROS 2 itself runs on, not whichever one your shell points at. On Ubuntu 24.04 that interpreter is marked externally managed, so this needs--break-system-packages, which still only writes to ~/.local:
The ROS package
videosdk_teleop_ros holds no code. It is a launch file, a default parameter
file, and the entry point that lets ROS start the node by name.
Run
colcon build --symlink-install twice the first time. The first build
succeeds and still leaves ros2 unable to find the package, and the second
writes the missing environment hook.Usage
Each terminal starts from a fresh shell with none of the environment:enable call. The leader logs the peer’s shape as it
is accepted or refused at arm time, which is the check that catches the two ends
disagreeing about units.
calibration_profile is where each host’s joint travel comes from. See
SO-101 for that, or set joint_min and joint_max
directly for any other arm.
Parameters
ros2 param list on a running node is the ground truth. The ones worth knowing
before you launch:
The packaged
config/videosdk_bridge.yaml sets max_norm_step: 1.0 rather
than the node’s 5.0, which turns the bridge’s own rate limit on. Leave it
if your driver has no limiter, raise it if it does.ros2 param dump /videosdk_bridge shows what a running node actually
resolved, YAML and command line together.Topics and services
Both roles publish and subscribe only what the parameter table names.There is no
~/set_torque, and torque_enable() on this bridge does nothing.
It powers no hardware, and the default failsafe is HOLD. Cutting power to
the motors has to come from your driver, which is the only thing here that
touches hardware.Normalisation
Each machine holds exactly one calibration: its own. The leader divides by its own travel, the follower multiplies by its own, and neither host needs the other’s numbers. Two arms with different travel, such as an SO-101 leader and follower differing by about 19% at the gripper, both still reach their own end stops. For a non-SO-101 robot there is nothing to install. Givejoint_min and
joint_max in whatever units your topics already carry:
Safety
Split by what each end can actually know.~/enable is the deadman, and it only exists on the leader. Disarming stops
the command stream, the follower’s watchdog expires, and the far arm holds.
Heartbeats keep flowing either way, so the follower can tell “operator let go”
from “link died”.
On an e-stop the bridge stops publishing commands and your driver’s own
stale-command behaviour takes over. Hold time is watchdog_timeout_s plus your
driver’s command timeout.
Schema checks
The leader validates the follower’s announced descriptor directly: profile, joint names in order, and normalised units. It refuses to arm otherwise.Without that check, a bridge paired with a native arm node would decode
0..1
as that node’s degrees, and every joint would drive to the bottom of its
travel.Timestamps
The bridge stampsheader.stamp with the local clock on the robot machine, so a
downstream driver measuring against it is measuring bridge to hardware. For the
operator-to-hardware figure across two hosts, use staleness_ms and the
observation-to-applied summary instead.
Joint states
Publish joint states from your driver at 10 Hz or more. Without them the bridge runs open-loop, echoing what it last commanded, andmax_misalignment and
tracking error stop meaning anything.
Adapters
rclpy. A plain callable (sink) and a thread-safe
latest-value cache stand in for the ROS graph, so you can exercise them with no
ROS installed. See
Adapters.
Message shape
The node speaks baresensor_msgs/JointState in both directions. ros2_control
and MoveIt expect a JointTrajectory-shaped controller interface and a URDF, so
that route needs a converter node between them.
Related
SO-101
Read joint travel from a LeRobot calibration instead of typing it in.
Safety
The checks a command passes before it reaches the motors.