Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate pal_statistics for introspection of controllers, hardware components and more #1918

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1b503d3
add first integration of pal_statistics
saikishor Nov 17, 2024
aca20e4
namespace the introspection_data to the controller_manager node naming
saikishor Nov 18, 2024
07334de
Add integration into the controller and hardware components base clas…
saikishor Nov 19, 2024
884c080
remove execution time introspection
saikishor Nov 28, 2024
c181b5d
handle cases when the value_ptr_ is invalid
saikishor Nov 28, 2024
a107a99
add CLEAR_ALL_REGISTRIES macro in destructor
saikishor Dec 4, 2024
180b127
disable introspection on cleanup
saikishor Dec 4, 2024
6581204
rename macro to REGISTER_ROS2_CONTROL_INTROSPECTION
saikishor Dec 5, 2024
4eee6ad
update release_notes
saikishor Dec 6, 2024
7e22d6a
add missing header in controller_interface_base.hpp
saikishor Dec 7, 2024
a57b973
Update debugging docs
saikishor Dec 7, 2024
f2c4e0e
add more documentation about the topics
saikishor Dec 9, 2024
719c747
delete the move constructors
saikishor Dec 9, 2024
6ed4be6
Add macro with also enable argument
saikishor Dec 9, 2024
81c0277
place dependencies alphabetically
saikishor Dec 9, 2024
10fe456
Update doc/debugging.rst
saikishor Dec 9, 2024
870da8a
move the documentation to introspection.rst
saikishor Dec 9, 2024
612158d
update docs on the third argument to enable introspection
saikishor Dec 9, 2024
d4923ae
use clang jobs from christoph
saikishor Dec 9, 2024
447a273
Revert "Add macro with also enable argument"
saikishor Dec 9, 2024
bc04f2a
Update doc/introspection.rst
saikishor Dec 9, 2024
aa19ef9
Use the master CI for clang
saikishor Dec 17, 2024
3838f45
remove the visibility_macro on get_name method
saikishor Dec 31, 2024
f201856
return std::string instead of const ref as char * are returned by lif…
saikishor Dec 31, 2024
fb80164
Apply suggestions from code review
saikishor Jan 1, 2025
5f91488
Add changes from Denis review comments
saikishor Jan 1, 2025
d398ea7
Merge branch 'master' into integrate/pal_statistics
saikishor Jan 9, 2025
b72f313
Merge branch 'master' into integrate/pal_statistics
saikishor Jan 15, 2025
c0df59d
Merge branch 'master' into integrate/pal_statistics
saikishor Jan 15, 2025
0904ee1
Merge branch 'master' into integrate/pal_statistics
saikishor Jan 17, 2025
877ce1e
Merge branch 'master' into integrate/pal_statistics
saikishor Jan 20, 2025
a89b26e
Merge branch 'master' into integrate/pal_statistics
saikishor Jan 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "hardware_interface/loaned_command_interface.hpp"
#include "hardware_interface/loaned_state_interface.hpp"

#include "pal_statistics/pal_statistics_utils.hpp"
saikishor marked this conversation as resolved.
Show resolved Hide resolved
#include "rclcpp/version.h"
#include "rclcpp_lifecycle/lifecycle_node.hpp"

Expand Down Expand Up @@ -305,6 +306,14 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
*/
void wait_for_trigger_update_to_finish();

std::string get_name() const;

/// Enable or disable introspection of the controller.
/**
* \param[in] enable Enable introspection if true, disable otherwise.
*/
void enable_introspection(bool enable);

protected:
std::vector<hardware_interface::LoanedCommandInterface> command_interfaces_;
std::vector<hardware_interface::LoanedStateInterface> state_interfaces_;
Expand All @@ -316,6 +325,9 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
bool is_async_ = false;
std::string urdf_ = "";
ControllerUpdateStats trigger_stats_;

protected:
pal_statistics::RegistrationsRAII stats_registrations_;
};

using ControllerInterfaceBaseSharedPtr = std::shared_ptr<ControllerInterfaceBase>;
Expand Down
26 changes: 25 additions & 1 deletion controller_interface/src/controller_interface_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <string>
#include <vector>

#include "hardware_interface/introspection.hpp"
#include "lifecycle_msgs/msg/state.hpp"

