mrs_lib
Various reusable classes, functions and utilities for use in MRS projects
Loading...
Searching...
No Matches
subscriber_handler.hpp
1// clang: MatousFormat
2
3#ifndef SUBSCRIBER_HANDLER_HPP
4#define SUBSCRIBER_HANDLER_HPP
5
6#ifndef SUBSCRIBER_HANDLER_H
8#endif
9
11#include <mrs_lib/timer_handler.h>
12#include <mutex>
13#include <condition_variable>
14
15namespace mrs_lib
16{
17 /* SubscriberHandler::Impl class //{ */
18 // implements the constructor, getMsg() method and data_callback method (non-thread-safe)
19 template <typename MessageType>
20 class SubscriberHandler<MessageType>::Impl
21 {
22 public:
23 using timeout_callback_t = typename SubscriberHandler<MessageType>::timeout_callback_t;
24 using message_callback_t = typename SubscriberHandler<MessageType>::message_callback_t;
25 using data_callback_t = std::function<void(const typename MessageType::ConstSharedPtr&)>;
26
27 private:
28 friend class SubscriberHandler<MessageType>;
29
30 public:
31 /* constructor //{ */
32 Impl(const SubscriberHandlerOptions& options, const message_callback_t& message_callback = message_callback_t())
33 : m_node(options.node),
34 m_qos(options.qos),
35 m_sub_opts(options.subscription_options),
36 m_topic_name(options.topic_name),
37 m_node_name(options.node_name),
38 m_got_data(false),
39 m_new_data(false),
40 m_used_data(false),
41 m_timeout_manager(options.timeout_manager),
42 m_latest_message_time(0),
43 m_latest_message(nullptr),
44 m_message_callback(message_callback)
45 {
46
47 // initialize the callback for the TimeoutManager
48 if (options.timeout_callback)
49 {
50 m_timeout_mgr_callback = std::bind(options.timeout_callback, m_topic_name, std::placeholders::_1);
51 } else
52 {
53 m_timeout_mgr_callback = std::bind(&Impl::default_timeout_callback, this, m_topic_name, std::placeholders::_1);
54 }
55
56 if (options.no_message_timeout != mrs_lib::no_timeout)
57 {
58
59 // initialize a new TimeoutManager if not provided by the user
60 if (!m_timeout_manager)
61 {
62 m_timeout_manager =
63 std::make_shared<mrs_lib::TimeoutManager>(m_node, rclcpp::Rate(1.0 / (options.no_message_timeout.seconds() * 0.5), m_node->get_clock()));
64 }
65
66 // register the timeout callback with the TimeoutManager, paused until start() creates the subscription
67 m_timeout_id = m_timeout_manager->registerNew(options.no_message_timeout, m_timeout_mgr_callback, m_node->get_clock()->now(), false, false);
68 }
69 }
70 //}
71
72 virtual ~Impl() = default;
73
74 public:
75 /* getMsg() method //{ */
76 virtual typename MessageType::ConstSharedPtr getMsg()
77 {
78 m_new_data = false;
79 m_used_data = true;
80 return peekMsg();
81 }
82 //}
83
84 /* peekMsg() method //{ */
85 virtual typename MessageType::ConstSharedPtr peekMsg() const
86 {
87 /* assert(m_got_data); */
88 /* if (!m_got_data) */
89 /* ROS_ERROR("[%s]: No data received yet from topic '%s' (forgot to check hasMsg()?)! Returning empty message.", m_node_name.c_str(), */
90 /* topicName().c_str()); */
91 return m_latest_message;
92 }
93 //}
94
95 /* hasMsg() method //{ */
96 virtual bool hasMsg() const
97 {
98 return m_got_data;
99 }
100 //}
101
102 /* newMsg() method //{ */
103 virtual bool newMsg() const
104 {
105 return m_new_data;
106 }
107 //}
108
109 /* usedMsg() method //{ */
110 virtual bool usedMsg() const
111 {
112 return m_used_data;
113 }
114 //}
115
116 /* waitForNew() method //{ */
117 virtual typename MessageType::ConstSharedPtr waitForNew(const rclcpp::Duration& timeout)
118 {
119 // convert the ros type to chrono type
120 const std::chrono::duration<float> chrono_timeout(timeout.seconds());
121 // lock the mutex guarding the m_new_data flag
122 std::unique_lock lock(m_new_data_mtx);
123 // if new data is available, return immediately, otherwise attempt to wait for new data using the respective condition variable
124 if (m_new_data)
125 return getMsg();
126 else if (m_new_data_cv.wait_for(lock, chrono_timeout) == std::cv_status::no_timeout && m_new_data)
127 return getMsg();
128 else
129 return nullptr;
130 };
131 //}
132
133 /* lastMsgTime() method //{ */
134 virtual rclcpp::Time lastMsgTime() const
135 {
136 return m_latest_message_time;
137 };
138 //}
139
140 /* topicName() method //{ */
141 virtual std::string topicName() const
142 {
143 if (m_sub == nullptr)
144 {
145 return "";
146 }
147
148 std::string ret = m_sub->get_topic_name();
149
150 if (ret.empty())
151 {
152 ret = m_node->get_node_topics_interface()->resolve_topic_name(m_topic_name);
153 }
154
155 return ret;
156 }
157 //}
158
159 /* getNumPublishers() method //{ */
160 virtual uint32_t getNumPublishers() const
161 {
162 return m_sub->get_publisher_count();
163 };
164 //}
165
166 /* setNoMessageTimeout() method //{ */
167 virtual void setNoMessageTimeout(const rclcpp::Duration& timeout)
168 {
169 if (timeout == mrs_lib::no_timeout)
170 {
171 // if there is a timeout callback already registered but the user wants to disable it, pause it
172 if (m_timeout_manager != nullptr && m_timeout_id.has_value())
173 m_timeout_manager->pause(m_timeout_id.value());
174 // otherwise, there is no callback, so nothing to do
175 } else
176 {
177 // if there is no callback manager, create it
178 if (m_timeout_manager == nullptr)
179 m_timeout_manager = std::make_shared<mrs_lib::TimeoutManager>(m_node, rclcpp::Rate(1.0 / (timeout.seconds() * 0.5), m_node->get_clock()));
180
181 // only arm the timeout while subscribed, otherwise start() arms it
182 const bool running = m_sub != nullptr;
183
184 // if there is an existing timeout callback registered, change its timeout
185 if (m_timeout_id.has_value())
186 m_timeout_manager->change(m_timeout_id.value(), timeout, m_timeout_mgr_callback, m_node->get_clock()->now(), false, running);
187
188 // otherwise, register it
189 else
190 m_timeout_id = m_timeout_manager->registerNew(timeout, m_timeout_mgr_callback, m_node->get_clock()->now(), false, running);
191 }
192 }
193 //}
194
195 /* start() method //{ */
196 virtual void start()
197 {
198 // the timeout callback reads m_sub, so it must not run while m_sub is being replaced
199 pauseTimeout();
200 subscribe();
201 startTimeout();
202 }
203 //}
204
205 /* stop() method //{ */
206 virtual void stop()
207 {
208 pauseTimeout();
209 m_sub.reset();
210 }
211 //}
212
213 protected:
214 rclcpp::Node::SharedPtr m_node;
215 rclcpp::Subscription<MessageType>::SharedPtr m_sub;
216 rclcpp::QoS m_qos = rclcpp::SystemDefaultsQoS();
217 rclcpp::SubscriptionOptions m_sub_opts;
218
219 protected:
220 std::string m_topic_name;
221 std::string m_node_name;
222
223 protected:
224 bool m_got_data; // whether any data was received
225
226 mutable std::mutex m_new_data_mtx;
227 mutable std::condition_variable m_new_data_cv;
228 bool m_new_data; // whether new data was received since last call to get_data
229
230 bool m_used_data; // whether get_data was successfully called at least once
231
232 protected:
233 std::shared_ptr<mrs_lib::TimeoutManager> m_timeout_manager;
234 std::optional<mrs_lib::TimeoutManager::timeout_id_t> m_timeout_id;
235 mrs_lib::TimeoutManager::callback_t m_timeout_mgr_callback;
236
237 protected:
238 rclcpp::Time m_latest_message_time;
239 typename MessageType::ConstSharedPtr m_latest_message;
240 message_callback_t m_message_callback;
241
242 protected:
243 /* subscribe() method //{ */
244 void subscribe()
245 {
246 const std::function<void(const typename MessageType::SharedPtr)> cbk = std::bind(&Impl::data_callback, this, std::placeholders::_1);
247
248 m_sub = m_node->create_subscription<MessageType>(m_topic_name, m_qos, cbk, m_sub_opts);
249 RCLCPP_INFO_STREAM(m_node->get_logger(), "Subscribed to topic '" << m_topic_name << "' -> '" << m_sub->get_topic_name() << "'");
250 }
251 //}
252
253 /* pauseTimeout() and startTimeout() methods //{ */
254 void pauseTimeout()
255 {
256 if (m_timeout_manager && m_timeout_id.has_value())
257 m_timeout_manager->pause(m_timeout_id.value());
258 }
259
260 void startTimeout()
261 {
262 if (m_timeout_manager && m_timeout_id.has_value())
263 m_timeout_manager->start(m_timeout_id.value(), m_node->get_clock()->now());
264 }
265 //}
266
267 /* default_timeout_callback() method //{ */
268 void default_timeout_callback([[maybe_unused]] const std::string& topic_name, const rclcpp::Time& last_msg)
269 {
270 // runs under the TimeoutManager lock: no topicName(), it takes the handler lock (lock-order deadlock with data_callback())
271 if (!m_sub)
272 return;
273
274 const rclcpp::Duration since_msg = (m_node->get_clock()->now() - last_msg);
275 const auto n_pubs = m_sub->get_publisher_count();
276 const std::string txt = "Did not receive any message from topic '" + std::string(m_sub->get_topic_name()) + "' for " + std::to_string(since_msg.seconds())
277 + "s (" + std::to_string(n_pubs) + " QoS-compatible publishers on this topic)";
278
279 RCLCPP_WARN_STREAM(m_node->get_logger(), txt);
280 }
281 //}
282
283 /* process_new_message() method //{ */
284 void process_new_message(const typename MessageType::ConstSharedPtr& msg)
285 {
286 m_latest_message_time = m_node->get_clock()->now();
287 m_latest_message = msg;
288 // If the message callback is registered, the new data will immediately be processed,
289 // so reset the flag. Otherwise, set the flag.
290 m_new_data = !m_message_callback;
291 m_got_data = true;
292 m_new_data_cv.notify_one();
293 }
294 //}
295
296 /* data_callback() method //{ */
297 virtual void data_callback(const typename MessageType::ConstSharedPtr& msg)
298 {
299 if (m_timeout_manager && m_timeout_id.has_value())
300 m_timeout_manager->reset(m_timeout_id.value(), m_node->get_clock()->now());
301
302 {
303 std::lock_guard lck(m_new_data_mtx);
304 process_new_message(msg);
305 }
306
307 // execute the callback after unlocking the mutex to enable multi-threaded callback execution
308 if (m_message_callback)
309 m_message_callback(msg);
310 }
311 //}
312 };
313 //}
314
315 /* SubscribeHandler_threadsafe class //{ */
316 template <typename MessageType>
317 class SubscriberHandler<MessageType>::ImplThreadsafe : public SubscriberHandler<MessageType>::Impl
318 {
319 private:
320 using impl_class_t = SubscriberHandler<MessageType>::Impl;
321
322 public:
323 using timeout_callback_t = typename impl_class_t::timeout_callback_t;
324 using message_callback_t = typename impl_class_t::message_callback_t;
325
326 friend class SubscriberHandler<MessageType>;
327
328 public:
329 ImplThreadsafe(const SubscriberHandlerOptions& options, const message_callback_t& message_callback = message_callback_t())
330 : impl_class_t::Impl(options, message_callback)
331 {
332 }
333
334 public:
335 virtual bool hasMsg() const override
336 {
337 std::lock_guard lck(m_mtx);
338 return impl_class_t::hasMsg();
339 }
340 virtual bool newMsg() const override
341 {
342 std::lock_guard lck(m_mtx);
343 return impl_class_t::newMsg();
344 }
345 virtual bool usedMsg() const override
346 {
347 std::lock_guard lck(m_mtx);
348 return impl_class_t::usedMsg();
349 }
350 virtual typename MessageType::ConstSharedPtr getMsg() override
351 {
352 std::lock_guard lck(m_mtx);
353 return impl_class_t::getMsg();
354 }
355 virtual typename MessageType::ConstSharedPtr peekMsg() const override
356 {
357 std::lock_guard lck(m_mtx);
358 return impl_class_t::peekMsg();
359 }
360 virtual rclcpp::Time lastMsgTime() const override
361 {
362 std::lock_guard lck(m_mtx);
363 return impl_class_t::lastMsgTime();
364 };
365 virtual std::string topicName() const override
366 {
367 std::lock_guard lck(m_mtx);
368 return impl_class_t::topicName();
369 };
370 // the TimeoutManager is called without m_mtx: timeout callbacks run under its lock and may call the methods above
371 virtual void start() override
372 {
373 this->pauseTimeout();
374 {
375 std::lock_guard lck(m_mtx);
376 impl_class_t::subscribe();
377 }
378 this->startTimeout();
379 }
380 virtual void stop() override
381 {
382 this->pauseTimeout();
383 std::lock_guard lck(m_mtx);
384 this->m_sub.reset();
385 }
386
387 virtual ~ImplThreadsafe() override = default;
388
389 protected:
390 virtual void data_callback(const typename MessageType::ConstSharedPtr& msg) override
391 {
392 // reset before locking m_mtx, see start()
393 if (this->m_timeout_manager && this->m_timeout_id.has_value())
394 this->m_timeout_manager->reset(this->m_timeout_id.value(), this->m_node->get_clock()->now());
395
396 {
397 std::scoped_lock lck(m_mtx, this->m_new_data_mtx);
398 impl_class_t::process_new_message(msg);
399 }
400
401 // execute the callback after unlocking the mutex to enable multi-threaded callback execution
402 if (this->m_message_callback)
403 impl_class_t::m_message_callback(msg);
404 }
405
406 private:
407 mutable std::recursive_mutex m_mtx;
408 };
409 //}
410
411} // namespace mrs_lib
412
413#endif // SUBSCRIBER_HANDLER_HPP
Definition subscriber_handler.hpp:21
virtual bool newMsg() const
Used to check whether at least one message has been received on the handled topic since the last call...
Definition subscriber_handler.h:150
virtual void stop()
Disables the callbacks for the handled topic.
Definition subscriber_handler.h:252
virtual MessageType::ConstSharedPtr getMsg()
Returns the last received message on the topic, handled by this SubscriberHandler....
Definition subscriber_handler.h:117
virtual uint32_t getNumPublishers() const
Returns number of publishers registered at the topic.
Definition subscriber_handler.h:217
virtual void setNoMessageTimeout(const rclcpp::Duration &timeout)
Sets the timeout for no received message.
Definition subscriber_handler.h:228
virtual std::string topicName() const
Returns the resolved (full) name of the topic, handled by this SubscriberHandler.
Definition subscriber_handler.h:195
SubscriberHandler()
Default constructor to avoid having to use pointers.
Definition subscriber_handler.h:265
virtual bool usedMsg() const
Used to check whether getMsg() was called at least once on this SubscriberHandler.
Definition subscriber_handler.h:161
virtual MessageType::ConstSharedPtr waitForNew(const rclcpp::Duration &timeout)
Blocks until new data becomes available or until the timeout runs out or until a spurious wake-up.
Definition subscriber_handler.h:173
virtual bool hasMsg() const
Used to check whether at least one message has been received on the handled topic.
Definition subscriber_handler.h:139
virtual void start()
Enables the callbacks for the handled topic.
Definition subscriber_handler.h:240
virtual MessageType::ConstSharedPtr peekMsg() const
Returns the last received message on the topic without modifying the newMsg() or usedMsg() flags.
Definition subscriber_handler.h:128
std::function< void(typename MessageType::ConstSharedPtr)> message_callback_t
Convenience type for the message callback function.
Definition subscriber_handler.h:107
std::function< void(const std::string &topic_name, const rclcpp::Time &last_msg)> timeout_callback_t
Type for the timeout callback function.
Definition subscriber_handler.h:102
virtual rclcpp::Time lastMsgTime() const
Returns time of the last received message on the topic, handled by this SubscriberHandler.
Definition subscriber_handler.h:184
All mrs_lib functions, classes, variables and definitions are contained in this namespace.
Definition attitude_converter.h:24
A helper class to simplify setup of SubscriberHandler construction. This class is passed to the Subsc...
Definition subscriber_handler.h:33
rclcpp::Node::SharedPtr node
The ROS NodeHandle to be used for subscription.
Definition subscriber_handler.h:39
std::string topic_name
Name of the ROS topic to be handled.
Definition subscriber_handler.h:43
std::shared_ptr< mrs_lib::TimeoutManager > timeout_manager
Will be used for handling message timouts if necessary. If no manager is specified,...
Definition subscriber_handler.h:45
std::function< void(const std::string &topic_name, const rclcpp::Time &last_msg)> timeout_callback
This function will be called if no new message is received for the no_message_timeout duration....
Definition subscriber_handler.h:52
std::string node_name
Name of the ROS node, using this handle (used for messages printed to console).
Definition subscriber_handler.h:41
rclcpp::Duration no_message_timeout
If no new message is received for this duration, the timeout_callback function will be called....
Definition subscriber_handler.h:48
Defines SubscriberHandler and related convenience classes for subscribing to ROS topics.