Skip to main content

proton_ros2

proton_ros2 is a ROS 2 adapter for Proton. It runs both a ROS 2 node and a protoncpp node in the same process, and bridges Proton bundles to ROS 2 topics for publishers and subscribers, or services for servers and clients.

note

The proton_ros2 package is only released in ROS 2 Jazzy, but can be built from source in other ROS 2 distros.

Converting from Proton to ROS 2

Bundles are dynamic enough that they can support the message structure of most ROS 2 messages. ROS 2 primitives can be mapped to an equivalent Proton Signal primitive, and a Proton Bundle structure can be defined based on the ROS 2 message fields.

Primitive Scalar mapping

ROS 2Proton
float32float
float64double
int8int32
int16int32
int32int32
int64int64
uint8uint32
uint16uint32
uint32uint32
uint64uint64
boolbool
charuint32
byteuint32
stringstring

Primitive List mapping

ROS 2Proton
float32[]list_float
float64[]list_double
int32[]list_int32
int64[]list_int64
uint8[]bytes
uint32[]list_uint32
uint64[]list_uint64
bool[]list_bool
byte[]bytes
string[]list_string
note

Types outside of these tables are not currently supported

Type Adapters

Using the rclcpp::TypeAdapter struct, conversion functions can be created to convert back and forth between Bundles and ROS 2 messages. proton_ros2 implements a similar ServiceTypeAdapter for ROS 2 service request and response messages.

proton_ros2 implements a Python-based code generated that will generate templated C++ code for all supported ROS 2 messages and services. Each message will have a convert_to_ros and convert_to_custom function generated which implements the logic for converting from Proton to ROS 2. Because ROS 2 messages have the ability to contain nested submessages, the generator will attempt to flatten the message into a format that can be mapped to a Proton Bundle. Flattening is done by recursing through a field in a ROS 2 message until the primitive data type is reached, then creating a signal with an equivalent Proton primitive type. The name of the signal will be the name of each field in the path joined by an underscore.

For example, the sensor_msgs/IMU has a geometry_msgs/Quaternion field called orientation. Within Quaternion are the fields x, y, z, and w of type float64. The Bundle that would be used for this message would then have to have the signals orientation_x, orientation_y, orientation_z, and orientation_w of type double, which would be mapped to the ROS 2 message fields.

Configuration

In the Proton configuration file, a ros2 section can be added to map Proton Bundles to ROS 2 topics or services.

Topics

Topic publishers or subscribers can be configured with the following keys:

KeyTypeDescriptionRequired
topicstringTopic nameYes
messagestringROS 2 message typeYes
bundlestringThe Proton Bundle for this topicYes
qosstring or MapThe QoS profile for this topicNo

Bundles that the target consumes will create a ROS 2 publisher, while Bundles that the target produces will create a ROS 2 subscriber. The Bundle must have a signal with a name matching the path of the ROS 2 message field, as well as the same equivalent type.

Example

To create a publisher to the topic /my_string of type std_msgs/msg/String, the following configuration can be used:

nodes:
- name: pc
transport:
type: udp4
ip: 127.0.0.1
port: 11416
- name: mcu
transport:
type: udp4
ip: 127.0.0.1
port: 11417

connections:
- first: {node: pc, id: 0}
second: {node: mcu, id: 0}

bundles:
- name: my_string
id: 0x100
producer: mcu
consumer: pc
signals:
- {name: data, type: string, capacity: 64}

ros2:
topics:
- {topic: /my_string, message: std_msgs/msg/String, bundle: my_string}

When the node is launched with pc as the target, it will know that pc consumes the my_string bundle, and so it will create a publisher that will convert the bundle to a std_msgs/msg/String and publish the message as soon as the bundle is consumed.

Note that the signal is named data to match the equivalent ROS 2 field.

Services

Service servers or clients can be configured with the following keys:

KeyTypeDescriptionRequired
topicstringService topic nameYes
servicestringROS 2 service typeYes
requeststringThe Proton Bundle for the request message of this serviceYes
responsestringThe Proton Bundle for the response message of this serviceNo
timeoutuint32Number of milliseconds to wait for a Proton response or a ROS 2 service server (Default: 1000ms)No
qosstring or MapThe QoS profile for this serviceNo

If the target produces the request Bundle, then the node will create a service server. Otherwise, it will create a service client. The response Bundle is optional. Each Bundle must have a signal with a name matching the path of the ROS 2 message field for the corresponding Request or Response message, as well as the equivalent primitive type.

Example

To create a service on the topic /light_switch of type std_srvs/srv/SetBool, the following configuration can be used:

nodes:
- name: pc
transport:
type: udp4
ip: 127.0.0.1
port: 11416
- name: mcu
transport:
type: udp4
ip: 127.0.0.1
port: 11417

connections:
- first: {node: pc, id: 0}
second: {node: mcu, id: 0}

bundles:
- name: light_switch_request
id: 0x100
producer: pc
consumer: mcu
signals:
- {name: data, type: bool}
- name: light_switch_response
id: 0x101
producer: mcu
consumer: pc
signals:
- {name: success, type: bool}
- {name: message, type: string, capacity: 64}