namespace controller_interface
Expand Down Expand Up @@ -61,6 +62,7 @@ return_type ControllerInterfaceBase::init(
node_->register_on_cleanup(
[this](const rclcpp_lifecycle::State & previous_state) -> CallbackReturn
{
enable_introspection(false);
saikishor marked this conversation as resolved.
Show resolved Hide resolved
if (is_async() && async_handler_ && async_handler_->is_running())
{
async_handler_->stop_thread();
Expand All @@ -71,6 +73,7 @@ return_type ControllerInterfaceBase::init(
node_->register_on_activate(
[this](const rclcpp_lifecycle::State & previous_state) -> CallbackReturn
{
enable_introspection(true);
destogl marked this conversation as resolved.
Show resolved Hide resolved
if (is_async() && async_handler_ && async_handler_->is_running())
{
// This is needed if it is disabled due to a thrown exception in the async callback thread
Expand All @@ -80,7 +83,11 @@ return_type ControllerInterfaceBase::init(
});

node_->register_on_deactivate(
std::bind(&ControllerInterfaceBase::on_deactivate, this, std::placeholders::_1));
[this](const rclcpp_lifecycle::State & previous_state) -> CallbackReturn
{
enable_introspection(false);
return on_deactivate(previous_state);
});

node_->register_on_shutdown(
std::bind(&ControllerInterfaceBase::on_shutdown, this, std::placeholders::_1));
Expand Down Expand Up @@ -137,6 +144,8 @@ const rclcpp_lifecycle::State & ControllerInterfaceBase::configure()
thread_priority);
async_handler_->start_thread();
}
REGISTER_ROS2_CONTROL_INTROSPECTION("total_triggers", &trigger_stats_.total_triggers);
REGISTER_ROS2_CONTROL_INTROSPECTION("failed_triggers", &trigger_stats_.failed_triggers);
trigger_stats_.reset();

return get_node()->configure();
Expand Down Expand Up @@ -233,4 +242,19 @@ void ControllerInterfaceBase::wait_for_trigger_update_to_finish()
async_handler_->wait_for_trigger_cycle_to_finish();
}
}

std::string ControllerInterfaceBase::get_name() const { return get_node()->get_name(); }
saikishor marked this conversation as resolved.
Show resolved Hide resolved

void ControllerInterfaceBase::enable_introspection(bool enable)
{
if (enable)
{
stats_registrations_.enableAll();
}
else
{
stats_registrations_.disableAll();
}
}

} // namespace controller_interface
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class ControllerManager : public rclcpp::Node
const std::string & node_namespace = "",
const rclcpp::NodeOptions & options = get_cm_node_options());

virtual ~ControllerManager() = default;
virtual ~ControllerManager();

void robot_description_callback(const std_msgs::msg::String & msg);

Expand Down
9 changes: 9 additions & 0 deletions controller_manager/src/controller_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include "controller_interface/controller_interface_base.hpp"
#include "controller_manager_msgs/msg/hardware_component_state.hpp"
#include "hardware_interface/introspection.hpp"
#include "hardware_interface/types/lifecycle_state_names.hpp"
#include "lifecycle_msgs/msg/state.hpp"
#include "rcl/arguments.h"
Expand Down Expand Up @@ -283,6 +284,8 @@ ControllerManager::ControllerManager(
init_controller_manager();
}

ControllerManager::~ControllerManager() { CLEAR_ALL_REGISTRIES(); }
saikishor marked this conversation as resolved.
Show resolved Hide resolved

void ControllerManager::init_controller_manager()
{
// Get parameters needed for RT "update" loop to work
Expand Down Expand Up @@ -321,6 +324,10 @@ void ControllerManager::init_controller_manager()
diagnostics_updater_.add(
"Controller Manager Activity", this,
&ControllerManager::controller_manager_diagnostic_callback);
INITIALIZE_REGISTRY(
saikishor marked this conversation as resolved.
Show resolved Hide resolved
this, hardware_interface::DEFAULT_INTROSPECTION_TOPIC,
hardware_interface::DEFAULT_REGISTRY_KEY);
START_PUBLISH_THREAD(hardware_interface::DEFAULT_REGISTRY_KEY);
}

