ROS 2 rclcpp + rcl - rolling  rolling-29de98cf
ROS 2 C++ Client Library with ROS Client Library
time_source.cpp
1 // Copyright 2017 Open Source Robotics Foundation, Inc.
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 #include <memory>
16 #include <string>
17 #include <unordered_set>
18 #include <utility>
19 #include <vector>
20 
21 #include "builtin_interfaces/msg/time.hpp"
22 
23 #include "rcl/time.h"
24 
25 #include "rclcpp/clock.hpp"
26 #include "rclcpp/exceptions.hpp"
27 #include "rclcpp/logging.hpp"
28 #include "rclcpp/node.hpp"
29 #include "rclcpp/parameter_client.hpp"
30 #include "rclcpp/parameter_events_filter.hpp"
31 #include "rclcpp/time.hpp"
32 #include "rclcpp/time_source.hpp"
33 
34 namespace rclcpp
35 {
36 
37 class ClocksState final
38 {
39 public:
40  ClocksState()
41  : logger_(rclcpp::get_logger("rclcpp"))
42  {
43  }
44 
45  // An internal method to use in the clock callback that iterates and enables all clocks
46  void enable_ros_time()
47  {
48  if (ros_time_active_) {
49  // already enabled no-op
50  return;
51  }
52 
53  // Local storage
54  ros_time_active_ = true;
55 
56  // Update all attached clocks to zero or last recorded time
57  set_all_clocks(last_time_msg_, true);
58  }
59 
60  // An internal method to use in the clock callback that iterates and disables all clocks
61  void disable_ros_time()
62  {
63  if (!ros_time_active_) {
64  // already disabled no-op
65  return;
66  }
67 
68  // Local storage
69  ros_time_active_ = false;
70 
71  // Update all attached clocks
72  builtin_interfaces::msg::Time msg;
73  set_all_clocks(msg, false);
74  }
75 
76  // Check if ROS time is active
77  bool is_ros_time_active() const
78  {
79  return ros_time_active_;
80  }
81 
82  // Attach a clock
83  void attachClock(const rclcpp::Clock::SharedPtr & clock)
84  {
85  {
86  std::lock_guard<std::mutex> clock_guard(clock->get_clock_mutex());
87  if (clock->get_clock_type() != RCL_ROS_TIME && ros_time_active_) {
88  throw std::invalid_argument(
89  "ros_time_active_ can't be true while clock is not of RCL_ROS_TIME type");
90  }
91  }
92  std::lock_guard<std::mutex> guard(clock_list_lock_);
93  associated_clocks_.insert(clock);
94  // Set the clock to zero unless there's a recently received message
95  set_clock(last_time_msg_, ros_time_active_, clock);
96  }
97 
98  // Detach a clock
99  void detachClock(const rclcpp::Clock::SharedPtr & clock)
100  {
101  std::lock_guard<std::mutex> guard(clock_list_lock_);
102  auto removed = associated_clocks_.erase(clock);
103  if (removed == 0) {
104  RCLCPP_ERROR(logger_, "failed to remove clock");
105  }
106  }
107 
108  // Internal helper function used inside iterators
109  static void set_clock(
110  const builtin_interfaces::msg::Time & msg,
111  bool set_ros_time_enabled,
112  const rclcpp::Clock::SharedPtr & clock)
113  {
114  std::lock_guard<std::mutex> clock_guard(clock->get_clock_mutex());
115 
116  if (clock->get_clock_type() == RCL_ROS_TIME) {
117  // Do change
118  if (!set_ros_time_enabled && clock->ros_time_is_active()) {
119  auto ret = rcl_disable_ros_time_override(clock->get_clock_handle());
120  if (ret != RCL_RET_OK) {
121  rclcpp::exceptions::throw_from_rcl_error(
122  ret, "Failed to disable ros_time_override_status");
123  }
124  } else if (set_ros_time_enabled && !clock->ros_time_is_active()) {
125  auto ret = rcl_enable_ros_time_override(clock->get_clock_handle());
126  if (ret != RCL_RET_OK) {
127  rclcpp::exceptions::throw_from_rcl_error(
128  ret, "Failed to enable ros_time_override_status");
129  }
130  }
131 
132  auto ret = rcl_set_ros_time_override(
133  clock->get_clock_handle(),
134  rclcpp::Time(msg).nanoseconds());
135  if (ret != RCL_RET_OK) {
136  rclcpp::exceptions::throw_from_rcl_error(
137  ret, "Failed to set ros_time_override_status");
138  }
139  } else if (set_ros_time_enabled) {
140  throw std::invalid_argument(
141  "set_ros_time_enabled can't be true while clock is not of RCL_ROS_TIME type");
142  }
143  }
144 
145  // Internal helper function
146  void set_all_clocks(
147  const builtin_interfaces::msg::Time & msg,
148  bool set_ros_time_enabled)
149  {
150  std::lock_guard<std::mutex> guard(clock_list_lock_);
151  for (auto it = associated_clocks_.begin(); it != associated_clocks_.end(); ++it) {
152  set_clock(msg, set_ros_time_enabled, *it);
153  }
154  }
155 
156  // Cache the last clock message received
157  void cache_last_msg(const builtin_interfaces::msg::Time & msg)
158  {
159  last_time_msg_ = msg;
160  }
161 
162  bool are_all_clocks_rcl_ros_time()
163  {
164  std::lock_guard<std::mutex> guard(clock_list_lock_);
165  for (auto & clock : associated_clocks_) {
166  std::lock_guard<std::mutex> clock_guard(clock->get_clock_mutex());
167  if (clock->get_clock_type() != RCL_ROS_TIME) {
168  return false;
169  }
170  }
171  return true;
172  }
173 
174 private:
175  // Store (and update on node attach) logger for logging.
176  Logger logger_;
177 
178  // A lock to protect iterating the associated_clocks_ field.
179  std::mutex clock_list_lock_;
180  // An unordered_set to store references to associated clocks.
181  std::unordered_set<rclcpp::Clock::SharedPtr> associated_clocks_;
182 
183  // Local storage of validity of ROS time
184  // This is needed when new clocks are added.
185  bool ros_time_active_{false};
186  // Last set message to be passed to newly registered clocks
187  builtin_interfaces::msg::Time last_time_msg_{};
188 };
189 
191 {
192 public:
193  NodeState(const rclcpp::QoS & qos, bool use_clock_thread)
194  : use_clock_thread_(use_clock_thread),
195  logger_(rclcpp::get_logger("rclcpp")),
196  qos_(qos)
197  {
198  }
199 
200  ~NodeState()
201  {
202  if (
203  node_base_ || node_topics_ || node_graph_ || node_services_ ||
204  node_logging_ || node_clock_ || node_parameters_)
205  {
206  detachNode();
207  }
208  }
209 
210  // Check if a clock thread will be used
211  bool get_use_clock_thread()
212  {
213  return use_clock_thread_;
214  }
215 
216  // Set whether a clock thread will be used
217  void set_use_clock_thread(bool use_clock_thread)
218  {
219  use_clock_thread_ = use_clock_thread;
220  }
221 
222  // Check if the clock thread is joinable
223  bool clock_thread_is_joinable()
224  {
225  return clock_executor_thread_.joinable();
226  }
227 
228  // Attach a node to this time source
229  void attachNode(
230  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface,
231  rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr node_topics_interface,
232  rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph_interface,
233  rclcpp::node_interfaces::NodeServicesInterface::SharedPtr node_services_interface,
234  rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface,
235  rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_interface,
236  rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_interface)
237  {
238  std::lock_guard<std::mutex> guard(node_base_lock_);
239  node_base_ = std::move(node_base_interface);
240  node_topics_ = std::move(node_topics_interface);
241  node_graph_ = std::move(node_graph_interface);
242  node_services_ = std::move(node_services_interface);
243  node_logging_ = std::move(node_logging_interface);
244  node_clock_ = std::move(node_clock_interface);
245  node_parameters_ = std::move(node_parameters_interface);
246  // TODO(tfoote): Update QOS
247 
248  logger_ = node_logging_->get_logger();
249 
250  // Though this defaults to false, it can be overridden by initial parameter values for the
251  // node, which may be given by the user at the node's construction or even by command-line
252  // arguments.
253  rclcpp::ParameterValue use_sim_time_param;
254  const std::string use_sim_time_name = "use_sim_time";
255  if (!node_parameters_->has_parameter(use_sim_time_name)) {
256  use_sim_time_param = node_parameters_->declare_parameter(
257  use_sim_time_name,
258  rclcpp::ParameterValue(false));
259  } else {
260  use_sim_time_param = node_parameters_->get_parameter(use_sim_time_name).get_parameter_value();
261  }
262  if (use_sim_time_param.get_type() == rclcpp::PARAMETER_BOOL) {
263  if (use_sim_time_param.get<bool>()) {
264  sim_time_parameter_state_ = true;
265  clocks_state_.enable_ros_time();
266  create_clock_sub();
267  }
268  } else {
269  RCLCPP_ERROR(
270  logger_, "Invalid type '%s' for parameter 'use_sim_time', should be 'bool'",
271  rclcpp::to_string(use_sim_time_param.get_type()).c_str());
272  throw std::invalid_argument("Invalid type for parameter 'use_sim_time', should be 'bool'");
273  }
274 
275  on_set_parameters_callback_ = node_parameters_->add_on_set_parameters_callback(
276  std::bind(&TimeSource::NodeState::on_set_parameters, this, std::placeholders::_1));
277 
278  post_set_parameters_callback_ = node_parameters_->add_post_set_parameters_callback(
279  std::bind(&TimeSource::NodeState::post_set_parameters, this, std::placeholders::_1));
280  }
281 
282  // Detach the attached node
283  void detachNode()
284  {
285  // destroy_clock_sub() *must* be first here, to ensure that the executor
286  // can't possibly call any of the callbacks as we are cleaning up.
287  destroy_clock_sub();
288  std::lock_guard<std::mutex> guard(node_base_lock_);
289  clocks_state_.disable_ros_time();
290  if (on_set_parameters_callback_) {
291  node_parameters_->remove_on_set_parameters_callback(on_set_parameters_callback_.get());
292  }
293  if (post_set_parameters_callback_) {
294  node_parameters_->remove_post_set_parameters_callback(post_set_parameters_callback_.get());
295  }
296  on_set_parameters_callback_.reset();
297  post_set_parameters_callback_.reset();
298  node_base_.reset();
299  node_topics_.reset();
300  node_graph_.reset();
301  node_services_.reset();
302  node_logging_.reset();
303  node_clock_.reset();
304  node_parameters_.reset();
305  }
306 
307  void attachClock(const std::shared_ptr<rclcpp::Clock> & clock)
308  {
309  clocks_state_.attachClock(clock);
310  }
311 
312  void detachClock(const std::shared_ptr<rclcpp::Clock> & clock)
313  {
314  clocks_state_.detachClock(clock);
315  }
316 
317 private:
318  ClocksState clocks_state_;
319 
320  // Dedicated thread for clock subscription.
321  bool use_clock_thread_;
322  std::thread clock_executor_thread_;
323 
324  // Preserve the node reference
325  std::mutex node_base_lock_;
326  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_{nullptr};
327  rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr node_topics_{nullptr};
328  rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph_{nullptr};
329  rclcpp::node_interfaces::NodeServicesInterface::SharedPtr node_services_{nullptr};
330  rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_{nullptr};
331  rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_{nullptr};
332  rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_{nullptr};
333 
334  // Store (and update on node attach) logger for logging.
335  Logger logger_;
336 
337  // QoS of the clock subscription.
338  rclcpp::QoS qos_;
339 
340  // The subscription for the clock callback
342  std::shared_ptr<SubscriptionT> clock_subscription_{nullptr};
343  std::mutex clock_sub_lock_;
344  rclcpp::CallbackGroup::SharedPtr clock_callback_group_;
345  rclcpp::executors::SingleThreadedExecutor::SharedPtr clock_executor_;
346 
347  // The clock callback itself
348  void clock_cb(const std::shared_ptr<const rosgraph_msgs::msg::Clock> & msg)
349  {
350  if (!clocks_state_.is_ros_time_active() && sim_time_parameter_state_) {
351  clocks_state_.enable_ros_time();
352  }
353  // Cache the last message in case a new clock is attached.
354  clocks_state_.cache_last_msg(msg->clock);
355 
356  if (sim_time_parameter_state_) {
357  clocks_state_.set_all_clocks(msg->clock, true);
358  }
359  }
360 
361  // Create the subscription for the clock topic
362  void create_clock_sub()
363  {
364  std::lock_guard<std::mutex> guard(clock_sub_lock_);
365  if (clock_subscription_) {
366  // Subscription already created.
367  return;
368  }
369 
371  options.qos_overriding_options = rclcpp::QosOverridingOptions(
372  {
373  rclcpp::QosPolicyKind::Depth,
374  rclcpp::QosPolicyKind::Durability,
375  rclcpp::QosPolicyKind::History,
376  rclcpp::QosPolicyKind::Reliability,
377  });
378 
379  if (use_clock_thread_) {
380  clock_callback_group_ = node_base_->create_callback_group(
381  rclcpp::CallbackGroupType::MutuallyExclusive,
382  false
383  );
384  options.callback_group = clock_callback_group_;
385  rclcpp::ExecutorOptions exec_options;
386  exec_options.context = node_base_->get_context();
387  clock_executor_ =
388  std::make_shared<rclcpp::executors::SingleThreadedExecutor>(exec_options);
389  if (!clock_executor_thread_.joinable()) {
390  clock_executor_thread_ = std::thread(
391  [this]() {
392  clock_executor_->add_callback_group(clock_callback_group_, node_base_);
393  clock_executor_->spin();
394  }
395  );
396  }
397  }
398 
399  clock_subscription_ = rclcpp::create_subscription<rosgraph_msgs::msg::Clock>(
400  node_parameters_,
401  node_topics_,
402  "/clock",
403  qos_,
404  [this](const std::shared_ptr<const rosgraph_msgs::msg::Clock> & msg) {
405  bool execute_cb = false;
406  {
407  std::lock_guard<std::mutex> guard(node_base_lock_);
408  // We are using node_base_ as an indication if there is a node attached.
409  // Only call the clock_cb if that is the case.
410  execute_cb = node_base_ != nullptr;
411  }
412  if (execute_cb) {
413  clock_cb(msg);
414  }
415  },
416  options
417  );
418  }
419 
420  // Destroy the subscription for the clock topic
421  void destroy_clock_sub()
422  {
423  std::lock_guard<std::mutex> guard(clock_sub_lock_);
424  if (clock_executor_thread_.joinable()) {
425  clock_executor_->cancel();
426  clock_executor_thread_.join();
427  clock_executor_->remove_callback_group(clock_callback_group_);
428  }
429  clock_subscription_.reset();
430  }
431 
432  // On set Parameters callback handle
433  node_interfaces::OnSetParametersCallbackHandle::SharedPtr on_set_parameters_callback_{nullptr};
434 
435  // Post set Parameters callback handle
436  node_interfaces::PostSetParametersCallbackHandle::SharedPtr
437  post_set_parameters_callback_{nullptr};
438 
439  // Callback for parameter settings
440  rcl_interfaces::msg::SetParametersResult on_set_parameters(
441  const std::vector<rclcpp::Parameter> & parameters)
442  {
443  rcl_interfaces::msg::SetParametersResult result;
444  result.successful = true;
445  for (const auto & param : parameters) {
446  if (param.get_name() == "use_sim_time" && param.get_type() == rclcpp::PARAMETER_BOOL) {
447  if (param.as_bool() && !(clocks_state_.are_all_clocks_rcl_ros_time())) {
448  result.successful = false;
449  result.reason =
450  "use_sim_time parameter can't be true while clocks are not all of RCL_ROS_TIME type";
451  RCLCPP_ERROR(
452  logger_,
453  "use_sim_time parameter can't be true while clocks are not all of RCL_ROS_TIME type");
454  }
455  }
456  }
457  return result;
458  }
459 
460  // Callback for post parameter updates
461  void post_set_parameters(const std::vector<rclcpp::Parameter> & parameters)
462  {
463  // "use_sim_time" has been set, so just applys it to internal states
464  for (const auto & param : parameters) {
465  if (param.get_name() == "use_sim_time") {
466  if (param.as_bool()) {
467  sim_time_parameter_state_ = true;
468  clocks_state_.enable_ros_time();
469  create_clock_sub();
470  } else {
471  sim_time_parameter_state_ = false;
472  destroy_clock_sub();
473  clocks_state_.disable_ros_time();
474  }
475  }
476  }
477  }
478 
479  bool sim_time_parameter_state_ = false;
480 };
481 
483  const std::shared_ptr<rclcpp::Node> & node,
484  const rclcpp::QoS & qos,
485  bool use_clock_thread)
486 : TimeSource(qos, use_clock_thread)
487 {
488  attachNode(node);
489 }
490 
492  const rclcpp::QoS & qos,
493  bool use_clock_thread)
494 : constructed_use_clock_thread_(use_clock_thread),
495  constructed_qos_(qos)
496 {
497  node_state_ = std::make_shared<NodeState>(qos, use_clock_thread);
498 }
499 
500 void TimeSource::attachNode(const rclcpp::Node::SharedPtr & node)
501 {
502  node_state_->set_use_clock_thread(node->get_node_options().use_clock_thread());
503  attachNode(
504  node->get_node_base_interface(),
505  node->get_node_topics_interface(),
506  node->get_node_graph_interface(),
507  node->get_node_services_interface(),
508  node->get_node_logging_interface(),
509  node->get_node_clock_interface(),
510  node->get_node_parameters_interface());
511 }
512 
514  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface,
515  rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr node_topics_interface,
516  rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph_interface,
517  rclcpp::node_interfaces::NodeServicesInterface::SharedPtr node_services_interface,
518  rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface,
519  rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_interface,
520  rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_interface)
521 {
522  node_state_->attachNode(
523  std::move(node_base_interface),
524  std::move(node_topics_interface),
525  std::move(node_graph_interface),
526  std::move(node_services_interface),
527  std::move(node_logging_interface),
528  std::move(node_clock_interface),
529  std::move(node_parameters_interface));
530 }
531 
533 {
534  node_state_.reset();
535  node_state_ = std::make_shared<NodeState>(
536  constructed_qos_,
537  constructed_use_clock_thread_);
538 }
539 
540 void TimeSource::attachClock(const std::shared_ptr<rclcpp::Clock> & clock)
541 {
542  node_state_->attachClock(clock);
543 }
544 
545 void TimeSource::detachClock(const std::shared_ptr<rclcpp::Clock> & clock)
546 {
547  node_state_->detachClock(clock);
548 }
549 
551 {
552  return node_state_->get_use_clock_thread();
553 }
554 
555 void TimeSource::set_use_clock_thread(bool use_clock_thread)
556 {
557  node_state_->set_use_clock_thread(use_clock_thread);
558 }
559 
561 {
562  return node_state_->clock_thread_is_joinable();
563 }
564 
566 {
567 }
568 
569 } // namespace rclcpp
Store the type and value of a parameter.
RCLCPP_PUBLIC ParameterType get_type() const
Return an enum indicating the type of the set value.
Encapsulation of Quality of Service settings.
Definition: qos.hpp:114
Options that are passed in subscription/publisher constructor to specify QoSConfigurability.
Subscription implementation, templated on the type of message this subscription receives.
RCLCPP_PUBLIC bool get_use_clock_thread()
Get whether a separate clock thread is used or not.
RCLCPP_PUBLIC void attachNode(const rclcpp::Node::SharedPtr &node)
Attach node to the time source.
RCLCPP_PUBLIC void set_use_clock_thread(bool use_clock_thread)
Set whether to use a separate clock thread or not.
RCLCPP_PUBLIC bool clock_thread_is_joinable()
Check if the clock thread is joinable.
RCLCPP_PUBLIC ~TimeSource()
TimeSource Destructor.
RCLCPP_PUBLIC void attachClock(const rclcpp::Clock::SharedPtr &clock)
Attach a clock to the time source to be updated.
RCLCPP_PUBLIC void detachNode()
Detach the node from the time source.
RCLCPP_PUBLIC TimeSource(const rclcpp::Node::SharedPtr &node, const rclcpp::QoS &qos=rclcpp::ClockQoS(), bool use_clock_thread=true)
Constructor.
RCLCPP_PUBLIC void detachClock(const rclcpp::Clock::SharedPtr &clock)
Detach a clock from the time source.
RCLCPP_PUBLIC rcl_time_point_value_t nanoseconds() const
Get the nanoseconds since epoch.
Definition: time.cpp:215
Versions of rosidl_typesupport_cpp::get_message_type_support_handle that handle adapted types.
RCLCPP_PUBLIC std::string to_string(const FutureReturnCode &future_return_code)
String conversion function for FutureReturnCode.
RCLCPP_PUBLIC Logger get_logger(const std::string &name)
Return a named logger.
Definition: logger.cpp:32
Options to be passed to the executor constructor.
rclcpp::CallbackGroup::SharedPtr callback_group
The callback group for this subscription. NULL to use the default callback group.
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_enable_ros_time_override(rcl_clock_t *clock)
Enable the ROS time abstraction override.
Definition: time.c:297
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_disable_ros_time_override(rcl_clock_t *clock)
Disable the ROS time abstraction override.
Definition: time.c:319
@ RCL_ROS_TIME
Use ROS time.
Definition: time.h:66
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_set_ros_time_override(rcl_clock_t *clock, rcl_time_point_value_t time_value)
Set the current time for this RCL_ROS_TIME time source.
Definition: time.c:359
#define RCL_RET_OK
Success return code.
Definition: types.h:27