Skip to main content

proton_ros2

proton_ros2 is a ROS 2 adaptor for proton. It runs both a ROS 2 node and a proton_cpp node in the same process, and bridges proton bundles to ROS 2 topics for publishers and subscribers.

proton_ros2 is a collection of packages for running proton on ROS 2 robots. These packages create an ecosystem to map proton bundles to ROS 2 messages and topics.

Package Hierarchy

note

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

Message Bridging​

In order to map the runtime-configurable proton Signals to compile-time ROS 2 messages, a binding configuration is required. This binding configuration maps proton's Signal types to specific ROS 2 message types. Bundles are analagous to publishers and subscribers, in that when a Bundle is successfully decoded, the bundle callback can be used to trigger a topic publication, and a received topic can trigger a Bundle to be sent to a peer.

Type Mapping​

Proton Signal types are simplistic enough to map to common ROS 2 message field types:

ROS 2proton
float32float
float64double
int8int32
int16int32
int32int32
int64int64
uint8uint32
uint16uint32
uint32uint32
uint64uint64
boolbool
charuint32
byteuint32
stringstring
byte[]bytes
note

Types outside of this table are not currently supported!!!

Message Binding Plugins (proton_ros2_adaptor_generator/proton_ros2_interfaces)

ROS 2 IDL does not support runtime creation of message types, instead, we have to run the code generator on .msg files to create classes. To support binding ROS 2 message fields to Proton signals, and to support arbitrary or custom ROS 2 message types, we need some common interfaces:

  • An interface for receiving Proton data and populating the ROS 2 message fields
  • An interface for subscribing to ROS 2 messages and updating Proton Signals in the Registry
  • An interface for loading the above bindings.

The answer is to autogenerate source code based on a message binding schema between ROS 2 and Proton. The architecture for this is to use a script proton_ros2_adaptor_generator to create a package containing the mappings. Each class within the package is based on an interface defined in proton_ros2_interfaces, using rclcpp's lower-level serialization API.

proton_ros2 then loads each plugin and filters based on the bindings in the config.

Message Binding Configuration​

In the proton configuration file, a messages section can be added to map proton Signals to fields in a ROS 2 message.

# These are generation-time bindings
messages:
# Binding name is unique identifier; ros2_type is the ROS message
- name: Drive
ros2_type: geometry_msgs/msg/Twist
mapping:
# ros2.path is the path to the particular field of the message struct, including nesting
- {ros2.path: linear.x, proton.signal: forward_speed, type: float}
- {ros2.path: linear.y, proton.signal: strafe_speed, type: float}
- {ros2.path: angular.z, proton.signal: turn_rate, type: float}

# For a ROS 2 message with an array type, the index must be supplied
- name: BoardTemps
ros2_type: clearpath_platform_msgs/msg/Temperature
stamp: header.stamp
mapping:
- {ros2.path: header.frame_id, proton.signal: board_temp_frame_id, type: string}
- {ros2.path: temperatures, ros2.index: 0, proton.signal: temp_mcu, type: float}
- {ros2.path: temperatures, ros2.index: 1, proton.signal: temp_pcb, type: float}
- {ros2.path: temperatures, ros2.index: 2, proton.signal: temp_fan1, type: float}

Publishing and Subscribing​

From there, message bindings are mapped to ROS 2 publishers and subscribers. Bindings can be reused across different topics.

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

KeyTypeDescriptionRequired
topicstringTopic nameYes
bindingstringMessage binding used for conversionYes
bundlestringThe proton Bundle for this topicYes
qosmapThe QoS profile for this topicNo
publishers:
- topic: /robot/board_temps
binding: BoardTemps # References adapter by binding name
bundle: telemetry # Publish when this bundle decodes
qos:
profile: sensor_data

subscribers:
- topic: /cmd_vel
binding: Drive
bundle: commands # Trigger this bundle after conversion
qos:
profile: default

QoS​

The QoS profiles topics 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

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, we can redefine the /robot/board_temps topic to something like this

publishers:
- topic: /robot/board_temps
binding: BoardTemps
bundle: telemetry
qos:
history: keep_last
depth: 15
reliability: best_effort
durability: volatile

Generating Message Binding Adaptor Packages​

Message binding packages are generated via the above config and proton_ros2_adaptor_generator

Dependencies​

  • python3-yaml: For parsing the configuration
  • python3-jinja2: Jinja is used as DSL for code generation template files.

Required Inputs​

  • config/-c: Path to yaml configuration file containing the proton configuration and bindings.
  • output/-o: Output directory for generated package
  • package_name/-p: Name of the generated ROS 2 package

Optional Inputs​

  • maintainer-name: Name of maintainer in package.xml
  • maintainer-email: Maintainer email in package.xml

Example Invocation​