void ControllerManager::initialize_parameters()
Expand Down Expand Up @@ -2605,6 +2612,8 @@ controller_interface::return_type ControllerManager::update(
manage_switch();
}

PUBLISH_ASYNC_STATISTICS(hardware_interface::DEFAULT_REGISTRY_KEY);

return ret;
}

Expand Down
Binary file added doc/images/plotjuggler.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/plotjuggler_select_topics.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/plotjuggler_visualizing_data.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ Guidelines and Best Practices
:titlesonly:

Debugging the Controller Manager and Plugins <debugging.rst>
Introspecting Controllers and Hardware Components <introspection.rst>
81 changes: 81 additions & 0 deletions doc/introspection.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@

Introspection of the ros2_control setup
***************************************

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.
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``.
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.
saikishor marked this conversation as resolved.
Show resolved Hide resolved

All the registered variables are published over 3 topics: ``~/introspection_data/full``, ``~/introspection_data/names``, and ``~/introspection_data/values``.
- 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.
- 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.
- The ``~/introspection_data/values`` topic publishes the values of the registered variables. This topic contains the values of the variables registered.

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.

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/>`_ to visualize the data.
saikishor marked this conversation as resolved.
Show resolved Hide resolved

.. note::
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.

How to introspect internal variables of controllers and hardware components
============================================================================

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.

.. note::
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.

How to register a variable for introspection
---------------------------------------------

1. Include the necessary headers in the controller or hardware component header file.

.. code-block:: cpp

#include <hardware_interface/introspection.hpp>

2. Register the variable in the configure method of the controller or hardware component.

.. code-block:: cpp

void MyController::on_configure()
{
...
// Register the variable for introspection (disabled by default)
// The variable is introspected only when the controller is active and
// then deactivated when the controller is deactivated.
REGISTER_ROS2_CONTROL_INTROSPECTION("my_variable_name", &my_variable_);
destogl marked this conversation as resolved.
Show resolved Hide resolved
...
}

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.

.. note::
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.
saikishor marked this conversation as resolved.
Show resolved Hide resolved
saikishor marked this conversation as resolved.
Show resolved Hide resolved

Types of entities that can be introspected
-------------------------------------------

- Any variable that can be cast to a double is suitable for registration.
- A function that returns a value that can be cast to a double is also suitable for registration.
- Variables of complex structures can be registered by having defined introspection for its every internal variable.
saikishor marked this conversation as resolved.
Show resolved Hide resolved
- 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>`_.

.. note::
Registering the variables for introspection is not real-time safe. It is recommended to register the variables in the ``on_configure`` method only.

Data Visualization
*******************

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.

1. Open ``PlotJuggler`` running ``ros2 run plotjuggler plotjuggler``.
.. image:: images/plotjuggler.png
2. Visualize the data:
- Importing from the ros2bag
- Subscribing to the ROS2 topics live with the ``ROS2 Topic Subscriber`` option under ``Streaming`` header.
3. Choose the topics ``~/introspection_data/names`` and ``~/introspection_data/values`` from the popup window.
.. image:: images/plotjuggler_select_topics.png
4. Now, select the variables that are of your interest and drag them to the plot.
.. image:: images/plotjuggler_visualizing_data.png
3 changes: 3 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ For details see the controller_manager section.
* 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>`_)
* The new ``PoseSensor`` semantic component provides a standard interface for hardware providing cartesian poses (`#1775 <https://github.com/ros-controls/ros2_control/pull/1775>`_)
* 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>`_)
* 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>`_)

