Callback Graph

The CB graph is a networkx multidigraph. The graph vertices are refered to as “actions”, to distinguish them from ROS nodes. In networkx, vertices have arbitrary hashable type and mutable attributes. Here, the hashable type is an increasing, unique, integer ID which is generated by (TODO: function). Every vertex has exactly one attribute with name “data”, which is of type Action.

Edges in networkx have no value, but every edge in the CB graph have an attribute to distinguish its type: Every edge has an attribute “edge_type” of type EdgeType.

class orchestrator.orchestrator_lib.action.EdgeType(value)

An enumeration.

CAUSALITY = 0

Edge points to the action which produces a required input

SAME_NODE = 1

Edge points to a previous action at the same node

SAME_TOPIC = 2

Points to a previous action receiving a topic published by this action

SERVICE_GROUP = 3

Points to a previous action calling a service provided by this node, or to the provider of a service called by this action

Action Types

The following action types are available:

orchestrator.orchestrator_lib.action.Action

alias of Union[TimerCallbackAction, RxAction, OrchestratorBufferAction, OrchestratorStatusAction, DataProviderInputAction]

The first two are “callback actions”, meaning they represent callbacks executed at nodes within the ROS stack.

orchestrator.orchestrator_lib.action.CallbackAction

alias of Union[TimerCallbackAction, RxAction]

class orchestrator.orchestrator_lib.action._BaseAction(state: ActionState, node: str, timestamp: Time, cause: TopicInput | TimerInput)

Base class for callback actions.

cause: TopicInput | TimerInput
node: str
state: ActionState
timestamp: Time
class orchestrator.orchestrator_lib.action.TimerCallbackAction(state: ActionState, node: str, timestamp: Time, cause: TimerInput)

Execution of a timer callback at a ROS node.

cause: TimerInput

Description of the causing input

property period: int

Alias for cause.period, provided for compatibility.

class orchestrator.orchestrator_lib.action.RxAction(state: ActionState, node: str, timestamp: Time, cause: TopicInput, data: Any | None = None, is_approximate_time_synced: bool = False)

Execution of a subscription callback at a ROS node.

cause: TopicInput

Description of causing topic input

data: Any | None = None

Cached input ROS message triggering this callback (deserialized)

is_approximate_time_synced: bool = False

Flag indicating that this topic input belongs to an approximate-time-synced message filter. This implies that the filter-callback might not be executed for this input.

property topic: str

Alias for cause.input_topic, provided for compatibility.

The remaining types are actions executed by the orchestrator or data source:

orchestrator.orchestrator_lib.action.OrchestratorAction

alias of Union[OrchestratorBufferAction, OrchestratorStatusAction, DataProviderInputAction]

Action state

class orchestrator.orchestrator_lib.action.ActionState(value)

An enumeration.

WAITING = 1

Waiting for external input (message data or time increment)

READY = 2

Data is buffered, ready to execute once allowed by ordering constraints

RUNNING = 3

Running, expecting output as specified by model

Graph Processing

“Processing” refers to iterating over the graph and executing actions if possible. Actions are able to be executed if their state is Ready (implying the required input is available/has been buffered) and if they have no outgoing edges (ordering dependencies). Processing is done exhaustively, meaning that the graph is repeatedly iterated over until no action has been executed within one iteration.

Timer Handling

Timers are triggered by messages on /clock, just as they would be during a simulation run. This requires all nodes using timers to set the use_sim_time parameter to True. When the data source announces a clock-publish, the corresponding timer callbacks are created in the CB graph. The actions are set to READY once the corresponding clock message actually arrives at the orchestrator. Clock messages are not buffered, since they can be recreated trivially from the known timer execution time.

Clock messages are only forwarded to nodes when they trigger a timer callback. This implies that using self.get_clock().now() or similar during callback execution or elsewhere always returns the time of the most recent timer callback. This is consistent with the model that nodes only execute during callbacks, and that all callbacks complete in zero time.

Unexpected behavior may occur although if non-timer callbacks exist which access the current node time. Usually, clock-messages could be received between the last timer callback and this callback, updating the node time. It would not be wise to rely on assumptions such as that the node time has already advanced to the current simulation time when an input from that simulation timestep is received, since the clock message may not always arrive before the input. If this is a problem, the time handling may need to be changed to forward every clock message (which presents a challenge since the clock-receive callback has no observable feedback from outside the node).