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/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 
173  template<typename MessageT>
174  typename nav2::Publisher<MessageT>::SharedPtr
176  const std::string & topic_name,
177  const rclcpp::QoS & qos = nav2::qos::StandardTopicQoS(),
178  const rclcpp::CallbackGroup::SharedPtr & callback_group = nullptr)
179  {
180  auto pub = nav2::interfaces::create_publisher<MessageT>(
181  shared_from_this(), topic_name, qos, callback_group);
182  this->add_managed_entity(pub);
183 
184  // Automatically activate the publisher if the node is already active
185  if (get_current_state().id() ==
186  lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
187  {
188  pub->on_activate();
189  }
190 
191  return pub;
192  }
193 
200  template<typename ServiceT>
201  typename nav2::ServiceClient<ServiceT>::SharedPtr
203  const std::string & service_name,
204  bool use_internal_executor = false)
205  {
206  return nav2::interfaces::create_client<ServiceT>(
207  shared_from_this(), service_name, use_internal_executor);
208  }
209 
217  template<typename ServiceT>
218  typename nav2::ServiceServer<ServiceT>::SharedPtr
220  const std::string & service_name,
221  typename nav2::ServiceServer<ServiceT>::CallbackType cb,
222  rclcpp::CallbackGroup::SharedPtr callback_group = nullptr)
223  {
224  return nav2::interfaces::create_service<ServiceT>(
225  shared_from_this(), service_name, cb, callback_group);
226  }
227 
235  template<typename DurationRepT, typename DurationT, typename CallbackT>
236  typename rclcpp::GenericTimer<CallbackT>::SharedPtr
238  std::chrono::duration<DurationRepT, DurationT> period,
239  CallbackT callback,
240  rclcpp::CallbackGroup::SharedPtr group = nullptr)
241  {
242  return rclcpp::create_timer(
243  nav2::selectSteadyOrSimClock(this),
244  period,
245  std::move(callback),
246  group,
247  this->get_node_base_interface().get(),
248  this->get_node_timers_interface().get());
249  }
250 
261  template<typename ActionT>
262  typename nav2::SimpleActionServer<ActionT>::SharedPtr
264  const std::string & action_name,
265  typename nav2::SimpleActionServer<ActionT>::ExecuteCallback execute_callback,
266  typename nav2::SimpleActionServer<ActionT>::GoalReceivedCallback goal_received_callback =
267  nullptr,
268  typename nav2::SimpleActionServer<ActionT>::CompletionCallback compl_cb = nullptr,
269  std::chrono::milliseconds server_timeout = std::chrono::milliseconds(500),
270  bool spin_thread = false,
271  const bool realtime = false)
272  {
273  return nav2::interfaces::create_action_server<ActionT>(
274  shared_from_this(), action_name, execute_callback,
275  goal_received_callback, compl_cb, server_timeout, spin_thread, realtime);
276  }
277 
284  template<typename ActionT>
285  typename nav2::ActionClient<ActionT>::SharedPtr
287  const std::string & action_name,
288  rclcpp::CallbackGroup::SharedPtr callback_group = nullptr)
289  {
290  return nav2::interfaces::create_action_client<ActionT>(
291  shared_from_this(), action_name, callback_group);
292  }
293 
297  nav2::LifecycleNode::SharedPtr shared_from_this()
298  {
299  return std::static_pointer_cast<nav2::LifecycleNode>(
300  rclcpp_lifecycle::LifecycleNode::shared_from_this());
301  }
302 
306  nav2::LifecycleNode::WeakPtr weak_from_this()
307  {
308  return std::static_pointer_cast<nav2::LifecycleNode>(
309  rclcpp_lifecycle::LifecycleNode::shared_from_this());
310  }
311 
318  nav2::CallbackReturn on_error(const rclcpp_lifecycle::State & /*state*/)
319  {
320  RCLCPP_FATAL(
321  get_logger(),
322  "Lifecycle node %s does not have error state implemented", get_name());
323  return nav2::CallbackReturn::SUCCESS;
324  }
325 
329  void autostart()
330  {
331  using lifecycle_msgs::msg::State;
332  autostart_timer_ = this->create_timer(
333  0s,
334  [this]() -> void {
335  autostart_timer_->cancel();
336  RCLCPP_INFO(get_logger(), "Auto-starting node: %s", this->get_name());
337  if (configure().id() != State::PRIMARY_STATE_INACTIVE) {
338  RCLCPP_ERROR(
339  get_logger(), "Auto-starting node %s failed to configure!", this->get_name());
340  return;
341  }
342  if (activate().id() != State::PRIMARY_STATE_ACTIVE) {
343  RCLCPP_ERROR(
344  get_logger(), "Auto-starting node %s failed to activate!", this->get_name());
345  }
346  });
347  }
348 
354  virtual void on_rcl_preshutdown()
355  {
356  RCLCPP_INFO(
357  get_logger(), "Running Nav2 LifecycleNode rcl preshutdown (%s)",
358  this->get_name());
359 
360  runCleanups();
361 
362  destroyBond();
363  }
364 
368  void createBond()
369  {
370  if (bond_heartbeat_period > 0.0) {
371  RCLCPP_INFO(get_logger(), "Creating bond (%s) to lifecycle manager.", this->get_name());
372 
373  bond_ = std::make_shared<bond::Bond>(
374  std::string("bond"),
375  this->get_name(),
376  shared_from_this());
377 
378  bond_->setHeartbeatPeriod(bond_heartbeat_period);
379  bond_->setHeartbeatTimeout(4.0);
380  bond_->start();
381  }
382  }
383 
387  void destroyBond()
388  {
389  if (bond_heartbeat_period > 0.0) {
390  RCLCPP_INFO(get_logger(), "Destroying bond (%s) to lifecycle manager.", this->get_name());
391 
392  if (bond_) {
393  bond_.reset();
394  }
395  }
396  }
397 
398 protected:
403  {
404  RCLCPP_INFO(
405  get_logger(),
406  "\n\t%s lifecycle node launched. \n"
407  "\tWaiting on external lifecycle transitions to activate\n"
408  "\tSee https://design.ros2.org/articles/node_lifecycle.html for more information.",
409  get_name());
410  }
411 
418  {
419  rclcpp::Context::SharedPtr context = get_node_base_interface()->get_context();
420 
421  rcl_preshutdown_cb_handle_ = std::make_unique<rclcpp::PreShutdownCallbackHandle>(
422  context->add_pre_shutdown_callback(
423  std::bind(&LifecycleNode::on_rcl_preshutdown, this))
424  );
425  }
426 
430  void runCleanups()
431  {
432  /*
433  * In case this lifecycle node wasn't properly shut down, do it here.
434  * We will give the user some ability to clean up properly here, but it's
435  * best effort; i.e. we aren't trying to account for all possible states.
436  */
437  if (get_current_state().id() ==
438  lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
439  {
440  this->deactivate();
441  }
442 
443  if (get_current_state().id() ==
444  lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
445  {
446  this->cleanup();
447  }
448  }
449 
450  // Connection to tell that server is still up
451  std::unique_ptr<rclcpp::PreShutdownCallbackHandle> rcl_preshutdown_cb_handle_{nullptr};
452  std::shared_ptr<bond::Bond> bond_{nullptr};
453  double bond_heartbeat_period{0.1};
454  rclcpp::TimerBase::SharedPtr autostart_timer_;
455 
456 private:
462  static bool getEnableLifecycleServices(const rclcpp::NodeOptions & options)
463  {
464  // Check if the parameter is explicitly set in NodeOptions
465  for (const auto & param : options.parameter_overrides()) {
466  if (param.get_name() == "enable_lifecycle_services") {
467  return param.as_bool();
468  }
469  }
470  // Default to true if not specified
471  return true;
472  }
473 };
474 
475 } // namespace nav2
476 
477 #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)
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.