controller_manager
******************
Expand Down Expand Up @@ -85,6 +86,7 @@ controller_manager
* 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>`_).
* 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>`_).
* 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>`_).
* 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>`_).
* 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>`_).

hardware_interface
Expand Down Expand Up @@ -159,6 +161,7 @@ hardware_interface
* 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.
* With (`#1763 <https://github.com/ros-controls/ros2_control/pull/1763>`_) parsing for SDF published to ``robot_description`` topic is now also supported.
* 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.
* 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>`_)

joint_limits
************
Expand Down
1 change: 1 addition & 0 deletions hardware_interface/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS
tinyxml2_vendor
joint_limits
urdf
pal_statistics
)

find_package(ament_cmake REQUIRED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "hardware_interface/types/lifecycle_state_names.hpp"
#include "hardware_interface/types/trigger_type.hpp"
#include "lifecycle_msgs/msg/state.hpp"
#include "pal_statistics/pal_statistics_utils.hpp"
#include "rclcpp/duration.hpp"
#include "rclcpp/logger.hpp"
#include "rclcpp/node_interfaces/node_clock_interface.hpp"
Expand Down Expand Up @@ -93,7 +94,7 @@ class ActuatorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNod
*/
ActuatorInterface(const ActuatorInterface & other) = delete;

ActuatorInterface(ActuatorInterface && other) = default;
ActuatorInterface(ActuatorInterface && other) = delete;
destogl marked this conversation as resolved.
Show resolved Hide resolved

virtual ~ActuatorInterface() = default;

Expand Down Expand Up @@ -522,6 +523,22 @@ class ActuatorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNod
*/
const HardwareInfo & get_hardware_info() const { return info_; }

/// Enable or disable introspection of the hardware.
/**
* \param[in] enable Enable introspection if true, disable otherwise.
*/
void enable_introspection(bool enable)
{
if (enable)
{
stats_registrations_.enableAll();
}
else
{
stats_registrations_.disableAll();
}
}

protected:
HardwareInfo info_;
// interface names to InterfaceDescription
Expand All @@ -548,6 +565,9 @@ class ActuatorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNod
std::unordered_map<std::string, StateInterface::SharedPtr> actuator_states_;
std::unordered_map<std::string, CommandInterface::SharedPtr> actuator_commands_;
std::atomic<TriggerType> next_trigger_ = TriggerType::READ;

protected:
pal_statistics::RegistrationsRAII stats_registrations_;
};

} // namespace hardware_interface
Expand Down
41 changes: 41 additions & 0 deletions hardware_interface/include/hardware_interface/handle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <variant>

#include "hardware_interface/hardware_info.hpp"
#include "hardware_interface/introspection.hpp"
#include "hardware_interface/macros.hpp"

namespace hardware_interface
Expand Down Expand Up @@ -207,6 +208,24 @@ class StateInterface : public Handle
{
}

void registerIntrospection() const
{
if (std::holds_alternative<double>(value_))
{
std::function<double()> f = [this]()
{ return value_ptr_ ? *value_ptr_ : std::numeric_limits<double>::quiet_NaN(); };
REGISTER_ENTITY(DEFAULT_REGISTRY_KEY, "state_interface." + get_name(), f);
}
}

void unregisterIntrospection() const
{
if (std::holds_alternative<double>(value_))
{
UNREGISTER_ENTITY(DEFAULT_REGISTRY_KEY, "state_interface." + get_name());
}
}

StateInterface(const StateInterface & other) = default;

StateInterface(StateInterface && other) = default;
Expand Down Expand Up @@ -234,6 +253,28 @@ class CommandInterface : public Handle

CommandInterface(CommandInterface && other) = default;

void registerIntrospection() const
{
if (std::holds_alternative<double>(value_))
{
RCLCPP_INFO_STREAM(
saikishor marked this conversation as resolved.
Show resolved Hide resolved
rclcpp::get_logger("command_interface"), "Registering handle: " << get_name());
std::function<double()> f = [this]()
{ return value_ptr_ ? *value_ptr_ : std::numeric_limits<double>::quiet_NaN(); };
REGISTER_ENTITY(DEFAULT_REGISTRY_KEY, "command_interface." + get_name(), f);
saikishor marked this conversation as resolved.
Show resolved Hide resolved
}
}

void unregisterIntrospection() const
{
if (std::holds_alternative<double>(value_))
{
RCLCPP_INFO_STREAM(
saikishor marked this conversation as resolved.
Show resolved Hide resolved
rclcpp::get_logger("command_interface"), "Unregistering handle: " << get_name());
UNREGISTER_ENTITY(DEFAULT_REGISTRY_KEY, "command_interface." + get_name());
saikishor marked this conversation as resolved.
Show resolved Hide resolved
}
}

using Handle::Handle;

using SharedPtr = std::shared_ptr<CommandInterface>;
Expand Down
Loading
Loading