mrs_lib
Various reusable classes, functions and utilities for use in MRS projects
Loading...
Searching...
No Matches
subscriber.impl.hpp
1#ifndef MRS_LIB_SUBSCRIBER_IMPL_HPP_
2#define MRS_LIB_SUBSCRIBER_IMPL_HPP_
3
4#include "mrs_lib/subscriber.hpp"
5
6#include <chrono>
7#include <cstddef>
8#include <functional>
9#include <memory>
10#include <optional>
11#include <stdexcept>
12#include <string>
13#include <string_view>
14
15#include <rclcpp/create_subscription.hpp>
16#include <rclcpp/duration.hpp>
17#include <rclcpp/subscription_options.hpp>
18#include <rclcpp/time.hpp>
19
20#include "mrs_lib/internal/coroutine_callback_helpers.hpp"
21#include "mrs_lib/logger.hpp"
22#include "mrs_lib/timeout_manager.hpp"
25
26namespace mrs_lib
27{
29 template <typename MessageType>
30 class Subscriber<MessageType>::Impl
31 {
32 struct TimeoutData
33 {
34 std::shared_ptr<TimeoutManager2> timeout_manager{};
35 std::shared_ptr<TimeoutManagerHandle> timeout_handle{};
36 };
37
38 struct LatestMessageData
39 {
40 rclcpp::Time time{};
41 std::shared_ptr<const MessageType> message{};
42 bool is_new = false;
43 };
44
45 public:
46 explicit Impl(const SubscriberOptions& options, std::string_view topic_name, std::optional<MessageCallback> callback)
47 : node_interfaces_(options.node_interfaces),
48 logger_(node_interfaces_),
49 unresolved_name_(topic_name),
50 timeout_data_(init_timeout_data(options, topic_name)),
51 user_callback_(check_nonempty_callback_function(std::move(callback))),
52 subscription_(rclcpp::create_subscription<MessageType>(
53 node_interfaces_, std::string(topic_name), options.qos,
54 std::function<void(std::shared_ptr<const MessageType>)>(std::bind_front(&Impl::message_callback, this)), std::invoke([&]() {
55 rclcpp::SubscriptionOptions opts{};
56 opts.callback_group = options.callback_group;
57 return opts;
58 })))
59 {
60 logger_.info("Subscribed to topic '{}' -> '{}'", this->get_unresolved_topic_name(), this->get_topic_name());
61 }
62
63 explicit Impl(const SubscriberOptions& options, std::string_view topic_name, MessageCoroCallback callback)
64 : Impl(options, topic_name, internal::get_detached_coro_callback_launcher(callback, options.callback_group))
65 {
66 }
67
68 ~Impl() = default;
69 Impl(const Impl&) = delete;
70 Impl& operator=(const Impl&) = delete;
71 Impl(Impl&&) = delete;
72 Impl& operator=(Impl&&) = delete;
73
74 [[nodiscard]] std::optional<std::shared_ptr<const MessageType>> get_message()
75 {
76 auto guard = latest_message_data_.acquire();
77 guard->is_new = false;
78
79 if (guard->message != nullptr)
80 {
81 return guard->message;
82 } else
83 {
84 return std::nullopt;
85 }
86 }
87
88 [[nodiscard]] std::optional<std::shared_ptr<const MessageType>> get_new_message()
89 {
90 auto guard = latest_message_data_.acquire();
91
92 if (guard->is_new && guard->message != nullptr)
93 {
94 guard->is_new = false;
95 return guard->message;
96 } else
97 {
98 return std::nullopt;
99 }
100 }
101
102 [[nodiscard]] std::optional<std::shared_ptr<const MessageType>> peek_message() const
103 {
104 auto guard = latest_message_data_.acquire();
105 if (guard->message != nullptr)
106 {
107 return guard->message;
108 } else
109 {
110 return std::nullopt;
111 }
112 }
113
114 [[nodiscard]] std::optional<std::shared_ptr<const MessageType>> peek_new_message() const
115 {
116 auto guard = latest_message_data_.acquire();
117
118 if (guard->is_new && guard->message != nullptr)
119 {
120 return guard->message;
121 } else
122 {
123 return std::nullopt;
124 }
125 }
126
127 [[nodiscard]] rclcpp::Time get_last_message_time() const
128 {
129 auto guard = latest_message_data_.acquire();
130 return guard->time;
131 };
132
133 [[nodiscard]] std::string get_topic_name() const
134 {
135 return subscription_->get_topic_name();
136 }
137
138 [[nodiscard]] std::string get_unresolved_topic_name() const
139 {
140 return unresolved_name_;
141 }
142
143 [[nodiscard]] size_t get_publisher_count() const
144 {
145 return subscription_->get_publisher_count();
146 };
147
148 private:
149 static std::optional<MessageCallback> check_nonempty_callback_function(std::optional<MessageCallback> callback)
150 {
151 if (callback.has_value() && !callback.value())
152 {
153 throw std::logic_error("Subscriber callback must not be empty.");
154 } else
155 {
156 return std::move(callback);
157 }
158 }
159
160 TimeoutData init_timeout_data(const SubscriberOptions& options, std::string_view topic_name)
161 {
162 if (!options.no_message_timeout.has_value())
163 {
164 return {};
165 }
166
167 auto no_message_timeout = options.no_message_timeout.value();
168
169 auto timeout_callback = std::invoke([&]() -> TimeoutManager2::Callback {
170 if (options.timeout_callback)
171 {
172 return [callback = options.timeout_callback, name = std::string(topic_name)](rclcpp::Time time) { callback(name, time); };
173 } else
174 {
175 return std::bind_front(&Impl::default_timeout_callback, this, std::string(topic_name));
176 }
177 });
178
179
180 std::shared_ptr<mrs_lib::TimeoutManager2> timeout_manager = options.timeout_manager;
181 // initialize a new TimeoutManager if not provided by the user
182 if (timeout_manager == nullptr)
183 {
184 timeout_manager = std::make_shared<mrs_lib::TimeoutManager2>(node_interfaces_, no_message_timeout / 2);
185 }
186
187 // register the timeout callback with the TimeoutManager
188 auto timeout_handle = timeout_manager->register_new({
189 .timeout = no_message_timeout,
190 .callback = timeout_callback,
191 });
192
193 return {
194 .timeout_manager = timeout_manager,
195 .timeout_handle = timeout_handle,
196 };
197 }
198
199 void default_timeout_callback(std::string_view, rclcpp::Time last_msg)
200 {
201 const rclcpp::Duration since_msg = (node_interfaces_.get_node_clock_interface()->get_clock()->now() - last_msg);
202 const auto n_pubs = subscription_->get_publisher_count();
203 logger_.warning("Did not receive any message from topic '{}' for {}s ({} publishers on this topic)", get_topic_name(), since_msg.seconds(),
204 std::to_string(n_pubs));
205 }
206
207 void process_new_message(const std::shared_ptr<const MessageType>& msg)
208 {
209 auto latest_message_data_guard = latest_message_data_.acquire();
210 latest_message_data_guard->time = node_interfaces_.get_node_clock_interface()->get_clock()->now();
211 latest_message_data_guard->message = msg;
212 // If the message callback is registered, the new data will immediately be processed,
213 // so reset the flag. Otherwise, set the flag.
214 latest_message_data_guard->is_new = !user_callback_.has_value();
215 }
216
217 void message_callback(const std::shared_ptr<const MessageType>& msg)
218 {
219 {
220 auto timeout_data_guard = timeout_data_.acquire();
221
222 if (timeout_data_guard->timeout_manager && timeout_data_guard->timeout_handle)
223 {
224 timeout_data_guard->timeout_handle->reset_timeout();
225 }
226 }
227
228 process_new_message(msg);
229
230 // execute the callback after unlocking the mutex to enable multi-threaded callback execution
231 if (user_callback_.has_value())
232 {
233 (*user_callback_)(msg);
234 }
235 }
236
237 private:
238 SubscriberNodeInterfaces node_interfaces_;
239 Logger logger_;
240
241 std::string unresolved_name_;
242
243 OwningMutex<TimeoutData> timeout_data_;
244 OwningMutex<LatestMessageData> latest_message_data_;
245
246 std::optional<MessageCallback> user_callback_;
247
248 Pimpl<rclcpp::Subscription<MessageType>, std::shared_ptr<rclcpp::Subscription<MessageType>>> subscription_;
249 };
250
251 template <typename MessageType>
252 inline Subscriber<MessageType>::Subscriber(const SubscriberOptions& options, std::string_view topic_name, MessageCallback callback)
253 : impl_(std::in_place_type_t<Impl>{}, options, topic_name, std::move(callback))
254 {
255 }
256
257 template <typename MessageType>
258 inline Subscriber<MessageType>::Subscriber(const SubscriberOptions& options, std::string_view topic_name, MessageCoroCallback callback)
259 : impl_(std::in_place_type_t<Impl>{}, options, topic_name, std::move(callback))
260 {
261 }
262
263 template <typename MessageType>
264 inline Subscriber<MessageType>::Subscriber(const SubscriberOptions& options, std::string_view topic_name, PolledSubscriberTag)
265 : impl_(std::in_place_type_t<Impl>{}, options, topic_name, std::nullopt)
266 {
267 }
268
269 template <typename MessageType>
270 std::optional<std::shared_ptr<const MessageType>> Subscriber<MessageType>::get_message()
271 {
272 return impl_->get_message();
273 };
274
275 template <typename MessageType>
276 std::optional<std::shared_ptr<const MessageType>> Subscriber<MessageType>::get_new_message()
277 {
278 return impl_->get_new_message();
279 };
280
281 template <typename MessageType>
282 std::optional<std::shared_ptr<const MessageType>> Subscriber<MessageType>::peek_message() const
283 {
284 return impl_->peek_message();
285 };
286
287 template <typename MessageType>
288 std::optional<std::shared_ptr<const MessageType>> Subscriber<MessageType>::peek_new_message() const
289 {
290 return impl_->peek_message();
291 };
292
293 template <typename MessageType>
295 {
296 return impl_->get_last_message_time();
297 };
298
299 template <typename MessageType>
301 {
302 return impl_->get_topic_name();
303 };
304
305 template <typename MessageType>
307 {
308 return impl_->get_unresolved_topic_name();
309 };
310
311 template <typename MessageType>
313 {
314 return impl_->get_publisher_count();
315 };
316
317} // namespace mrs_lib
318
319#endif // MRS_LIB_SUBSCRIBER_IMPL_HPP_
Wrapper for rclcpp::Subscription with callback and polling interface.
Definition subscriber.hpp:78
Subscriber(const SubscriberOptions &options, std::string_view topic_name, MessageCallback callback)
Construct a Subscriber with the specified callback.
Definition subscriber.impl.hpp:252
uint32_t get_publisher_count() const
Returns number of publishers registered at the topic.
Definition subscriber.impl.hpp:312
std::optional< std::shared_ptr< const MessageType > > get_new_message()
Get the latest new message, if there is one.
Definition subscriber.impl.hpp:276
std::string get_unresolved_topic_name() const
Returns the unresolved name of the topic handled by this Subscriber.
Definition subscriber.impl.hpp:306
std::optional< std::shared_ptr< const MessageType > > peek_new_message() const
Get the latest new message, if there is one.
Definition subscriber.impl.hpp:288
std::optional< std::shared_ptr< const MessageType > > peek_message() const
Get the latest received message, if there is one.
Definition subscriber.impl.hpp:282
std::function< void(std::shared_ptr< const MessageType >)> MessageCallback
Type of the accepted callback.
Definition subscriber.hpp:84
CoroCallback< void(std::shared_ptr< const MessageType >)> MessageCoroCallback
Type of the accepted coroutine callback.
Definition subscriber.hpp:87
std::optional< std::shared_ptr< const MessageType > > get_message()
Get the latest received message, if there is one.
Definition subscriber.impl.hpp:270
rclcpp::Time get_last_message_time() const
Returns time of the last received message on the topic, handled by this Subscriber.
Definition subscriber.impl.hpp:294
std::string get_topic_name() const
Returns the resolved (full) name of the topic handled by this Subscriber.
Definition subscriber.impl.hpp:300
All mrs_lib functions, classes, variables and definitions are contained in this namespace.
Definition attitude_converter.h:24
rclcpp::node_interfaces::NodeInterfaces< rclcpp::node_interfaces::NodeBaseInterface, rclcpp::node_interfaces::NodeTimersInterface, rclcpp::node_interfaces::NodeClockInterface, rclcpp::node_interfaces::NodeLoggingInterface, rclcpp::node_interfaces::NodeParametersInterface, rclcpp::node_interfaces::NodeTopicsInterface > SubscriberNodeInterfaces
ROS node interfaces required by the Subscriber.
Definition subscriber.hpp:30
Tag for explicitly marking Subscriber that has no callback.
Definition subscriber.hpp:68
Options for configuring the Subscriber.
Definition subscriber.hpp:39