Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
lifecycle_node.hpp
1 // Copyright (c) 2025 Open Navigation LLC
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 NAV2_ROS_COMMON__LIFECYCLE_NODE_HPP_
16 #define NAV2_ROS_COMMON__LIFECYCLE_NODE_HPP_
17 
18 #include <memory>
19 #include <string>
20 #include <thread>
21 #include <vector>
22 #include <utility>
23 #include <chrono>
24 
25 #include "lifecycle_msgs/msg/state.hpp"
26 #include "nav2_ros_common/node_utils.hpp"
27 #include "rcl_interfaces/msg/parameter_descriptor.hpp"
28 #include "nav2_ros_common/node_thread.hpp"
29 #include "rclcpp_lifecycle/lifecycle_node.hpp"
30 #include "rclcpp/callback_group.hpp"
31 #include "rclcpp/context.hpp"
32 #include "rclcpp/create_timer.hpp"
33 #include "rclcpp/logging.hpp"
34 #include "rclcpp/node_options.hpp"
35 #include "rclcpp/parameter.hpp"
36 #include "rclcpp/qos.hpp"
37 #include "rclcpp/timer.hpp"
38 #include "bondcpp/bond.hpp"
39 #include "bond/msg/constants.hpp"
40 #include "nav2_ros_common/interface_factories.hpp"
41 #include "nav2_ros_common/rate.hpp"
42 
43 namespace nav2
44 {
45 
46 using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
47 using namespace std::chrono_literals; // NOLINT
48 
53 class LifecycleNode : public rclcpp_lifecycle::LifecycleNode
54 {
55 public:
56  using SharedPtr = std::shared_ptr<nav2::LifecycleNode>;
57  using WeakPtr = std::weak_ptr<nav2::LifecycleNode>;
58  using SharedConstPointer = std::shared_ptr<const nav2::LifecycleNode>;
59 
67  const std::string & node_name,
68  const std::string & ns,
69  const rclcpp::NodeOptions & options = rclcpp::NodeOptions())
70  : rclcpp_lifecycle::LifecycleNode(node_name, ns, options, getEnableLifecycleServices(options))
71  {
72  // server side never times out from lifecycle manager
73  this->declare_parameter(bond::msg::Constants::DISABLE_HEARTBEAT_TIMEOUT_PARAM, true);
74  this->set_parameter(
75  rclcpp::Parameter(
76  bond::msg::Constants::DISABLE_HEARTBEAT_TIMEOUT_PARAM, true));
77 
78  bond_heartbeat_period = this->declare_or_get_parameter<double>("bond_heartbeat_period", 0.25);
79  bool autostart_node = this->declare_or_get_parameter("autostart_node", false);
80  if (autostart_node) {
81  autostart();
82  }
83 
84  printLifecycleNodeNotification();
85 
86  register_rcl_preshutdown_callback();
87  }
88 
95  const std::string & node_name,
96  const rclcpp::NodeOptions & options = rclcpp::NodeOptions())
97  : nav2::LifecycleNode(node_name, "", options)
98  {}
99 
100  virtual ~LifecycleNode()
101  {
102  RCLCPP_INFO(get_logger(), "Destroying");
103 
104  runCleanups();
105 
106  if (rcl_preshutdown_cb_handle_) {
107  rclcpp::Context::SharedPtr context = get_node_base_interface()->get_context();
108  context->remove_pre_shutdown_callback(*(rcl_preshutdown_cb_handle_.get()));
109  rcl_preshutdown_cb_handle_.reset();
110  }
111  }
112 
121  template<typename ParameterT>
122  inline ParameterT declare_or_get_parameter(
123  const std::string & parameter_name,
124  const ParameterDescriptor & parameter_descriptor = ParameterDescriptor())
125  {
126  return nav2::declare_or_get_parameter<ParameterT>(
127  this, parameter_name, parameter_descriptor);
128  }
129 
139  template<typename ParamType>
140  inline ParamType declare_or_get_parameter(
141  const std::string & parameter_name,
142  const ParamType & default_value,
143  const ParameterDescriptor & parameter_descriptor = ParameterDescriptor())
144  {
145  return nav2::declare_or_get_parameter(
146  this, parameter_name,
147  default_value, parameter_descriptor);
148  }
149 
158  template<
159  typename MessageT,
160  typename CallbackT>
161  typename nav2::Subscription<MessageT>::SharedPtr
163  const std::string & topic_name,
164  CallbackT && callback,
165  const rclcpp::QoS & qos = nav2::qos::StandardTopicQoS(),
166  const rclcpp::CallbackGroup::SharedPtr & callback_group = nullptr)
167  {
168  return nav2::interfaces::create_subscription<MessageT>(
169  shared_from_this(), topic_name,
170  std::forward<CallbackT>(callback), qos, callback_group);
171  }
172 
181  template<typename MessageT>
182  typename nav2::Publisher<MessageT>::SharedPtr
184  const std::string & topic_name,
185  const rclcpp::QoS & qos = nav2::qos::StandardTopicQoS(),
186  const rclcpp::CallbackGroup::SharedPtr & callback_group = nullptr,
187  rclcpp::PublisherMatchedCallbackType matched_callback = nullptr)
188  {
189  auto pub = nav2::interfaces::create_publisher<MessageT>(
190  shared_from_this(), topic_name, qos, callback_group, matched_callback);
191  this->add_managed_entity(pub);
192 
193  // Automatically activate the publisher if the node is already active
194  if (get_current_state().id() ==
195  lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
196  {
197  pub->on_activate();
198  }
199 
200  return pub;
201  }
202 
209  template<typename ServiceT>
210  typename nav2::ServiceClient<ServiceT>::SharedPtr
212  const std::string & service_name,
213  bool use_internal_executor = false)
214  {
215  return nav2::interfaces::create_client<ServiceT>(
216  shared_from_this(), service_name, use_internal_executor);
217  }
218 
226  template<typename ServiceT>
227  typename nav2::ServiceServer<ServiceT>::SharedPtr
229  const std::string & service_name,
230  typename nav2::ServiceServer<ServiceT>::CallbackType cb,
231  rclcpp::CallbackGroup::SharedPtr callback_group = nullptr)
232  {
233  return nav2::interfaces::create_service<ServiceT>(
234  shared_from_this(), service_name, cb, callback_group);
235  }
236 
244  template<typename DurationRepT, typename DurationT, typename CallbackT>
245  typename rclcpp::GenericTimer<CallbackT>::SharedPtr
247  std::chrono::duration<DurationRepT, DurationT> period,
248  CallbackT callback,
249  rclcpp::CallbackGroup::SharedPtr group = nullptr)
250  {
251  return rclcpp::create_timer(
252  nav2::selectSteadyOrSimClock(this),
253  period,
254  std::move(callback),
255  group,
256  this->get_node_base_interface().get(),
257  this->get_node_timers_interface().get());
258  }
259 
270  template<typename ActionT>
271  typename nav2::SimpleActionServer<ActionT>::SharedPtr
273  const std::string & action_name,
274  typename nav2::SimpleActionServer<ActionT>::ExecuteCallback execute_callback,
275  typename nav2::SimpleActionServer<ActionT>::GoalReceivedCallback goal_received_callback =
276  nullptr,
277  typename nav2::SimpleActionServer<ActionT>::CompletionCallback compl_cb = nullptr,
278  std::chrono::milliseconds server_timeout = std::chrono::milliseconds(500),
279  bool spin_thread = false,
280  const bool realtime = false)
281  {
282  return nav2::interfaces::create_action_server<ActionT>(
283  shared_from_this(), action_name, execute_callback,
284  goal_received_callback, compl_cb, server_timeout, spin_thread, realtime);
285  }
286 
293  template<typename ActionT>
294  typename nav2::ActionClient<ActionT>::SharedPtr
296  const std::string & action_name,
297  rclcpp::CallbackGroup::SharedPtr callback_group = nullptr)
298  {
299  return nav2::interfaces::create_action_client<ActionT>(
300  shared_from_this(), action_name, callback_group);
301  }
302 
306  nav2::LifecycleNode::SharedPtr shared_from_this()
307  {
308  return std::static_pointer_cast<nav2::LifecycleNode>(
309  rclcpp_lifecycle::LifecycleNode::shared_from_this());
310  }
311 
315  nav2::LifecycleNode::WeakPtr weak_from_this()
316  {
317  return std::static_pointer_cast<nav2::LifecycleNode>(
318  rclcpp_lifecycle::LifecycleNode::shared_from_this());
319  }
320 
327  nav2::CallbackReturn on_error(const rclcpp_lifecycle::State & /*state*/)
328  {
329  RCLCPP_FATAL(
330  get_logger(),
331  "Lifecycle node %s does not have error state implemented", get_name());
332  return nav2::CallbackReturn::SUCCESS;
333  }
334 
338  void autostart()
339  {
340  using lifecycle_msgs::msg::State;
341  autostart_timer_ = this->create_timer(
342  0s,
343  [this]() -> void {
344  autostart_timer_->cancel();
345  RCLCPP_INFO(get_logger(), "Auto-starting node: %s", this->get_name());
346  if (configure().id() != State::PRIMARY_STATE_INACTIVE) {
347  RCLCPP_ERROR(
348  get_logger(), "Auto-starting node %s failed to configure!", this->get_name());
349  return;
350  }
351  if (activate().id() != State::PRIMARY_STATE_ACTIVE) {
352  RCLCPP_ERROR(
353  get_logger(), "Auto-starting node %s failed to activate!", this->get_name());
354  }
355  });
356  }
357 
363  virtual void on_rcl_preshutdown()
364  {
365  RCLCPP_INFO(
366  get_logger(), "Running Nav2 LifecycleNode rcl preshutdown (%s)",
367  this->get_name());
368 
369  runCleanups();
370 
371  destroyBond();
372  }
373 
377  void createBond()
378  {
379  if (bond_heartbeat_period > 0.0) {
380  RCLCPP_INFO(get_logger(), "Creating bond (%s) to lifecycle manager.", this->get_name());
381 
382  bond_ = std::make_shared<bond::Bond>(
383  std::string("bond"),
384  this->get_name(),
385  shared_from_this());
386 
387  bond_->setHeartbeatPeriod(bond_heartbeat_period);
388  bond_->setHeartbeatTimeout(4.0);
389  bond_->start();
390  }
391  }
392 
396  void destroyBond()
397  {
398  if (bond_heartbeat_period > 0.0) {
399  RCLCPP_INFO(get_logger(), "Destroying bond (%s) to lifecycle manager.", this->get_name());
400 
401  if (bond_) {
402  bond_.reset();
403  }
404  }
405  }
406 
407 protected:
412  {
413  RCLCPP_INFO(
414  get_logger(),
415  "\n\t%s lifecycle node launched. \n"
416  "\tWaiting on external lifecycle transitions to activate\n"
417  "\tSee https://design.ros2.org/articles/node_lifecycle.html for more information.",
418  get_name());
419  }
420 
427  {
428  rclcpp::Context::SharedPtr context = get_node_base_interface()->get_context();
429 
430  rcl_preshutdown_cb_handle_ = std::make_unique<rclcpp::PreShutdownCallbackHandle>(
431  context->add_pre_shutdown_callback(
432  std::bind(&LifecycleNode::on_rcl_preshutdown, this))
433  );
434  }
435 
439  void runCleanups()
440  {
441  /*
442  * In case this lifecycle node wasn't properly shut down, do it here.
443  * We will give the user some ability to clean up properly here, but it's
444  * best effort; i.e. we aren't trying to account for all possible states.
445  */
446  if (get_current_state().id() ==
447  lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
448  {
449  this->deactivate();
450  }
451 
452  if (get_current_state().id() ==
453  lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
454  {
455  this->cleanup();
456  }
457  }
458 
459  // Connection to tell that server is still up
460  std::unique_ptr<rclcpp::PreShutdownCallbackHandle> rcl_preshutdown_cb_handle_{nullptr};
461  std::shared_ptr<bond::Bond> bond_{nullptr};
462  double bond_heartbeat_period{0.1};
463  rclcpp::TimerBase::SharedPtr autostart_timer_;
464 
465 private:
471  static bool getEnableLifecycleServices(const rclcpp::NodeOptions & options)
472  {
473  // Check if the parameter is explicitly set in NodeOptions
474  for (const auto & param : options.parameter_overrides()) {
475  if (param.get_name() == "enable_lifecycle_services") {
476  return param.as_bool();
477  }
478  }
479  // Default to true if not specified
480  return true;
481  }
482 };
483 
484 } // namespace nav2
485 
486 #endif // NAV2_ROS_COMMON__LIFECYCLE_NODE_HPP_
A lifecycle node wrapper to enable common Nav2 needs such as manipulating parameters.
nav2::CallbackReturn on_error(const rclcpp_lifecycle::State &)
Abstracted on_error state transition callback, since unimplemented as of 2020 in the managed ROS2 nod...
void autostart()
Automatically configure and active the node.
void destroyBond()
Destroy bond connection to lifecycle manager.
nav2::LifecycleNode::SharedPtr shared_from_this()
Get a shared pointer of this.
void printLifecycleNodeNotification()
Print notifications for lifecycle node.
nav2::Publisher< MessageT >::SharedPtr create_publisher(const std::string &topic_name, const rclcpp::QoS &qos=nav2::qos::StandardTopicQoS(), const rclcpp::CallbackGroup::SharedPtr &callback_group=nullptr, rclcpp::PublisherMatchedCallbackType matched_callback=nullptr)
Create a publisher to a topic using Nav2 QoS profiles and PublisherOptions.
rclcpp::GenericTimer< CallbackT >::SharedPtr create_timer(std::chrono::duration< DurationRepT, DurationT > period, CallbackT callback, rclcpp::CallbackGroup::SharedPtr group=nullptr)
Create a sim-time-aware timer for Nav2 lifecycle nodes.
ParameterT declare_or_get_parameter(const std::string &parameter_name, const ParameterDescriptor &parameter_descriptor=ParameterDescriptor())
Declares or gets a parameter with specified type (not value). If the parameter is already declared,...
nav2::SimpleActionServer< ActionT >::SharedPtr create_action_server(const std::string &action_name, typename nav2::SimpleActionServer< ActionT >::ExecuteCallback execute_callback, typename nav2::SimpleActionServer< ActionT >::GoalReceivedCallback goal_received_callback=nullptr, typename nav2::SimpleActionServer< ActionT >::CompletionCallback compl_cb=nullptr, std::chrono::milliseconds server_timeout=std::chrono::milliseconds(500), bool spin_thread=false, const bool realtime=false)
Create a SimpleActionServer to host with an action.
nav2::ServiceClient< ServiceT >::SharedPtr create_client(const std::string &service_name, bool use_internal_executor=false)
Create a ServiceClient to interface with a service.
void createBond()
Create bond connection to lifecycle manager.
LifecycleNode(const std::string &node_name, const std::string &ns, const rclcpp::NodeOptions &options=rclcpp::NodeOptions())
A lifecycle node constructor.
virtual void on_rcl_preshutdown()
Perform preshutdown activities before our Context is shutdown. Note that this is related to our Conte...
nav2::Subscription< MessageT >::SharedPtr create_subscription(const std::string &topic_name, CallbackT &&callback, const rclcpp::QoS &qos=nav2::qos::StandardTopicQoS(), const rclcpp::CallbackGroup::SharedPtr &callback_group=nullptr)
Create a subscription to a topic using Nav2 QoS profiles and SubscriptionOptions.
nav2::ServiceServer< ServiceT >::SharedPtr create_service(const std::string &service_name, typename nav2::ServiceServer< ServiceT >::CallbackType cb, rclcpp::CallbackGroup::SharedPtr callback_group=nullptr)
Create a ServiceServer to host with a service.
nav2::LifecycleNode::WeakPtr weak_from_this()
Get a shared pointer of this.
void register_rcl_preshutdown_callback()
ParamType declare_or_get_parameter(const std::string &parameter_name, const ParamType &default_value, const ParameterDescriptor &parameter_descriptor=ParameterDescriptor())
Declares or gets a parameter. If the parameter is already declared, returns its value; otherwise decl...
nav2::ActionClient< ActionT >::SharedPtr create_action_client(const std::string &action_name, rclcpp::CallbackGroup::SharedPtr callback_group=nullptr)
Create a ActionClient to call an action using.
LifecycleNode(const std::string &node_name, const rclcpp::NodeOptions &options=rclcpp::NodeOptions())
A lifecycle node constructor with no namespace.
A QoS profile for standard reliable topics with a history of 10 messages.