Sentinel
Connect with ROS 2

Robot control interface

Map measured joint state, position commands, robot description, joint names, and QoS.

The generic ROS 2 bridge connects Sentinel's control pipeline to the controller you already run. A manipulator integration needs three things:

InterfaceDirectionPurpose
Robot descriptionYour stack → SentinelJoint names, kinematics, and limits
sensor_msgs/msg/JointStateYour stack → SentinelContinuous measured robot state
Position command topicSentinel → your stackLive targets for the robot controller

Start and validate measured state before Sentinel. Every controlled joint must be present, and the stream must continue while the robot is idle or disarmed.

Publish measured state

Publish sensor_msgs/msg/JointState continuously at 100 Hz or faster:

ros2 topic hz /robot/joint_states
ros2 topic echo /robot/joint_states --once

Populate:

  • header.stamp with the measurement time.
  • name with stable joint names.
  • position in radians.
  • velocity in radians per second when available.
  • effort in newton-metres when available.

Sentinel matches samples by joint name. The order may change between messages, but each message's names and values must remain aligned.

Configure stale-state detection

The bridge faults when measured state stops arriving for longer than stale_timeout_s. The default is 0.5 seconds:

state:
  topic: /robot/joint_states
  qos: sensor
  stale_timeout_s: 0.5

Set the timeout from the expected publisher rate and worst validated network gap. Some high-rate remote systems need a larger value; for example, the Anvil OpenArm integration uses 2.5 seconds.

Setting stale_timeout_s: 0 disables stale-state fault detection. Do not disable it to hide an unreliable publisher or DDS connection.

Provide the robot description

Sentinel uses the URDF for joint names, kinematics, and limits. Choose one source:

SourceUse when
URDF or xacro fileThe model ships with the integration
robot_description parameterAn existing robot_state_publisher owns the description
Transient-local robot_description topicThe description is already distributed over ROS 2

Make the description available before the control pipeline starts.

description:
  source: parameter
  node: /robot_state_publisher
  parameter: robot_description
  timeout_s: 10.0

For multiple independently controlled systems, use a distinct description source for each one. Do not point two arms at the same unnamespaced description topic unless it contains the complete model expected by both systems.

Choose a command type

Use the message type your controller already accepts.

Use std_msgs/msg/Float64MultiArray with a forward position controller. This is the recommended ros2_control path for Sentinel's continuous target stream.

The message contains no joint names. Declare the exact fixed slot order:

command:
  outputs:
    - topic: /arm_controller/commands
      qos: reliable
      joint_names:
        - shoulder_pan_joint
        - shoulder_lift_joint
        - elbow_joint
        - wrist_1_joint
        - wrist_2_joint
        - wrist_3_joint

A bimanual system can declare one output per arm. A joint may appear in only one output.

Use trajectory_msgs/msg/JointTrajectory when your controller accepts named position targets:

command:
  topic: /robot/joint_trajectory
  qos: reliable
  single_point: false

The controller can identify joints from joint_names. Treat the incoming data as a live command stream; do not build up a queue of stale motion.

Never infer a Float64MultiArray slot order from the current JointState message. State may be reordered. The command array order is fixed by the controller configuration.

Map joint names and coordinates

Use remapping when the external controller and Sentinel URDF use different names. Use an affine transform when a joint has a different sign or zero:

command:
  topic: /external/arm/joint_trajectory
  qos: reliable
  joint_remap:
    shoulder_pan_joint: joint_1
  joint_transform:
    wrist_3_joint:
      scale: -1.0
      offset: 0.0

state:
  topic: /external/arm/joint_states
  qos: sensor
  joint_remap:
    joint_1: shoulder_pan_joint
  joint_transform:
    wrist_3_joint:
      scale: -1.0
      offset: 0.0
  stale_timeout_s: 0.5

For commands:

external = scale × sentinel + offset

State applies the inverse transform. Keep the public interface in SI units; do not use transforms to introduce degrees.

Match QoS

Set QoS independently for command and state:

PresetTypical use
reliableController command subscriptions
sensor or best_effortHigh-rate state publishers using sensor-data QoS
defaultPublishers and subscribers using ROS 2 defaults

If a topic appears in ros2 topic list but samples do not arrive, inspect both endpoints:

ros2 topic info --verbose /robot/joint_states

Add other capabilities

The same bridge can expose more than an arm:

CapabilitySupported command
GripperJointTrajectory, Float64, Float64MultiArray, or a slot in an arm array
Mobile basegeometry_msgs/msg/Twist
NeckJointTrajectory or PoseStamped
Dexterous handJointTrajectory or Float64MultiArray
ElevatorFloat64, Twist, or JointTrajectory
PTZTwist or TwistStamped

Start with measured arm state and arm commands. Add optional capabilities after the base integration is stable.

Validate before Sentinel

Inspect the URDF

Confirm that every controlled joint, link, and limit matches the physical robot.

Verify continuous measured state

Confirm the rate, names, units, and values. Leave the controller idle and verify that state continues.

Send one small controller-native command

Test the command topic without Sentinel in a cleared workspace.

Stop the command publisher

Confirm that the controller holds or stops according to the robot's validated watchdog behavior.

Verify from the Sentinel environment

Repeat discovery, sample, QoS, and subscriber checks from the container and domain that will run Sentinel.