夜雨聆风学习资料网

ARTICLE · 1286382

Nav2 源码(2): LifecycleManager 如何处理导航节点的启动与状态切换

Nav2 源码(2): LifecycleManager 如何处理导航节点的启动与状态切换
一,背景
    上一篇 Nav2 源码(1):LifecycleManager 是怎么工作的? 只写了 lifecycle-manager 通过回调触发管理函数的调用,但是,细节并没有提到,我觉得还是有必要搞清楚。
    我觉得本文值得仔细看看。
    接下来的内容涉及到 lifecycle-node 的一些实现细节,而不只是 demo 呈现的几种状态机,这些细节是 Ros2 官网没有提到过的。
    本文提及的 Ros2 是 humble 版本。没有特别提示,本公众号所有的 Ros2 源码都是这个版本。
    Nav2 是 humble 分支,版本是 1.1.20-7-g3c3db59d。
    不同版本的代码可能有差异。
    不要完全相信 AI 给出的东西,很可能跟源码对不上。
二,lifecycle-node 的默认服务
1,默认服务
在 lifecycle-node 的构造时,它会创建默认的几个 Ros2 Service,这几个服务是 lifecycle-node 状态切换、查询的核心所在。
我们先搞清楚有哪些服务,提供了什么功能,然后再举几个例子。这部分了解了,后面再看 lifecycle-manager 触发 planner-server 的回调,就会很容易理解。
代码块 1
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";
以上代码来源于 Ros2 源码包中的如下文件中:
~/src/ros2/rcl/rcl_lifecycle/src/com_interface.c
其中,~/src/ 是我本地目录,ros2 开始是 Ros2 源码包自己的目录。
lifecycle-node 构造函数用到这些 char* 的代码调用链路比较深,且不是当前这篇分享的关注重点,所以,知道他们的存在就行了。
这些 char* 会有名称解析,把 "~" 部分换成服务节点的名称。例如,planner-server 节点也是 lifecycle-node,它最终创建的服务就是 /planner_server/change_state 等。看一下我本地的查询结果:
图片 1
pub_transition_event_topic 
这个家伙是个例外,他是个 topic,忘截图了。。。
2,服务功能简介
    这里就介绍 2 个服务
change_state:
消息类型: lifecycle_msgs/srv/ChangeState;
功能: 接收 client 的请求,执行一次状态转换;

get_state:

    消息类型: lifecycle_msgs/srv/GetState;

    功能: 获取节点当前的状态;

3,服务功能和 ros2 lifecycle 命令行的对应关系

Ros2 cli 命令

底层服务

说明

ros2 lifecycle set 

change_state

执行一次状态转换

ros2 lifecycle get

get_state

获取节点当前的状态

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。

代码块 2
classLifecycleServiceClient{public:  explicit     LifecycleServiceClient(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 是怎么个情况。

代码块 3
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 out      client_->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 的细节

代码块 4
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 up  rclcpp::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 行:同上。

代码块 5
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() 

代码块 6
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()

我们看一下这个接口的实现:

代码块 7
boolLifecycleManager::changeStateForAllNodes(std::uint8_t transition, bool hard_change){  // Hard change will continue even if a node fails  if(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;}
第 10 行:调用 changeStateForNode()
我们继续看这个接口的实现:
代码块 8
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;}
第 4 行:我们先看一下成员变量的信息:
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 框架。

    学习的乐趣大概就在于此吧。

就这样

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料