Skip to content

Commit 254b6e8

Browse files
authored
Integrate pal_statistics for introspection of controllers, hardware components and more (#1918)
1 parent 675a7a9 commit 254b6e8

20 files changed

+310
-6
lines changed

controller_interface/include/controller_interface/controller_interface_base.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "realtime_tools/async_function_handler.hpp"
2424

2525
#include "hardware_interface/handle.hpp"
26+
#include "hardware_interface/introspection.hpp"
2627
#include "hardware_interface/loaned_command_interface.hpp"
2728
#include "hardware_interface/loaned_state_interface.hpp"
2829

@@ -305,6 +306,14 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
305306
*/
306307
void wait_for_trigger_update_to_finish();
307308

309+
std::string get_name() const;
310+
311+
/// Enable or disable introspection of the controller.
312+
/**
313+
* \param[in] enable Enable introspection if true, disable otherwise.
314+
*/
315+
void enable_introspection(bool enable);
316+
308317
protected:
309318
std::vector<hardware_interface::LoanedCommandInterface> command_interfaces_;
310319
std::vector<hardware_interface::LoanedStateInterface> state_interfaces_;
@@ -316,6 +325,9 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
316325
bool is_async_ = false;
317326
std::string urdf_ = "";
318327
ControllerUpdateStats trigger_stats_;
328+
329+
protected:
330+
pal_statistics::RegistrationsRAII stats_registrations_;
319331
};
320332

321333
using ControllerInterfaceBaseSharedPtr = std::shared_ptr<ControllerInterfaceBase>;

controller_interface/src/controller_interface_base.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <string>
1919
#include <vector>
2020

21+
#include "hardware_interface/introspection.hpp"
2122
#include "lifecycle_msgs/msg/state.hpp"
2223

