Adapting Nodes

Node Requirements

Ensure the node meets the required execution behavior:

  • All execution must happen in ROS callbacks. Those can be subscription-, timer- and service-callbacks. Execution not inside ROS callbacks might include things such as a separate main-loop possibly in a separate thread, this is not allowed as it can not be controlled by the executor. It is permissible to perform processing in a separate thread, if that processing is only ever triggered by a ROS callback, and as such could be modeled or implemented as a single ROS callback.

  • Observable node behavior must be deterministic. Specifically, the sequence of (ROS-)outputs (outgoing service calls and published messages) must remain exactly the same if the same sequence of inputs is applied again. This implicitly forbids reading data from sensors or devices which may change at any point. Stochastic processes depending on randomness might be used if the pseudo-RNG is deterministically seeded.

  • Inputs and outputs must be causally related, in a way that is describeable by the config file, see below. Callbacks may omit one or multiple outputs in some invocations, see the status message.

Status Outputs

If a callback does not produce an output, it must inform the orchestrator of callback completion by publishing a Status message.

Listing 1 orchestrator_interfaces/msg/Status ROS message definition
1string node_name
2string[] omitted_outputs
3int32 debug_id

Omitted Outputs

Status messages may list omitted_outputs. This is a list of topic outputs which would usually occur during the current callback, but will not occur in this specific callback invocation. It must contain remapped topic names, not internal names. The actual topic name is available from ROS publishers in rclcpp (rclcpp::PublisherBase::get_topic_name) and rclpy (rclpy.publisher.Publisher.topic_name):

status_msg.omitted_outputs
  .push_back(this->publisher->get_topic_name());
status_msg.omitted_outputs = [self.publisher.topic_name]

Hidden Subscriptions

Components such as the tf2_ros.transform_listener.TransformListener create subscriptions with callbacks that do not publish status messages. To enable using the orchestrator with those components, it is possible to wrap the rclpy.node.Node class:

Listing 2 rclpy Node wrapper with status publisher
 1class OrchestratorWrapperNode:
 2    def __init__(self, node):
 3        self.node = node
 4        self.callbacks = {}
 5        self.orchestrator_status_pub = self.node.create_publisher(Status, "/status", 10)
 6
 7    def publish_status(self):
 8        status_msg = Status()
 9        status_msg.node_name = self.node.get_name()
10        self.orchestrator_status_pub.publish(status_msg)
11
12    def create_subscription(self, topic_type, topic, callback, *args, **kwargs):
13        self.callbacks[topic] = callback
14        return self.node.create_subscription(topic_type, topic, lambda msg, topic=topic: self.handle(msg, topic), *args, **kwargs)
15
16    def destroy_subscription(self, subscription):
17        self.node.destroy_subscription(subscription)
18
19    def handle(self, msg, topic):
20        self.callbacks[topic](msg)
21        self.publish_status()
22
23class MyModule:
24    def __init__(self, node):
25        self.tf2_buffer = tf2_ros.Buffer()
26        self.tf2_listener = tf2_ros.TransformListener(self.tf2_buffer, OrchestratorWrapperNode(node))
27        # ...

JSON Node Behavior Description

Node behavior needs to be described statically in a JSON configuration file. The file must adhere to the following schema (which is available at node_config_schema.json for IDE integration): Examples of node configs are given below.

Node Configuration

Behavior specification of a ROS node

node_config_schema

type

object

properties

  • name

type

string

  • callbacks

List of callback specifications

type

array

items

Callback specification

  • services

type

array

items

type

string

uniqueItems

True

additionalProperties

False

Callback specification

type

object

properties

  • name

type

string

  • trigger

oneOf

Internal input topic name

type

string

Internal input topic name

Timer

ApproximateTimeSynchronizer

  • outputs

type

array

items

type

string

  • service_calls

type

array

items

Service name

type

string

  • changes_dataprovider_state

type

boolean

  • may_cause_reconfiguration

type

boolean

additionalProperties

False

Internal input topic name

type

object

properties

  • type

type

string

const

topic

  • name

type

string

additionalProperties

False

Timer

type

object

properties

  • type

type

string

const

timer

  • period

Timer period in nanoseconds

type

integer

exclusiveMinimum

0

additionalProperties

False

ApproximateTimeSynchronizer

type

object

properties

  • type

type

string

const

approximate_time_sync

  • input_topics

type

array

items

type

string

minItems

2

uniqueItems

True

  • slop

type

number

  • queue_size

type

integer

additionalProperties

False

Node Config Example

A node config might then look like this:

Listing 3 “Detector Node” config example
 1{
 2    "name": "detector",
 3    "callbacks": [
 4        {
 5            "name": "Input processing callback",
 6            "trigger": "input",
 7            "outputs": [
 8                "output"
 9            ]
10        }
11    ]
12}
Listing 4 “Planning Node” config example
 1{
 2    "name": "planning",
 3    "callbacks": [
 4        {
 5            "name": "Planning timer callback",
 6            "trigger": {
 7                "type": "timer",
 8                "period": 300000000
 9            },
10            "outputs": [
11                "output"
12            ],
13            "service_calls": [
14                "egomotion"
15            ]
16        }
17    ]
18}