ros2:
services:
- {topic: /light_switch, service: std_srvs/srv/SetBool, request: light_switch_request, response: light_switch_response, timeout: 100}

When the node is launched with pc as the target, it will know that pc produces the request bundle, and so it will create a service that will convert a std_srvs/srv/SetBool request to the light_switch_request Bundle, then send it to the mcu target. It will then wait up to 100ms for a light_switch_response Bundle to be received. If successful, that Bundle will be converted to a std_srvs/srv/SetBool response, and returned to the ROS 2 service client.

QoS

The QoS profiles for both Topics and Services can also be configured. You can either choose a predefined standard profile, or create a custom one. To choose a standard profile, set the value of the qos key to the name of the profile.

Standard profiles

The following standard QoS profiles are supported:

ProfileDescription
defaultDefault topic profile
servicesDefault services profile
sensor_dataBest Effort sensor data profile
rosoutTransient Local profile for /rosout
system_defaultsDefault profile based on middleware

For example, a /rosout topic can be defined like this:

ros2:
topics:
- {topic: /rosout, message: rcl_interfaces/msg/Log, qos: rosout, bundle: log}

Custom profiles

A custom QoS profile can be created by defining the value of the qos key as another map. The map can have the following keys:

KeyTypeRequired
historystringNo
depthuint32No
reliabilitystringNo
durabilitystringNo
History

The history value can be one of the following policies:

PolicyDescription
keep_lastKeep the last depth messages
keep_allKeep all messages
system_defaultDefault based on middleware

The default history is system_default

Depth

If history is set to keep_last, depth will determine how many messages to keep.

The default depth is 10

Reliability

The reliability value can be one of the following policies:

PolicyDescription
best_effortAttempt to deliver samples, but may lose them if the network is not robust
reliableGuarantee that samples are delivered, may retry multiple times
system_defaultDefault based on middleware

The default reliability is system_default

Durability

The durability value can be one of the following policies:

PolicyDescription
transient_localThe publisher becomes responsible for persisting samples for “late-joining” subscriptions
volatileNo attempt is made to persist samples
system_defaultDefault based on middleware

The default durability is system_default

For example, a my_string topic can be defined like this:

ros2:
topics:
- topic: my_string
message: std_msgs/msg/String
qos:
history: keep_last
depth: 15
reliability: best_effort
durability: volatile
bundle: my_string

Usage

The executable produced by proton_ros2 is proton_ros2_node.

It takes two ROS parameters:

  • config_file (string): path to the Proton YAML configuration file
  • target (string): Proton node name this process represents (used to decide which bundles are produced/consumed)

The launch file proton_ros2.launch.py wires these parameters and supports an optional namespace.

Launch the node with ros2 CLI:

ros2 launch proton_ros2 proton_ros2.launch.py config_file:=/path/to/config.yaml target:=target_node namespace:=/my_namespace

The launched node with automatically spin up a ROS 2 and Proton node, connect to Proton peers, and create ROS 2 publishers, subscribers, services, and clients as defined in the configuration file.

Supported messages

The following message and service packages are currently supported in the proton_ros2 binary. Messages marked with a are supported and follow the standard Proton to ROS 2 mapping convention. Messages with a ✴️ symbol are supported with a custom mapping from Proton to ROS 2. Messages marked with are not supported at this time.

Messages

clearpath_platform_msgs

Message
DisplayStatus
Drive
DriveFeedback
Fans
Feedback
Lights✴️
PinoutCommand
PinoutState
Power
RGB
Status
StopStatus
Temperature

geometry_msgs

Message
Accel
AccelStamped
AccelWithCovariance
AccelWithCovarianceStamped
Inertia
InertiaStamped
Point
Point32
PointStamped
Polygon
PolygonInstance
PolygonInstanceStamped
PolygonStamped
Pose
Pose2D
PoseArray
PoseStamped
PoseWithCovariance
PoseWithCovarianceStamped
Quaternion
QuaternionStamped
Transform
TransformStamped
Twist
TwistStamped
TwistWithCovariance
TwistWithCovarianceStamped
Vector3
Vector3Stamped
VelocityStamped
Wrench
WrenchStamped

nmea_msgs

Message
Gpgga
Gpgsa
Gpgst
Gpgsv
GpgsvSatellite
Gprmc
Gpvtg
Gpzda
Sentence

rcl_interfaces

Message
FloatingPointRange
IntegerRange
ListParametersResult
Log
LoggerLevel
Parameter
ParameterDescriptor
ParameterEvent
ParameterEventDescriptors
ParameterType
ParameterValue
SetParametersResult

sensor_msgs

Message
BatteryState
CameraInfo
ChannelFloat32
CompressedImage
FluidPressure
Illuminance
Image
Imu
JointState
Joy
JoyFeedback
JoyFeedbackArray
LaserEcho
LaserScan
MagneticField
MultiDOFJointState
MultiEchoLaserScan
NavSatFix
NavSatStatus
PointCloud
PointCloud2
PointField
Range
RegionOfInterest
RelativeHumidity
Temperature
TimeReference