2324
namespace controller_interface
@@ -82,6 +83,9 @@ return_type ControllerInterfaceBase::init(
8283
node_->register_on_cleanup(
8384
[this](const rclcpp_lifecycle::State & previous_state) -> CallbackReturn
8485
{
86+
// make sure introspection is disabled on controller cleanup as users may manually enable
87+
// it in `on_configure` and `on_deactivate` - see the docs for details
88+
enable_introspection(false);
8589
if (is_async() && async_handler_ && async_handler_->is_running())
8690
{
8791
async_handler_->stop_thread();
@@ -92,6 +96,7 @@ return_type ControllerInterfaceBase::init(
9296
node_->register_on_activate(
9397
[this](const rclcpp_lifecycle::State & previous_state) -> CallbackReturn
9498
{
99+
enable_introspection(true);
95100
if (is_async() && async_handler_ && async_handler_->is_running())
96101
{
97102
// This is needed if it is disabled due to a thrown exception in the async callback thread
@@ -101,7 +106,11 @@ return_type ControllerInterfaceBase::init(
101106
});
102107

103108
node_->register_on_deactivate(
104-
std::bind(&ControllerInterfaceBase::on_deactivate, this, std::placeholders::_1));
109+
[this](const rclcpp_lifecycle::State & previous_state) -> CallbackReturn
110+
{
111+
enable_introspection(false);
112+
return on_deactivate(previous_state);
113+
});
105114

106115
node_->register_on_shutdown(
107116
std::bind(&ControllerInterfaceBase::on_shutdown, this, std::placeholders::_1));
@@ -158,6 +167,8 @@ const rclcpp_lifecycle::State & ControllerInterfaceBase::configure()
158167
thread_priority);
159168
async_handler_->start_thread();
160169
}
170+
REGISTER_ROS2_CONTROL_INTROSPECTION("total_triggers", &trigger_stats_.total_triggers);
171+
REGISTER_ROS2_CONTROL_INTROSPECTION("failed_triggers", &trigger_stats_.failed_triggers);
161172
trigger_stats_.reset();
162173

163174
return get_node()->configure();
@@ -258,4 +269,19 @@ void ControllerInterfaceBase::wait_for_trigger_update_to_finish()
258269
async_handler_->wait_for_trigger_cycle_to_finish();
259270
}
260271
}
272+
273+
std::string ControllerInterfaceBase::get_name() const { return get_node()->get_name(); }
274+
275+
void ControllerInterfaceBase::enable_introspection(bool enable)
276+
{
277+
if (enable)
278+
{
279+
stats_registrations_.enableAll();
280+
}
281+
else
282+
{
283+
stats_registrations_.disableAll();
284+
}
285+
}
286+
261287
} // namespace controller_interface

controller_manager/src/controller_manager.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#include "controller_interface/controller_interface_base.hpp"
2424
#include "controller_manager_msgs/msg/hardware_component_state.hpp"
25+
#include "hardware_interface/introspection.hpp"
2526
#include "hardware_interface/types/lifecycle_state_names.hpp"
2627
#include "lifecycle_msgs/msg/state.hpp"
2728
#include "rcl/arguments.h"
@@ -331,6 +332,7 @@ ControllerManager::ControllerManager(
331332

332333
ControllerManager::~ControllerManager()
333334
{
335+
CLEAR_ALL_ROS2_CONTROL_INTROSPECTION_REGISTRIES();
334336
if (preshutdown_cb_handle_)
335337
{
336338
rclcpp::Context::SharedPtr context = this->get_node_base_interface()->get_context();
@@ -408,6 +410,11 @@ void ControllerManager::init_controller_manager()
408410
"Controller Manager Activity", this,
409411
&ControllerManager::controller_manager_diagnostic_callback);
410412

413+
INITIALIZE_ROS2_CONTROL_INTROSPECTION_REGISTRY(
414+
this, hardware_interface::DEFAULT_INTROSPECTION_TOPIC,
415+
hardware_interface::DEFAULT_REGISTRY_KEY);
416+
START_ROS2_CONTROL_INTROSPECTION_PUBLISHER_THREAD(hardware_interface::DEFAULT_REGISTRY_KEY);
417+
411418
// Add on_shutdown callback to stop the controller manager
412419
rclcpp::Context::SharedPtr context = this->get_node_base_interface()->get_context();
413420
preshutdown_cb_handle_ =
@@ -2721,6 +2728,8 @@ controller_interface::return_type ControllerManager::update(
27212728
manage_switch();
27222729
}
27232730

2731+
PUBLISH_ROS2_CONTROL_INTROSPECTION_DATA_ASYNC(hardware_interface::DEFAULT_REGISTRY_KEY);
2732+
27242733
return ret;
27252734
}
27262735

doc/images/plotjuggler.png

92.4 KB
Loading
126 KB
Loading
184 KB
Loading

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ Guidelines and Best Practices
3737
:titlesonly:
3838

3939
Debugging the Controller Manager and Plugins <debugging.rst>
40+
Introspecting Controllers and Hardware Components <introspection.rst>

doc/introspection.rst

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
2+
Introspection of the ros2_control setup
3+
***************************************
4+
5+
With the integration of the ``pal_statistics`` package, the ``controller_manager`` node publishes the registered variables within the same process to the ``~/introspection_data`` topics.
6+
By default, all ``State`` and ``Command`` interfaces in the ``controller_manager`` are registered when they are added, and are unregistered when they are removed from the ``ResourceManager``.
7+
The state of the all the registered entities are published at the end of every ``update`` cycle of the ``controller_manager``. For instance, In a complete synchronous ros2_control setup (with synchronous controllers and hardware components), this data in the ``Command`` interface is the command used by the hardware components to command the hardware.
8+
9+
All the registered variables are published over 3 topics: ``~/introspection_data/full``, ``~/introspection_data/names``, and ``~/introspection_data/values``.
10+
- The ``~/introspection_data/full`` topic publishes the full introspection data along with names and values in a single message. This can be useful to track or view variables and information from command line.
11+
- The ``~/introspection_data/names`` topic publishes the names of the registered variables. This topic contains the names of the variables registered. This is only published every time a a variables is registered and unregistered.
12+
- The ``~/introspection_data/values`` topic publishes the values of the registered variables. This topic contains the values of the variables registered.
13+
14+
The topics ``~/introspection_data/full`` and ``~/introspection_data/values`` are always published on every update cycle asynchronously, provided that there is at least one subscriber to these topics.
15+
16+
The topic ``~/introspection_data/full`` can be used to integrate with your custom visualization tools or to track the variables from the command line. The topic ``~/introspection_data/names`` and ``~/introspection_data/values`` are to be used for visualization tools like `PlotJuggler <https://plotjuggler.io/>`_ or `RQT plot <http://wiki.ros.org/rqt_plot>`_ to visualize the data.
17+
18+
.. note::
19+
If you have a high frequency of data, it is recommended to use the ``~/introspection_data/names`` and ``~/introspection_data/values`` topic. So, that the data transferred and stored is minimized.
20+
21+
How to introspect internal variables of controllers and hardware components
22+
============================================================================
23+
24+
Any member variable of a controller or hardware component can be registered for the introspection. It is very important that the lifetime of this variable exists as long as the controller or hardware component is available.
25+
26+
.. note::
27+
If a variable's lifetime is not properly managed, it may be attempted to read, which in the worst case scenario will cause a segmentation fault.
28+
29+
How to register a variable for introspection
30+
---------------------------------------------
31+
32+
1. Include the necessary headers in the controller or hardware component header file.
33+
34+
.. code-block:: cpp
35+
36+
#include <hardware_interface/introspection.hpp>
37+
38+
2. Register the variable in the configure method of the controller or hardware component.
39+
40+
.. code-block:: cpp
41+
42+
void MyController::on_configure()
43+
{
44+
...
45+
// Register the variable for introspection (disabled by default)
46+
// The variable is introspected only when the controller is active and
47+
// then deactivated when the controller is deactivated.
48+
REGISTER_ROS2_CONTROL_INTROSPECTION("my_variable_name", &my_variable_);
49+
...
50+
}
51+
52+
3. By default, the introspection of all the registered variables of the controllers and the hardware components is only activated, when they are active and it is deactivated when the controller or hardware component is deactivated.
53+
54+
.. note::
55+
If you want to keep the introspection active even when the controller or hardware component is not active, you can do that by calling ``this->enable_introspection(true)`` in the ``on_configure`` and ``on_deactivate`` method of the controller or hardware component after registering the variables.
56+
57+
Types of entities that can be introspected
58+
-------------------------------------------
59+
60+
- Any variable that can be cast to a double is suitable for registration.
61+
- A function that returns a value that can be cast to a double is also suitable for registration.
62+
- Variables of complex structures can be registered by having defined introspection for their every internal variable.
63+
- Introspection of custom types can be done by defining a `custom introspection function <https://github.com/pal-robotics/pal_statistics/blob/humble-devel/pal_statistics/include/pal_statistics/registration_utils.hpp>`_.
64+
65+
.. note::
66+
Registering the variables for introspection is not real-time safe. It is recommended to register the variables in the ``on_configure`` method only.
67+
68+
Data Visualization
69+
*******************
70+
71+
Data can be visualized with any tools that display ROS topics, but we recommend `PlotJuggler <https://plotjuggler.io/>`_ for viewing high resolution live data, or data in bags.
72+
73+
1. Open ``PlotJuggler`` running ``ros2 run plotjuggler plotjuggler``.
74+
.. image:: images/plotjuggler.png
75+
2. Visualize the data:
76+
- Importing from the ros2bag
77+
- Subscribing to the ROS2 topics live with the ``ROS2 Topic Subscriber`` option under ``Streaming`` header.
78+
3. Choose the topics ``~/introspection_data/names`` and ``~/introspection_data/values`` from the popup window.
79+
.. image:: images/plotjuggler_select_topics.png
80+
4. Now, select the variables that are of your interest and drag them to the plot.
81+
.. image:: images/plotjuggler_visualizing_data.png

doc/release_notes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ For details see the controller_manager section.
2727
* The ``assign_interfaces`` and ``release_interfaces`` methods are now virtual, so that the user can override them to store the interfaces into custom variable types, so that the user can have the flexibility to take the ownership of the loaned interfaces to the controller (`#1743 <https://github.com/ros-controls/ros2_control/pull/1743>`_)
2828
* The new ``PoseSensor`` semantic component provides a standard interface for hardware providing cartesian poses (`#1775 <https://github.com/ros-controls/ros2_control/pull/1775>`_)
2929
* The controllers now support the fallback controllers (a list of controllers that will be activated, when the spawned controllers fails by throwing an exception or returning ``return_type::ERROR`` during the ``update`` cycle) (`#1789 <https://github.com/ros-controls/ros2_control/pull/1789>`_)
30+
* The controllers can be easily introspect the internal member variables using the macro ``REGISTER_ROS2_CONTROL_INTROSPECTION`` (`#1918 <https://github.com/ros-controls/ros2_control/pull/1918>`_)
3031
* A new ``SemanticComponentCommandInterface`` semantic component provides capabilities analogous to the ``SemanticComponentInterface``, but for write-only devices (`#1945 <https://github.com/ros-controls/ros2_control/pull/1945>`_)
3132
* The new semantic command interface ``LedRgbDevice`` provides standard (command) interfaces for 3-channel LED devices (`#1945 <https://github.com/ros-controls/ros2_control/pull/1945>`_)
3233

@@ -87,6 +88,7 @@ controller_manager
8788
* The ``ros2_control_node`` node has a new ``cpu_affinity`` parameter to bind the process to a specific CPU core. By default, this is not enabled. (`#1852 <https://github.com/ros-controls/ros2_control/pull/1852>`_).
8889
* The ``--service-call-timeout`` was added as parameter to the helper scripts ``spawner.py``. Useful when the CPU load is high at startup and the service call does not return immediately (`#1808 <https://github.com/ros-controls/ros2_control/pull/1808>`_).
8990
* The ``cpu_affinity`` parameter can now accept of types ``int`` or ``int_array`` to bind the process to a specific CPU core or multiple CPU cores. (`#1915 <https://github.com/ros-controls/ros2_control/pull/1915>`_).
91+
* The ``pal_statistics`` is now integrated into the controller_manager, so that the controllers, hardware components and the controller_manager can be easily introspected and monitored using the topics ``~/introspection_data/names`` and ``~/introspection_data/values`` (`#1918 <https://github.com/ros-controls/ros2_control/pull/1918>`_).
9092
* A python module ``test_utils`` was added to the ``controller_manager`` package to help with integration testing (`#1955 <https://github.com/ros-controls/ros2_control/pull/1955>`_).
9193

9294
hardware_interface
@@ -161,6 +163,7 @@ hardware_interface
161163
* With (`#1421 <https://github.com/ros-controls/ros2_control/pull/1421>`_) a key-value storage is added to InterfaceInfo. This allows to define extra params with per Command-/StateInterface in the ``.ros2_control.xacro`` file.
162164
* With (`#1763 <https://github.com/ros-controls/ros2_control/pull/1763>`_) parsing for SDF published to ``robot_description`` topic is now also supported.
163165
* With (`#1567 <https://github.com/ros-controls/ros2_control/pull/1567>`_) all the Hardware components now have a fully functional asynchronous functionality, by simply adding ``is_async`` tag to the ros2_control tag in the URDF. This will allow the hardware components to run in a separate thread, and the controller manager will be able to run the controllers in parallel with the hardware components.
166+
* The hardware components can be easily introspect the internal member variables using the macro ``REGISTER_ROS2_CONTROL_INTROSPECTION`` (`#1918 <https://github.com/ros-controls/ros2_control/pull/1918>`_)
164167

165168
joint_limits
166169
************

hardware_interface/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS
2222
tinyxml2_vendor
2323
joint_limits
2424
urdf
25+
pal_statistics
2526
)
2627

2728
find_package(ament_cmake REQUIRED)

0 commit comments

Comments
 (0)