Nav2 Navigation Stack - rolling  main
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/rclcpp.hpp"
31 #include "bondcpp/bond.hpp"
32 #include "bond/msg/constants.hpp"
33 #include "nav2_ros_common/interface_factories.hpp"
34 #include "nav2_ros_common/rate.hpp"
35 
36 namespace nav2
37 {
38 
39 using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
40 using namespace std::chrono_literals; // NOLINT
41 
46 class LifecycleNode : public rclcpp_lifecycle::LifecycleNode
47 {
48 public:
49  using SharedPtr = std::shared_ptr<nav2::LifecycleNode>;
50  using WeakPtr = std::weak_ptr<nav2::LifecycleNode>;
51  using SharedConstPointer = std::shared_ptr<const nav2::LifecycleNode>;
52 
60  const std::string & node_name,
61  const std::string & ns,
62  const rclcpp::NodeOptions & options = rclcpp::NodeOptions())
63  : rclcpp_lifecycle::LifecycleNode(node_name, ns, options, getEnableLifecycleServices(options))
64  {
65  // server side never times out from lifecycle manager
66  this->declare_parameter(bond::msg::Constants::DISABLE_HEARTBEAT_TIMEOUT_PARAM, true);
67  this->set_parameter(
68  rclcpp::Parameter(
69  bond::msg::Constants::DISABLE_HEARTBEAT_TIMEOUT_PARAM, true));
70 
71  bond_heartbeat_period = this->declare_or_get_parameter<double>("bond_heartbeat_period", 0.25);
72  bool autostart_node = this->declare_or_get_parameter("autostart_node", false);
73  if (autostart_node) {
74  autostart();
75  }
76 
77  printLifecycleNodeNotification();
78 
79  register_rcl_preshutdown_callback();
80  }
81 
88  const std::string & node_name,
89  const rclcpp::NodeOptions & options = rclcpp::NodeOptions())
90  : nav2::LifecycleNode(node_name, "", options)
91  {}
92 
93  virtual ~LifecycleNode()
94  {
95  RCLCPP_INFO(get_logger(), "Destroying");
96 
97  runCleanups();
98 
99  if (rcl_preshutdown_cb_handle_) {
100  rclcpp::Context::SharedPtr context = get_node_base_interface()->get_context();
101  context->remove_pre_shutdown_callback(*(rcl_preshutdown_cb_handle_.get()));
102  rcl_preshutdown_cb_handle_.reset();
103  }
104  }
105 
114  template<typename ParameterT>
115  inline ParameterT declare_or_get_parameter(
116  const std::string & parameter_name,
117  const ParameterDescriptor & parameter_descriptor = ParameterDescriptor())
118  {
119  return nav2::declare_or_get_parameter<ParameterT>(
120  this, parameter_name, parameter_descriptor);
121  }
122 
132  template<typename ParamType>
133  inline ParamType declare_or_get_parameter(
134  const std::string & parameter_name,
135  const ParamType & default_value,
136  const ParameterDescriptor & parameter_descriptor = ParameterDescriptor())
137  {
138  return nav2::declare_or_get_parameter(
139  this, parameter_name,
140  default_value, parameter_descriptor);
141  }
142 
151  template<
152  typename MessageT,
153  typename CallbackT>
154  typename nav2::Subscription<MessageT>::SharedPtr
156  const std::string & topic_name,
157  CallbackT && callback,
158  const rclcpp::QoS & qos = nav2::qos::StandardTopicQoS(),
159  const rclcpp::CallbackGroup::SharedPtr & callback_group = nullptr)
160  {
161  return nav2::interfaces::create_subscription<MessageT>(
162  shared_from_this(), topic_name,
163  std::forward<CallbackT>(callback), qos, callback_group);
164  }
165 
174  template<typename MessageT>
175  typename nav2::Publisher<MessageT>::SharedPtr
177  const std::string & topic_name,
178  const rclcpp::QoS & qos = nav2::qos::StandardTopicQoS(),
179  const rclcpp::CallbackGroup::SharedPtr & callback_group = nullptr,
180  rclcpp::PublisherMatchedCallbackType matched_callback = nullptr)
181  {
182  auto pub = nav2::interfaces::create_publisher<MessageT>(
183  shared_from_this(), topic_name, qos, callback_group, matched_callback);
184  this->add_managed_entity(pub);
185 
186  // Automatically activate the publisher if the node is already active
187  if (get_current_state().id() ==
188  lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
189  {
190  pub->on_activate();
191  }
192 
193  return pub;
194  }
195 
202  template<typename ServiceT>
203  typename nav2::ServiceClient<ServiceT>::SharedPtr
205  const std::string & service_name,
206  bool use_internal_executor = false)
207  {
208  return nav2::interfaces::create_client<ServiceT>(
209  shared_from_this(), service_name, use_internal_executor);
210  }
211 
219  template<typename ServiceT>
220  typename nav2::ServiceServer<ServiceT>::SharedPtr
222  const std::string & service_name,
223  typename nav2::ServiceServer<ServiceT>::CallbackType cb,
224  rclcpp::CallbackGroup::SharedPtr callback_group = nullptr)
225  {
226  return nav2::interfaces::create_service<ServiceT>(
227  shared_from_this(), service_name, cb, callback_group);
228  }
229 
237  template<typename DurationRepT, typename DurationT, typename CallbackT>
238  typename rclcpp::GenericTimer<CallbackT>::SharedPtr
240  std::chrono::duration<DurationRepT, DurationT> period,
241  CallbackT callback,
242  rclcpp::CallbackGroup::SharedPtr group = nullptr)
243  {
244  return rclcpp::create_timer(
245  nav2::selectSteadyOrSimClock(this),
246  period,
247  std::move(callback),
248  group,
249  this->get_node_base_interface().get(),
250  this->get_node_timers_interface().get());
251  }
252 
263  template<typename ActionT>
264  typename nav2::SimpleActionServer<ActionT>::SharedPtr
266  const std::string & action_name,
267  typename nav2::SimpleActionServer<ActionT>::ExecuteCallback execute_callback,
268  typename nav2::SimpleActionServer<ActionT>::GoalReceivedCallback goal_received_callback =
269  nullptr,
270  typename nav2::SimpleActionServer<ActionT>::CompletionCallback compl_cb = nullptr,
271  std::chrono::milliseconds server_timeout = std::chrono::milliseconds(500),
272  bool spin_thread = false,
273  const bool realtime = false)
274  {
275  return nav2::interfaces::create_action_server<ActionT>(
276  shared_from_this(), action_name, execute_callback,
277  goal_received_callback, compl_cb, server_timeout, spin_thread, realtime);
278  }
279 
286  template<typename ActionT>
287  typename nav2::ActionClient<ActionT>::SharedPtr
289  const std::string & action_name,
290  rclcpp::CallbackGroup::SharedPtr callback_group = nullptr)
291  {
292  return nav2::interfaces::create_action_client<ActionT>(
293  shared_from_this(), action_name, callback_group);
294  }
295 
299  nav2::LifecycleNode::SharedPtr shared_from_this()
300  {
301  return std::static_pointer_cast<nav2::LifecycleNode>(
302  rclcpp_lifecycle::LifecycleNode::shared_from_this());
303  }
304 
308  nav2::LifecycleNode::WeakPtr weak_from_this()
309  {
310  return std::static_pointer_cast<nav2::LifecycleNode>(
311  rclcpp_lifecycle::LifecycleNode::shared_from_this());
312  }
313 
320  nav2::CallbackReturn on_error(const rclcpp_lifecycle::State & /*state*/)
321  {
322  RCLCPP_FATAL(
323  get_logger(),
324  "Lifecycle node %s does not have error state implemented", get_name());
325  return nav2::CallbackReturn::SUCCESS;
326  }
327 
331  void autostart()
332  {
333  using lifecycle_msgs::msg::State;
334  autostart_timer_ = this->create_timer(
335  0s,
336  [this]() -> void {
337  autostart_timer_->cancel();
338  RCLCPP_INFO(get_logger(), "Auto-starting node: %s", this->get_name());
339  if (configure().id() != State::PRIMARY_STATE_INACTIVE) {
340  RCLCPP_ERROR(
341  get_logger(), "Auto-starting node %s failed to configure!", this->get_name());
342  return;
343  }
344  if (activate().id() != State::PRIMARY_STATE_ACTIVE) {
345  RCLCPP_ERROR(
346  get_logger(), "Auto-starting node %s failed to activate!", this->get_name());
347  }
348  });
349  }
350 
356  virtual void on_rcl_preshutdown()
357  {
358  RCLCPP_INFO(
359  get_logger(), "Running Nav2 LifecycleNode rcl preshutdown (%s)",
360  this->get_name());
361 
362  runCleanups();
363 
364  destroyBond();
365  }
366 
370  void createBond()
371  {
372  if (bond_heartbeat_period > 0.0) {
373  RCLCPP_INFO(get_logger(), "Creating bond (%s) to lifecycle manager.", this->get_name());
374 
375  bond_ = std::make_shared<bond::Bond>(
376  std::string("bond"),
377  this->get_name(),
378  shared_from_this());
379 
380  bond_->setHeartbeatPeriod(bond_heartbeat_period);
381  bond_->setHeartbeatTimeout(4.0);
382  bond_->start();
383  }
384  }
385 
389  void destroyBond()
390  {
391  if (bond_heartbeat_period > 0.0) {
392  RCLCPP_INFO(get_logger(), "Destroying bond (%s) to lifecycle manager.", this->get_name());
393 
394  if (bond_) {
395  bond_.reset();
396  }
397  }
398  }
399 
400 protected:
405  {
406  RCLCPP_INFO(
407  get_logger(),
408  "\n\t%s lifecycle node launched. \n"
409  "\tWaiting on external lifecycle transitions to activate\n"
410  "\tSee https://design.ros2.org/articles/node_lifecycle.html for more information.",
411  get_name());
412  }
413 
420  {
421  rclcpp::Context::SharedPtr context = get_node_base_interface()->get_context();
422 
423  rcl_preshutdown_cb_handle_ = std::make_unique<rclcpp::PreShutdownCallbackHandle>(
424  context->add_pre_shutdown_callback(
425  std::bind(&LifecycleNode::on_rcl_preshutdown, this))
426  );
427  }
428 
432  void runCleanups()
433  {
434  /*
435  * In case this lifecycle node wasn't properly shut down, do it here.
436  * We will give the user some ability to clean up properly here, but it's
437  * best effort; i.e. we aren't trying to account for all possible states.
438  */
439  if (get_current_state().id() ==
440  lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
441  {
442  this->deactivate();
443  }
444 
445  if (get_current_state().id() ==
446  lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
447  {
448  this->cleanup();
449  }
450  }
451 
452  // Connection to tell that server is still up
453  std::unique_ptr<rclcpp::PreShutdownCallbackHandle> rcl_preshutdown_cb_handle_{nullptr};
454  std::shared_ptr<bond::Bond> bond_{nullptr};
455  double bond_heartbeat_period{0.1};
456  rclcpp::TimerBase::SharedPtr autostart_timer_;
457 
458 private:
464  static bool getEnableLifecycleServices(const rclcpp::NodeOptions & options)
465  {
466  // Check if the parameter is explicitly set in NodeOptions
467  for (const auto & param : options.parameter_overrides()) {
468  if (param.get_name() == "enable_lifecycle_services") {
469  return param.as_bool();
470  }
471  }
472  // Default to true if not specified
473  return true;
474  }
475 };
476 
477 } // namespace nav2
478 
479 #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.