ROS2 control status

Description

Reports when a ros2_control controller or hardware component crosses into or out of the active lifecycle state -- the state in which it is actually commanding or reading hardware. Rather than polling list_controllers/list_hardware_components, Ros2ControlStatus subscribes to the controller manager's own ~/activity topic (controller_manager_msgs/ControllerManagerActivity), republished with transient-local QoS on every controller/hardware-component lifecycle change, and feeds each component's stream of (state, timestamp) samples to its own dc_common::StateTransitionDetector -- one detector per component, mirroring Fault's per-component detector map, so components activate and deactivate independently.

A Record is produced only for the boundary Intervention already reports in the same shape: a transition into active starts one ("event": "start"), and a transition out of active ends one ("event": "end"). A transition between two non-active states (e.g. unconfigured to inactive during startup) crosses no boundary and produces nothing.

Every Record carries the component type (controller or hardware_component), its name, the state it left (from_state) and entered (to_state), and how long the previous state had been held (previous_state_duration_s) -- on a start Record, how long the component was out of active before this; on an end Record, how long it had just been active. open is true on a start Record and false on an end Record, so a component still active when collection stops simply never gets a matching end Record and cannot be averaged as a zero. Every Record carries a seq that increments by one across every controller and hardware component, so a dropped Record is a detectable gap.

Parameters

ParameterDescriptionTypeDefault
topicTopic to subscribe to for controller manager activitystr"/controller_manager/activity" (Optional)

Schema

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Ros2ControlStatus",
  "properties": {
    "seq": { "type": "integer", "minimum": 1 },
    "component_type": { "type": "string", "enum": ["controller", "hardware_component"] },
    "component": { "type": "string" },
    "event": { "type": "string", "enum": ["start", "end"] },
    "from_state": { "type": "string", "enum": ["unconfigured", "inactive", "active", "finalized", "configuring", "cleaningup", "shuttingdown", "activating", "deactivating", "errorprocessing", "unknown"] },
    "to_state": { "type": "string", "enum": ["unconfigured", "inactive", "active", "finalized", "configuring", "cleaningup", "shuttingdown", "activating", "deactivating", "errorprocessing", "unknown"] },
    "previous_state_duration_s": { "type": "number", "minimum": 0 },
    "open": { "type": "boolean" }
  },
  "required": ["seq", "component_type", "component", "event", "from_state", "to_state", "previous_state_duration_s", "open"],
  "type": "object"
}

Configuration

...
ros2_control_status:
  plugin: "dc_measurements/Ros2ControlStatus"
  topic_output: "/dc/measurement/ros2_control_status"
  group_key: "ros2_control_status"
  topic: "/controller_manager/activity"

Example output

A start:

{
  "seq": 1,
  "component_type": "controller",
  "component": "diff_drive_controller",
  "event": "start",
  "from_state": "inactive",
  "to_state": "active",
  "previous_state_duration_s": 4.2,
  "open": true
}

and its end:

{
  "seq": 2,
  "component_type": "controller",
  "component": "diff_drive_controller",
  "event": "end",
  "from_state": "active",
  "to_state": "inactive",
  "previous_state_duration_s": 612.9,
  "open": false
}