ARTICLE · 1286382
Nav2 源码(2): LifecycleManager 如何处理导航节点的启动与状态切换
static const char * pub_transition_event_topic = "~/transition_event";static const char * srv_change_state_service = "~/change_state";static const char * srv_get_state_service = "~/get_state";static const char * srv_get_available_states_service = "~/get_available_states";static const char * srv_get_available_transitions_service = "~/get_available_transitions";static const char * srv_get_transition_graph = "~/get_transition_graph";
~/src/ros2/rcl/rcl_lifecycle/src/com_interface.c
pub_transition_event_topic get_state:
消息类型: lifecycle_msgs/srv/GetState;
功能: 获取节点当前的状态;
3,服务功能和 ros2 lifecycle 命令行的对应关系
Ros2 cli 命令 | 底层服务 | 说明 | |
ros2 lifecycle set | change_state | ||
ros2 lifecycle get | 获取节点当前的状态 |
4,举例说明
## 命令行输入
ros2 lifecycle set /planner_server configure
## 等价于
ros2 service call /planner_server/change_state \
lifecycle_msgs/srv/ChangeState \
"{transition: {id: 1, label: 'configure'}}"
三,工具类 LifecycleServiceClient 定义
LifecycleServiceClient 提供一个 Ros2 Client 的封装,创建 change_state 和 get_state 两个 Service 的 Client。
classLifecycleServiceClient{public:explicitLifecycleServiceClient(const std::string & lifecycle_node_name);LifecycleServiceClient(const std::string & lifecycle_node_name,rclcpp::Node::SharedPtr parent_node);/// Trigger a state change/*** 状态转换* transition 状态,定义在 lifecycle_msgs::msg::Transition*/bool change_state(const uint8_t transition,const std::chrono::seconds timeout);bool change_state(std::uint8_t transition);/*** 获取当前状态,状态值定义在 lifecycle_msgs::msg::State*/uint8_t get_state(const std::chrono::seconds timeout = std::chrono::seconds(2));protected:rclcpp::Node::SharedPtr node_;ServiceClient<lifecycle_msgs::srv::ChangeState> change_state_;ServiceClient<lifecycle_msgs::srv::GetState> get_state_;};
第 31 ~ 32 行:分别定义两个 ServiceClient 对象。
然后我们看看 ServiceClient 是怎么个情况。
template<class ServiceT>class ServiceClient{public:explicitServiceClient(const std::string & service_name,const rclcpp::Node::SharedPtr & provided_node): service_name_(service_name), node_(provided_node){callback_group_ = node_->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive,false);callback_group_executor_.add_callback_group(callback_group_, node_->get_node_base_interface());client_ = node_->create_client<ServiceT>(service_name,rclcpp::ServicesQoS().get_rmw_qos_profile(),callback_group_);}using RequestType = typename ServiceT::Request;using ResponseType = typename ServiceT::Response;/// 省略部分代码/*** @brief 执行 request 请求*/boolinvoke(typename RequestType::SharedPtr & request,typename ResponseType::SharedPtr & response){while (!client_->wait_for_service(std::chrono::seconds(1))) {if (!rclcpp::ok()) {throw std::runtime_error(service_name_ + " service client: interrupted while waiting for service");}}auto future_result = client_->async_send_request(request);if (callback_group_executor_.spin_until_future_complete(future_result) !=rclcpp::FutureReturnCode::SUCCESS){// Pending request must be manually cleaned up if execution is interrupted or timed outclient_->remove_pending_request(future_result);return false;}response = future_result.get();return response.get();}/// 省略部分代码protected:std::string service_name_;rclcpp::Node::SharedPtr node_;rclcpp::CallbackGroup::SharedPtr callback_group_;rclcpp::executors::SingleThreadedExecutor callback_group_executor_;typename rclcpp::Client<ServiceT>::SharedPtr client_;};
第 1 行:它是个 template,这样的话,创建 rclcpp::Client 就可以不用关注具体的 srv 类型了;
第 10 ~ 23 行:创建了一个名字叫做 service_name 的 client_;
invoke() 函数:我们这里关注 第 40 ~ 42 行。
Ros2 框架下,这是一个非常典型的调用方式,是 rclcpp::Client 向 Service 发起请求。
向哪个 Service 发起请求呢??我们往回看一点,在前面的构造函数中,已经知道 Service 的名字和类型。这样的话,invoke() 这个函数的功能就显而易见了。
四,回看 LifecycleServiceClient 的细节
LifecycleServiceClient::LifecycleServiceClient(const string & lifecycle_node_name,rclcpp::Node::SharedPtr parent_node): node_(parent_node),change_state_(lifecycle_node_name + "/change_state", node_),get_state_(lifecycle_node_name + "/get_state", node_){// Block until server is uprclcpp::Rate r(20);while(!get_state_.wait_for_service(2s)) {RCLCPP_INFO(node_->get_logger(), "Waiting for service %s...", get_state_.getServiceName().c_str());r.sleep();}}
第 5 行:创建一个 change_state_,这个家伙就是代码块 3 里刚刚提到的 ServiceClient。
假如,传入的 lifecycle_node_name 是 “planner_server”,那么,ServiceClient 里就应该创建一个名字叫 “planner_server/change_state” 的 rclcpp::Client。
第 6 行:同上。
bool LifecycleServiceClient::change_state(std::uint8_t transition){if (!change_state_.wait_for_service(5s)) {throw std::runtime_error("change_state service is not available!");}auto request = std::make_shared<lifecycle_msgs::srv::ChangeState::Request>();auto response = std::make_shared<lifecycle_msgs::srv::ChangeState::Response>();request->transition.id = transition;return change_state_.invoke(request, response);}
第 7 ~ 9 行:准备 request 和 response,把入参封装成请求的格式;
第 10 行:构造函数里准备好的 change_state_,调用前面提到的 invoke(),向 lifecycle-node 的 change_state 服务发起请求。这个服务就会根据传入的参数,调用 lifecycle-node 的回调函数。比如:on_activate()。
小结:
至此,我们已经把 LifecycleServiceClient 怎么触发 lifecycle-node 的回调函数的链路穿起来了。
这个链路串起来之后,我们知道它的底层原理,排查 lifecycle-manager 的一些问题的手段就丰富了。
比如,可以看看 lifecycle-manager 当前管理哪些节点。然后看一下节点的当前的状态。如果状态不符合预期,可以通过 ros2 命令去直接干预。
五,lifecycle-manager 的 startup()
bool LifecycleManager::startup(){message("Starting managed nodes bringup...");if(!changeStateForAllNodes(Transition::TRANSITION_CONFIGURE) ||!changeStateForAllNodes(Transition::TRANSITION_ACTIVATE)){RCLCPP_ERROR(get_logger(), "Failed to bring up all requested nodes. Aborting bringup.");return false;}message("Managed nodes are active");system_active_ = true;createBondTimer();return true;}
第 5 行:调用 changeStateForAllNodes()
我们看一下这个接口的实现:
boolLifecycleManager::changeStateForAllNodes(std::uint8_t transition, bool hard_change){// Hard change will continue even if a node failsif(transition == Transition::TRANSITION_CONFIGURE ||transition == Transition::TRANSITION_ACTIVATE){for(auto & node_name : node_names_) {try {if(!changeStateForNode(node_name, transition)&& !hard_change) {return false;}} catch(const std::runtime_error & e) {RCLCPP_ERROR(get_logger(),"Failed to change state for node: %s. Exception: %s.", node_name.c_str(), e.what());return false;}}} else {// 省略部分代码}return true;}
bool LifecycleManager::changeStateForNode(const std::string & node_name, std::uint8_t transition){message(transition_label_map_[transition] + node_name);if(!node_map_[node_name]->change_state(transition) ||!(node_map_[node_name]->get_state() == transition_state_map_[transition])){RCLCPP_ERROR(get_logger(), "Failed to change state for node: %s", node_name.c_str());return false;}if(transition == Transition::TRANSITION_ACTIVATE) {return createBondConnection(node_name);} else if (transition == Transition::TRANSITION_DEACTIVATE) {bond_map_.erase(node_name);}return true;}
std::map<std::string,std::shared_ptr<nav2_util::LifecycleServiceClient>> node_map_;
node_map_[node_name]->change_state(transition)
懒得画图了,让千问给画了给图。
七,总结
我觉得结合 Nav2 源码(1):LifecycleManager 是怎么工作的? 和本文,基本上可以把 lifecycle-manager 的工作搞清楚。并且,可以了解 lifecycle-node 的一些细节。
后面的 planner-server 也是用到了很多 Ros2 的特性。如果把这两篇提到的代码,和后面的 planner-server 代码涉及到的特性都搞清楚,那么,对 Ros2 框架的理解肯定能上一个台阶,不只是调 API 和调参。
真心佩服 Nav2 的作者们,Ros2 官方文档并没有介绍那么多细节,Nav2 的大神们应该是看过 Ros2 的源码,而且理解非常深,不然怎么可能写出 Nav2 框架。
学习的乐趣大概就在于此吧。
就这样















































