ros2 run proton_ros2_adaptor_generator proton_ros2_adaptor_generator -c /path/to/proton_ros2/config/example.yaml -o /path/to/proton_ws/src/example_bridge -p example_bridge

This will create a package named example_bridge in /path/to/proton_ws/src/example_bridge. This package inherits the interface defined in proton_ros2_interfaces used by proton_ros2 to be loaded via pluginlib to do the message bridging. This means that the generated bridge package must either be built as part of your workspace, or installed as a colcon package.

To mark an adaptor package as loadable for proton_ros2, it must be added to the binding YAML

adaptor_packages:
- example_bridge

Full Binding Configuration Example​

nodes:
- name: mcu
id: 0
endpoints:
- id: 0
type: udp4
ip: 127.0.0.1
port: 11418
- name: pc
id: 1
endpoints:
- id: 0
type: udp4
ip: 127.0.0.1
port: 11419

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

signals:
- {name: forward_speed, id: 0, type: float}
- {name: strafe_speed, id: 1, type: float}
- {name: turn_rate, id: 2, type: float}
- {name: board_temp_frame_id, id: 3, type: string, value: "board_link"}
- {name: temp_mcu, id: 4, type: float}
- {name: temp_pcb, id: 5, type: float}
- {name: temp_fan1, id: 6, type: float}

bundles:
- name: telemetry
id: 0x100
producers: [mcu]
consumers: [pc]
signals: [4, 5, 6]
period_ms: 500
- name: commands
id: 0x101
producers: [pc]
consumers: [mcu]
signals: [0, 1, 2]

adaptor_packages:
- example_bridge

# These are generation-time bindings
messages:
# Binding name is unique identifier; ros2_type is the ROS message
- name: Drive
ros2_type: geometry_msgs/msg/Twist
mapping:
# ros2.path is the path to the particular field of the message struct, including nesting
- {ros2.path: linear.x, proton.signal: forward_speed, type: float}
- {ros2.path: linear.y, proton.signal: strafe_speed, type: float}
- {ros2.path: angular.z, proton.signal: turn_rate, type: float}

- name: BoardTemps
ros2_type: clearpath_platform_msgs/msg/Temperature
stamp: header.stamp
mapping:
- {ros2.path: header.frame_id, proton.signal: board_temp_frame_id, type: string}
- {ros2.path: temperatures, ros2.index: 0, proton.signal: temp_mcu, type: float}
- {ros2.path: temperatures, ros2.index: 1, proton.signal: temp_pcb, type: float}
- {ros2.path: temperatures, ros2.index: 2, proton.signal: temp_fan1, type: float}

# This is a runtime config to map bindings to ROS topics
publishers:
- topic: /robot/board_temps
binding: BoardTemps # References adapter by binding name
bundle: telemetry # Publish when this bundle decodes
qos:
profile: sensor_data

subscribers:
- topic: /cmd_vel
binding: Drive
bundle: commands # Trigger this bundle after conversion
qos:
profile: default

proton_ros2_node

This is the main runnable ROS 2 node in the Proton/ROS 2 ecosystem. It combines the Proton Registry access and plugin loading found in proton_ros2 with the serial and/or Ethernet transport defined in proton_cpp. proton_ros2_node handles transport sending/receiving, and implements the state machine for accumulating and parsing Proton messages, depayloading them, and passing into the registry.

Building​

proton_ros2_node depends on OTTO Motors' asynchronous serial_hardware packages for accessing serial or Ethernet hardware. These are closed-source packages and are installable via Clearpath's APT mirror.

sudo apt install ros-jazzy-serial-hardware

Building proton_ros2_node is the same as any other package

cd /path/to/proton_ws
colcon build --packages-up-to proton_ros2_node
colcon build --packages-up-to example_bridge
note

This will not build any generated adaptor bridge packages as they are not direct dependencies, instead loaded dynamically via pluginlib

Example Invocation​

Once your binding configuration is complete and adaptor packages are built, proton_ros2_node is runnable via:

ros2 launch proton_ros2_node proton_ros2_node.launch.py proton_config_file:=./src/proton_ros2/config/example.yaml binding_config_file:=./src/proton_ros2/config/example.yaml target:=pc

Parameters​

  • proton_config_file: File containing proton signal/bundle/node mapping
  • binding_config_file: File containing proton/ROS 2 bindings used with proton_ros2_adaptor_generator. Can be the same file as proton_config_file

Running proton_ros2 With Your Own Transport Package

If you have a preferred or already used Ethernet or serial interface package, or if working with OTTO Motors' closed-source packages is nonviable, proton_ros2 can be used as a dependency for your own interface packages. Feel free to use proton_ros2_node as a reference for implementing the serial decoding state machine, as well as how to interface with proton_ros2's Node Manager API.