General information about this repository, including legal information and known issues/limitations, are given in README.md in the repository root.
This package contains the aggregator_node
.
It listens to the diagnostic_msgs/DiagnosticArray
messages on the /diagnostics
topic and aggregates and published them on the /diagnostics_agg
topic.
One use case for this package is to aggregate the diagnostics of a robot. Aggregation means that the diagnostics of the robot are grouped by various aspects, like their location on the robot, their type, etc. This will allow you to easily see which part of the robot is causing problems.
In our example, we are looking at a robot with arms and legs. The robot has two of each, one on each side. The robot also 4 camera sensors, one left and one right and one in the front and one in the back. These are all the available diagnostic sources:
/arms/left/motor
/arms/right/motor
/legs/left/motor
/legs/right/motor
/sensors/left/cam
/sensors/right/cam
/sensors/front/cam
/sensors/rear/cam
We want to group the diagnostics by
- all sensors
- all motors
- left side of the robot
- right side of the robot
We can achieve that by creating a configuration file that looks like this (see example_analyzers.yaml):
analyzers:
ros__parameters:
path: Aggregation
arms:
type: diagnostic_aggregator/GenericAnalyzer
path: Arms
startswith: [ '/arms' ]
legs:
type: diagnostic_aggregator/GenericAnalyzer
path: Legs
startswith: [ '/legs' ]
sensors:
type: diagnostic_aggregator/GenericAnalyzer
path: Sensors
startswith: [ '/sensors' ]
motors:
type: diagnostic_aggregator/GenericAnalyzer
path: Motors
contains: [ '/motor' ]
topology:
type: 'diagnostic_aggregator/AnalyzerGroup'
path: Topology
analyzers:
left:
type: diagnostic_aggregator/GenericAnalyzer
path: Left
contains: [ '/left' ]
right:
type: diagnostic_aggregator/GenericAnalyzer
path: Right
contains: [ '/right' ]
Based on this configuration, the rqt_robot_monitor will display the diagnostics information in a well-arranged manner as follows:
Note that it will also display the highest state per group to allow you to see at a glance which part of the robot is not working properly.
For example in the above image, the left side of the robot is not working properly, because the left camera is in the ERROR
state.
The aggregator_node
will load analyzers to process the diagnostics data.
An analyzer is a plugin that inherits from the diagnostic_aggregator::Analyzer
class.
Analyzers must be implemented in packages that directly depend on pluginlib
and diagnostic_aggregator
.
The diagnostic_aggregator::Analyzer
class is purely virtual and derived classes must implement the following methods:
init()
- Analyzer is initialized with base path and namespacematch()
- Returns true if the analyzer is interested in the status messageanalyze()
- Returns true if the analyzer will analyze the status messagereport()
- Returns results of analysis as vector of status messagesgetPath()
- Returns the prefix path of the analyzer (e.g., "/robot/motors/")getName()
- Returns the name of the analyzer (e.g., "Motors")
Analyzers can choose the value of the error level of their output. Usually, the error level of the output is the highest error level of the input. The analyzers are responsible for setting the name of each item in the output correctly.
The aggregator_node
can be configured at launch time like in this example:
pub_rate: 1.0 # Optional, defaults to 1.0
base_path: 'PRE' # Optional, defaults to ""
analyzers:
motors:
type: PR2MotorsAnalyzer
joints:
type: GenericAnalyzer
path: 'Joints'
regex: 'Joint*'
The pub_rate
parameter is the rate at which the aggregated diagnostics will be published.
The base_path
parameter is the prefix that will be added to the name of each item in the output.
Under the analyzers
key, you can specify the analyzers that you want to use.
Each analyzer must have a unique name.
Under the name, you must specify the type of the analyzer.
This must be the name of the class that implements the analyzer.
Additional parameters depend on the type of the analyzer.
Any diagnostic item that is not matched by any analyzer will be published by an "Other" analyzer. Items created by the "Other" analyzer will go stale after 5 seconds.
The critical
parameter makes the aggregator react immediately to a degradation in diagnostic state.
This is useful if the toplevel state is parsed by a watchdog for example.
You can launch the aggregator_node
like this (see example.launch.py.in):
aggregator = launch_ros.actions.Node(
package='diagnostic_aggregator',
executable='aggregator_node',
output='screen',
parameters=[analyzer_params_filepath])
return launch.LaunchDescription([
aggregator,
])
You can add analyzers at runtime using the add_analyzer
node like this (see example.launch.py.in):
add_analyzer = launch_ros.actions.Node(
package='diagnostic_aggregator',
executable='add_analyzer',
output='screen',
parameters=[add_analyzer_params_filepath])
return launch.LaunchDescription([
add_analyzer,
])
This node updates the parameters of the aggregator_node
by calling the service /analyzers/set_parameters_atomically
.
The aggregator_node
will detect when a parameter-event
has introduced new parameters to it.
When this happens the aggregator_node
will reload all analyzers based on its new set of parameters.
Adding analyzers this way can be done at runtime and can be made conditional.
In the example, add_analyzer
will add an analyzer for diagnostics that are marked optional:
/**:
ros__parameters:
optional:
type: diagnostic_aggregator/GenericAnalyzer
path: Optional
startswith: [ '/optional' ]
This will move the /optional/runtime/analyzer
diagnostic from the "Other" to "Aggregation" where it will not go stale after 5 seconds and will be taken into account for the toplevel state.
The diagnostic_aggregator
package provides a few basic analyzers that you can use to aggregate your diagnostics.
The diagnostic_aggregator::GenericAnalyzer
class is a basic analyzer that can be configured to match diagnostics based on their name.
By defining a path
parameter, you can specify the prefix that will be added to the name of each item in the output.
This way you can group diagnostics by their location or other aspects as demonstrated in the example.
The diagnostic_aggregator::AnalyzerGroup
class is a basic analyzer that can be configured to group other analyzers.
It has itself an analyzers
parameter that can be filled with other analyzers to group them.
An example of this is (see example_analyzers.yaml):
topology:
type: 'diagnostic_aggregator/AnalyzerGroup'
path: Topology
analyzers:
left:
type: diagnostic_aggregator/GenericAnalyzer
path: Left
contains: [ '/left' ]
right:
type: diagnostic_aggregator/GenericAnalyzer
path: Right
contains: [ '/right' ]
The diagnostic_aggregator::DiscardAnalyzer
class is a basic analyzer that discards all diagnostics that match it.
This can be useful if you want to ignore some diagnostics.
The diagnostic_aggregator::IgnoreAnalyzer
will ignore all parameters in its namespace and not match anything.
The difference between the DiscardAnalyzer
and the IgnoreAnalyzer
is that the DiscardAnalyzer
will match diagnostics and discard them, while the IgnoreAnalyzer
will not match anything.
This means that things that are ignored by the IgnoreAnalyzer
will still be published in the "Other" analyzer, while things that are discarded by the DiscardAnalyzer
will not be published at all.
diagnostics
(diagnostic_msgs/DiagnosticArray) - The diagnostics to be aggregated
diagnostics_agg
(diagnostic_msgs/DiagnosticArray) - The aggregated diagnosticsdiagnostics_toplevel_state
(diagnostic_msgs/DiagnosticStatus) - The highest state of the aggregated diagnostics
pub_rate
(double, default: 1.0) - The rate at which the aggregated diagnostics will be publishedbase_path
(string, default: "") - The prefix that will be added to the name of each item in the outputanalyzers
(map, default: {}) - The analyzers that will be used to aggregate the diagnostics
TODO: Port tutorials #contributions-welcome