Skip to content

Commit 6cc89ca

Browse files
Re-apply ros2#829: Add ParameterEventsSubscriber class (ros2#1573)
Also add the following fixes for CI: * Fix symbol visibility error on Windows * Remove an unused parameter to quiet a clang-tidy warning on MacOS Signed-off-by: Michael Jeronimo <[email protected]>
1 parent c767f0b commit 6cc89ca

File tree

6 files changed

+1006
-2
lines changed

6 files changed

+1006
-2
lines changed

rclcpp/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ set(${PROJECT_NAME}_SRCS
8080
src/rclcpp/parameter.cpp
8181
src/rclcpp/parameter_value.cpp
8282
src/rclcpp/parameter_client.cpp
83+
src/rclcpp/parameter_event_handler.cpp
8384
src/rclcpp/parameter_events_filter.cpp
8485
src/rclcpp/parameter_map.cpp
8586
src/rclcpp/parameter_service.cpp
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
// Copyright 2019 Intel Corporation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef RCLCPP__PARAMETER_EVENT_HANDLER_HPP_
16+
#define RCLCPP__PARAMETER_EVENT_HANDLER_HPP_
17+
18+
#include <list>
19+
#include <memory>
20+
#include <string>
21+
#include <unordered_map>
22+
#include <utility>
23+
#include <vector>
24+
25+
#include "rclcpp/create_subscription.hpp"
26+
#include "rclcpp/node_interfaces/get_node_base_interface.hpp"
27+
#include "rclcpp/node_interfaces/get_node_topics_interface.hpp"
28+
#include "rclcpp/node_interfaces/node_base_interface.hpp"
29+
#include "rclcpp/node_interfaces/node_topics_interface.hpp"
30+
#include "rclcpp/parameter.hpp"
31+
#include "rclcpp/qos.hpp"
32+
#include "rclcpp/subscription.hpp"
33+
#include "rclcpp/visibility_control.hpp"
34+
#include "rcl_interfaces/msg/parameter_event.hpp"
35+
36+
namespace rclcpp
37+
{
38+
39+
struct ParameterCallbackHandle
40+
{
41+
RCLCPP_SMART_PTR_DEFINITIONS(ParameterCallbackHandle)
42+
43+
using ParameterCallbackType = std::function<void (const rclcpp::Parameter &)>;
44+
45+
std::string parameter_name;
46+
std::string node_name;
47+
ParameterCallbackType callback;
48+
};
49+
50+
struct ParameterEventCallbackHandle
51+
{
52+
RCLCPP_SMART_PTR_DEFINITIONS(ParameterEventCallbackHandle)
53+
54+
using ParameterEventCallbackType =
55+
std::function<void (const rcl_interfaces::msg::ParameterEvent::SharedPtr &)>;
56+
57+
ParameterEventCallbackType callback;
58+
};
59+
60+
/// A class used to "handle" (monitor and respond to) changes to parameters.
61+
/**
62+
* The ParameterEventHandler class allows for the monitoring of changes to node parameters,
63+
* either a node's own parameters or parameters owned by other nodes in the system.
64+
* Multiple parameter callbacks can be set and will be invoked when the specified parameter
65+
* changes.
66+
*
67+
* The first step is to instantiate a ParameterEventHandler, providing a ROS node to use
68+
* to create any required subscriptions:
69+
*
70+
* auto param_handler = std::make_shared<rclcpp::ParameterEventHandler>(node);
71+
*
72+
* Next, you can supply a callback to the add_parameter_callback method, as follows:
73+
*
74+
* auto cb1 = [&node](const rclcpp::Parameter & p) {
75+
* RCLCPP_INFO(
76+
* node->get_logger(),
77+
* "cb1: Received an update to parameter \"%s\" of type %s: \"%ld\"",
78+
* p.get_name().c_str(),
79+
* p.get_type_name().c_str(),
80+
* p.as_int());
81+
* };
82+
* auto handle1 = param_handler->add_parameter_callback("an_int_param", cb1);
83+
*
84+
* In this case, we didn't supply a node name (the third, optional, parameter) so the
85+
* default will be to monitor for changes to the "an_int_param" parameter associated with
86+
* the ROS node supplied in the ParameterEventHandler constructor.
87+
* The callback, a lambda function in this case, simply prints out the value of the parameter.
88+
*
89+
* You may also monitor for changes to parameters in other nodes by supplying the node
90+
* name to add_parameter_callback:
91+
*
92+
* auto cb2 = [&node](const rclcpp::Parameter & p) {
93+
* RCLCPP_INFO(
94+
* node->get_logger(),
95+
* "cb2: Received an update to parameter \"%s\" of type: %s: \"%s\"",
96+
* p.get_name().c_str(),
97+
* p.get_type_name().c_str(),
98+
* p.as_string().c_str());
99+
* };
100+
* auto handle2 = param_handler->add_parameter_callback(
101+
* "some_remote_param_name", cb2, "some_remote_node_name");
102+
*
103+
* In this case, the callback will be invoked whenever "some_remote_param_name" changes
104+
* on remote node "some_remote_node_name".
105+
*
106+
* To remove a parameter callback, call remove_parameter_callback, passing the handle returned
107+
* from add_parameter_callback:
108+
*
109+
* param_handler->remove_parameter_callback(handle2);
110+
*
111+
* You can also monitor for *all* parameter changes, using add_parameter_event_callback.
112+
* In this case, the callback will be invoked whenever any parameter changes in the system.
113+
* You are likely interested in a subset of these parameter changes, so in the callback it
114+
* is convenient to use a regular expression on the node names or namespaces of interest.
115+
* For example:
116+
*
117+
* auto cb3 =
118+
* [fqn, remote_param_name, &node](const rcl_interfaces::msg::ParameterEvent::SharedPtr & event) {
119+
* // Look for any updates to parameters in "/a_namespace" as well as any parameter changes
120+
* // to our own node ("this_node")
121+
* std::regex re("(/a_namespace/.*)|(/this_node)");
122+
* if (regex_match(event->node, re)) {
123+
* // Now that we know the event matches the regular expression we scanned for, we can
124+
* // use 'get_parameter_from_event' to get a specific parameter name that we're looking for
125+
* rclcpp::Parameter p;
126+
* if (rclcpp::ParameterEventsSubscriber::get_parameter_from_event(
127+
* *event, p, remote_param_name, fqn))
128+
* {
129+
* RCLCPP_INFO(
130+
* node->get_logger(),
131+
* "cb3: Received an update to parameter \"%s\" of type: %s: \"%s\"",
132+
* p.get_name().c_str(),
133+
* p.get_type_name().c_str(),
134+
* p.as_string().c_str());
135+
* }
136+
*
137+
* // You can also use 'get_parameter*s*_from_event' to enumerate all changes that came
138+
* // in on this event
139+
* auto params = rclcpp::ParameterEventsSubscriber::get_parameters_from_event(*event);
140+
* for (auto & p : params) {
141+
* RCLCPP_INFO(
142+
* node->get_logger(),
143+
* "cb3: Received an update to parameter \"%s\" of type: %s: \"%s\"",
144+
* p.get_name().c_str(),
145+
* p.get_type_name().c_str(),
146+
* p.value_to_string().c_str());
147+
* }
148+
* }
149+
* };
150+
* auto handle3 = param_handler->add_parameter_event_callback(cb3);
151+
*
152+
* For both parameter callbacks and parameter event callbacks, when multiple callbacks are added,
153+
* the callbacks are invoked last-in, first-called order (LIFO).
154+
*
155+
* To remove a parameter event callback, use:
156+
*
157+
* param_handler->remove_event_parameter_callback(handle);
158+
*/
159+
class ParameterEventHandler
160+
{
161+
public:
162+
/// Construct a parameter events monitor.
163+
/**
164+
* \param[in] node The node to use to create any required subscribers.
165+
* \param[in] qos The QoS settings to use for any subscriptions.
166+
*/
167+
template<typename NodeT>
168+
ParameterEventHandler(
169+
NodeT node,
170+
const rclcpp::QoS & qos =
171+
rclcpp::QoS(rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_parameter_events)))
172+
{
173+
node_base_ = rclcpp::node_interfaces::get_node_base_interface(node);
174+
auto node_topics = rclcpp::node_interfaces::get_node_topics_interface(node);
175+
176+
event_subscription_ = rclcpp::create_subscription<rcl_interfaces::msg::ParameterEvent>(
177+
node_topics, "/parameter_events", qos,
178+
std::bind(&ParameterEventHandler::event_callback, this, std::placeholders::_1));
179+
}
180+
181+
using ParameterEventCallbackType =
182+
ParameterEventCallbackHandle::ParameterEventCallbackType;
183+
184+
/// Set a callback for all parameter events.
185+
/**
186+
* This function may be called multiple times to set multiple parameter event callbacks.
187+
*
188+
* \param[in] callback Function callback to be invoked on parameter updates.
189+
* \returns A handle used to refer to the callback.
190+
*/
191+
RCLCPP_PUBLIC
192+
ParameterEventCallbackHandle::SharedPtr
193+
add_parameter_event_callback(
194+
ParameterEventCallbackType callback);
195+
196+
/// Remove parameter event callback registered with add_parameter_event_callback.
197+
/**
198+
* \param[in] callback_handle Handle of the callback to remove.
199+
*/
200+
RCLCPP_PUBLIC
201+
void
202+
remove_parameter_event_callback(
203+
ParameterEventCallbackHandle::SharedPtr callback_handle);
204+
205+
using ParameterCallbackType = ParameterCallbackHandle::ParameterCallbackType;
206+
207+
/// Add a callback for a specified parameter.
208+
/**
209+
* If a node_name is not provided, defaults to the current node.
210+
*
211+
* \param[in] parameter_name Name of parameter to monitor.
212+
* \param[in] callback Function callback to be invoked upon parameter update.
213+
* \param[in] node_name Name of node which hosts the parameter.
214+
* \returns A handle used to refer to the callback.
215+
*/
216+
RCLCPP_PUBLIC
217+
ParameterCallbackHandle::SharedPtr
218+
add_parameter_callback(
219+
const std::string & parameter_name,
220+
ParameterCallbackType callback,
221+
const std::string & node_name = "");
222+
223+
/// Remove a parameter callback registered with add_parameter_callback.
224+
/**
225+
* The parameter name and node name are inspected from the callback handle. The callback handle
226+
* is erased from the list of callback handles on the {parameter_name, node_name} in the map.
227+
* An error is thrown if the handle does not exist and/or was already removed.
228+
*
229+
* \param[in] callback_handle Handle of the callback to remove.
230+
*/
231+
RCLCPP_PUBLIC
232+
void
233+
remove_parameter_callback(
234+
ParameterCallbackHandle::SharedPtr callback_handle);
235+
236+
/// Get an rclcpp::Parameter from a parameter event.
237+
/**
238+
* If a node_name is not provided, defaults to the current node.
239+
*
240+
* \param[in] event Event msg to be inspected.
241+
* \param[out] parameter Reference to rclcpp::Parameter to be assigned.
242+
* \param[in] parameter_name Name of parameter.
243+
* \param[in] node_name Name of node which hosts the parameter.
244+
* \returns Output parameter is set with requested parameter info and returns true if
245+
* requested parameter name and node is in event. Otherwise, returns false.
246+
*/
247+
RCLCPP_PUBLIC
248+
static bool
249+
get_parameter_from_event(
250+
const rcl_interfaces::msg::ParameterEvent & event,
251+
rclcpp::Parameter & parameter,
252+
const std::string parameter_name,
253+
const std::string node_name = "");
254+
255+
/// Get an rclcpp::Parameter from parameter event
256+
/**
257+
* If a node_name is not provided, defaults to the current node.
258+
*
259+
* The user is responsible to check if the returned parameter has been properly assigned.
260+
* By default, if the requested parameter is not found in the event, the returned parameter
261+
* has parameter value of type rclcpp::PARAMETER_NOT_SET.
262+
*
263+
* \param[in] event Event msg to be inspected.
264+
* \param[in] parameter_name Name of parameter.
265+
* \param[in] node_name Name of node which hosts the parameter.
266+
* \returns The resultant rclcpp::Parameter from the event.
267+
*/
268+
RCLCPP_PUBLIC
269+
static rclcpp::Parameter
270+
get_parameter_from_event(
271+
const rcl_interfaces::msg::ParameterEvent & event,
272+
const std::string parameter_name,
273+
const std::string node_name = "");
274+
275+
/// Get all rclcpp::Parameter values from a parameter event
276+
/**
277+
* \param[in] event Event msg to be inspected.
278+
* \returns A vector rclcpp::Parameter values from the event.
279+
*/
280+
RCLCPP_PUBLIC
281+
static std::vector<rclcpp::Parameter>
282+
get_parameters_from_event(
283+
const rcl_interfaces::msg::ParameterEvent & event);
284+
285+
using CallbacksContainerType = std::list<ParameterCallbackHandle::WeakPtr>;
286+
287+
protected:
288+
/// Callback for parameter events subscriptions.
289+
RCLCPP_PUBLIC
290+
void
291+
event_callback(const rcl_interfaces::msg::ParameterEvent::SharedPtr event);
292+
293+
// Utility function for resolving node path.
294+
std::string resolve_path(const std::string & path);
295+
296+
// Node interface used for base functionality
297+
std::shared_ptr<rclcpp::node_interfaces::NodeBaseInterface> node_base_;
298+
299+
// *INDENT-OFF* Uncrustify doesn't handle indented public/private labels
300+
// Hash function for string pair required in std::unordered_map
301+
// See: https://stackoverflow.com/questions/35985960/c-why-is-boosthash-combine-the-best-way-to-combine-hash-values
302+
class StringPairHash
303+
{
304+
public:
305+
template<typename T>
306+
inline void hash_combine(std::size_t & seed, const T & v) const
307+
{
308+
std::hash<T> hasher;
309+
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
310+
}
311+
312+
inline size_t operator()(const std::pair<std::string, std::string> & s) const
313+
{
314+
size_t seed = 0;
315+
hash_combine(seed, s.first);
316+
hash_combine(seed, s.second);
317+
return seed;
318+
}
319+
};
320+
// *INDENT-ON*
321+
322+
// Map container for registered parameters
323+
std::unordered_map<
324+
std::pair<std::string, std::string>,
325+
CallbacksContainerType,
326+
StringPairHash
327+
> parameter_callbacks_;
328+
329+
rclcpp::Subscription<rcl_interfaces::msg::ParameterEvent>::SharedPtr event_subscription_;
330+
331+
std::list<ParameterEventCallbackHandle::WeakPtr> event_callbacks_;
332+
333+
std::recursive_mutex mutex_;
334+
};
335+
336+
} // namespace rclcpp
337+
338+
#endif // RCLCPP__PARAMETER_EVENT_HANDLER_HPP_

rclcpp/include/rclcpp/rclcpp.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,15 @@
147147
#include "rclcpp/guard_condition.hpp"
148148
#include "rclcpp/logging.hpp"
149149
#include "rclcpp/node.hpp"
150-
#include "rclcpp/parameter.hpp"
151150
#include "rclcpp/parameter_client.hpp"
151+
#include "rclcpp/parameter_event_handler.hpp"
152+
#include "rclcpp/parameter.hpp"
152153
#include "rclcpp/parameter_service.hpp"
153154
#include "rclcpp/rate.hpp"
154155
#include "rclcpp/time.hpp"
155156
#include "rclcpp/utilities.hpp"
156157
#include "rclcpp/visibility_control.hpp"
157-
#include "rclcpp/wait_set.hpp"
158158
#include "rclcpp/waitable.hpp"
159+
#include "rclcpp/wait_set.hpp"
159160

160161
#endif // RCLCPP__RCLCPP_HPP_

0 commit comments

Comments
 (0)