std_msgs

Message
Bool
Byte
ByteMultiArray✴️
Char
ColorRGBA
Empty
Float32
Float32MultiArray✴️
Float64
Float64MultiArray✴️
Header
Int16
Int16MultiArray
Int32
Int32MultiArray✴️
Int64
Int64MultiArray✴️
Int8
Int8MultiArray
MultiArrayDimension
MultiArrayLayout
String
UInt16
UInt16MultiArray
UInt32
UInt32MultiArray✴️
UInt64
UInt64MultiArray✴️
UInt8
UInt8MultiArray✴️

Services

clearpath_platform_msgs

Message
ConfigureMcu
SetPinout

std_srvs

Message
Empty
SetBool
Trigger

Adding custom message packages

If you wish to add support for additional message or service packages, the proton_ros2 package will have to be built from source.

First, create a workspace and clone the proton_ros2 package:

mkdir ~/proton_ws/src -p
cd ~/proton_ws/src
git clone https://github.com/clearpathrobotics/proton_ros2.git

Then, modify the CMakelists.txt file to find your package:

find_package(my_msgs REQUIRED)

Finally, add your package to the PROTON_ROS2_MESSAGES list (or PROTON_ROS2_SERVICES for a service package) and build the package.

set(PROTON_ROS2_MESSAGES
clearpath_platform_msgs
geometry_msgs
nmea_msgs
rcl_interfaces
sensor_msgs
std_msgs
my_msgs
)
source /opt/ros/jazzy/setup.bash
cd ~/proton_ws
colcon build
source ~/proton_ws/install/setup.bash

If you encounter errors while building, it may suggest that the generator was not able to flatten a message in your message package. In that case, you may need to create a configuration file to either customize how that message is mapped to a Proton Bundle, or disable that message entirely.

Custom mapping

Generator mappings can be customized through a configuration YAML file specific to the message or service package. To create a configuration file, create a .yaml file with the same name as your message package underneat the config/messages or config/services folder in proton_ros2. Inside the file, add a package: entry with the name of your package. Then, add a messages: or services: entry. Under this section, you will add a list of each message that you want to modify:

package: clearpath_platform_msgs
messages:
- name: Lights
path: msg
mapping:
- {ros2.path: lights, ros2.length: 0, ros2.subpath: red, proton.signal: lights, proton.subindex: 0, type: list_bytes}
- {ros2.path: lights, ros2.length: 0, ros2.subpath: green, proton.signal: lights, proton.subindex: 1, type: list_bytes}
- {ros2.path: lights, ros2.length: 0, ros2.subpath: blue, proton.signal: lights, proton.subindex: 2, type: list_bytes}

In this example, the clearpath_platform_msgs/msg/Lights message is being modified to have a custom mapping. The default generator mapping would have created three Signals called lights_red, lights_green and lights_blue, each of type bytes. Instead, this custom mapping creates a single Signal called lights which is a list_bytes type and maps the indices of the list_bytes to each colour.

Mapping rules

A mapping rule can be created for each ROS 2 field and equivalent Proton Signal. There are several mapping types:

  • Scalar: ros_msg.field = proton.signal
  • Proton Indexed: ros_msg.field = proton.signal[proton_index]
  • ROS Indexed: ros_msg.field[ros_index] = proton.signal
  • Both Indexed: ros_msg.field[ros_index] = proton.signal[proton_index]
  • Fixed List: ros_msg.field[n] = proton.signal[n]
  • Dynamic List: ros_msg.field[] = proton.signal[]
  • Fixed Submessage: ros_msg.field[n].subfield = proton.signal[n]
  • Dynamic Submessage: ros_msg.field[].subfield = proton.signal[]
  • Fixed Subindex: ros_msg.field[n].subfield = proton.signal[n][proton_subindex] (list_bytes only)
  • Dynamic Subindex: ros_msg.field[].subfield = proton.signal[n][proton_subindex] (list_bytes only)

These rules are defined in the YAML file with these parameters:

  • ros2.path: ROS field path (dot-separated, e.g. layout.data_offset)
  • ros2.index: fixed index into a ROS array/vector
  • ros2.length: list length behavior
    • > 0 indicates fixed-size array length
    • 0 indicates dynamic list (resize destination on Proton->ROS)
  • ros2.subpath: field name inside a nested message element (used for arrays of nested messages)
  • proton.signal: Proton Signal name
  • proton.index: fixed index into a Proton list signal
  • proton.subindex: subindex inside a list_bytes element
  • type: Proton signal type string (e.g. uint32, list_float, bytes, list_bytes)
note

Signals do not support dynamically sized arrays, so dynamic arrays in ROS 2 will be limited to the length of the Signal array

Diagnostics

proton_ros2 registers diagnostics stats via diagnostic_updater:

  • Per-connection RX/TX rates (KB/s) from the embedded protoncpp node
  • Per-bundle frequency (Hz) depending on whether target produces or consumes the bundle
  • Heartbeat frequency checks for peers that have heartbeat enabled in the Proton config

Diagnostics are published through the standard ROS 2 diagnostics pipeline.