ARTICLE · 1266699
Nav2 源码(1):LifecycleManager 是怎么工作的?


那么,nav2_params.yaml 又是在哪里引用的呢?
图 3

从搜索结果来看,其实有不少引用,我们选择第一个:bringup_launch.py。





ComposableNode(package='nav2_lifecycle_manager',plugin='nav2_lifecycle_manager::LifecycleManager',name='lifecycle_manager_navigation',parameters=[{'use_sim_time': use_sim_time,'autostart': autostart,'node_names': lifecycle_nodes}]),
lifecycle_nodes = ['controller_server','smoother_server','planner_server','behavior_server','bt_navigator','waypoint_follower','velocity_smoother']
params_file.ymal 文件中,会对 Nav2 的各个模块做配置,然后把这个文件作为参数传递个 launch 文件。launch 文件运行时,会对 launch 文件和 yaml 文件做解析,把各个模块的参数的隶属关系做映射。启动 lifecycle-manager 时,会把 lifecycle_nodes 数组传给它,它对数组的节点做管理。被管理的节点,会根据自己的配置,加载模块。
例如:lifecycle-manager 会管理 planner_server 模块,planner_server 模块启动时,会加载 nav2_navfn_planner 插件。
以上并不是 Nav2 启动的全部,因为行为树和其他节点这里没有提到。但是也可以管中窥豹了。
四,一个 lifecycle-manager 的误区
从名字上来看,起初我以为 lifecycle-manager 会负责启动,关闭,清理被监管的节点。后来看源码的时候,发现好像不对(感觉不对的地方,等会儿说说),然后翻了一下 navigation_launch.py 文件,有如下内容:
代码块 3
load_nodes = GroupAction(condition=IfCondition(PythonExpression(['not ', use_composition])),actions=[/// 省略部分代码Node(package='nav2_planner',executable='planner_server',name='planner_server',output='screen',respawn=use_respawn,respawn_delay=2.0,parameters=[configured_params],arguments=['--ros-args', '--log-level', log_level],remappings=remappings),/// 省略部分代码])///// 以下是启动模块部分# Create the launch description and populateld = LaunchDescription()# Set environment variablesld.add_action(stdout_linebuf_envvar)# Declare the launch optionsld.add_action(declare_namespace_cmd)ld.add_action(declare_use_sim_time_cmd)ld.add_action(declare_params_file_cmd)ld.add_action(declare_autostart_cmd)ld.add_action(declare_use_composition_cmd)ld.add_action(declare_container_name_cmd)ld.add_action(declare_use_respawn_cmd)ld.add_action(declare_log_level_cmd)# Add the actions to launch all of the navigation nodesld.add_action(load_nodes)ld.add_action(load_composable_nodes)
按 launch 文件内容来看,planner_server 是作为一个独立进程启动的,似乎并不是由 lifecycle-manager 启动,和最开始的理解不一样。
然后搜了一下 AI,确认 :
lifecycle-manager 虽然管理节点,但是并不负责启动。
启动和管理分工如下:
1)launch:创建模块进程,例如 planner_server。创建时,模块的状态处于 unconfigured。
2)lifecycle-manager:所谓的“manager”,主要是对 lifecycle 节点的状态转换做处理。例如:unconfigured -> inactive -> active。
五,lifecycle-manager 源码
图 9

模块代码只有 2 个 h 和 3 个 cpp 文件。其中,lifecycle_manager_client.hpp 文件的内容在 lifecycle-manager 中并没有用到,接下来我们就不再花精力给它。
1,我们看一下 CMakeLists.txt 文件:
代码块 4
cmake_minimum_required(VERSION 3.5)project(nav2_lifecycle_manager)set(library_name ${PROJECT_NAME}_core)add_library(${library_name} SHAREDsrc/lifecycle_manager.cppsrc/lifecycle_manager_client.cpp)add_executable(lifecycle_managersrc/main.cpp)target_link_libraries(lifecycle_manager${library_name})rclcpp_components_register_nodes(${library_name}"nav2_lifecycle_manager::LifecycleManager")/// 省略部分代码ament_package()
第 6 ~ 9 行:生成一个共享库,依赖 2 个 cpp 文件;
第 11 ~ 17 行:生成一个可执行文件,依赖 1 个 cpp 文件和 1 个共享库;
代码块 5
#include<memory>#include"nav2_lifecycle_manager/lifecycle_manager.hpp"#include"rclcpp/rclcpp.hpp"intmain(int argc, char ** argv){rclcpp::init(argc, argv);auto node = std::make_shared<nav2_lifecycle_manager::LifecycleManager>();rclcpp::spin(node);rclcpp::shutdown();return 0;}
代码块 6
class LifecycleManager : public rclcpp::Node{public:/*** @brief A constructor for nav2_lifecycle_manager::LifecycleManager* @param options Additional options to control creation of the node.*/explicitLifecycleManager(const rclcpp::NodeOptions & options= rclcpp::NodeOptions());/*** @brief A destructor for nav2_lifecycle_manager::LifecycleManager*/~LifecycleManager();/// 省略部分代码}
代码块 7
LifecycleManager::LifecycleManager(const rclcpp::NodeOptions & options): Node("lifecycle_manager", options),diagnostics_updater_(this){RCLCPP_INFO(get_logger(), "Creating");// The list of names is parameterized, allowing this module to be used with a different set// of nodesdeclare_parameter("node_names", rclcpp::PARAMETER_STRING_ARRAY);declare_parameter("autostart", rclcpp::ParameterValue(false));declare_parameter("bond_timeout", 4.0);declare_parameter("bond_respawn_max_duration", 10.0);declare_parameter("attempt_respawn_reconnection", true);registerRclPreshutdownCallback();node_names_ = get_parameter("node_names").as_string_array();get_parameter("autostart", autostart_);double bond_timeout_s;get_parameter("bond_timeout", bond_timeout_s);bond_timeout_ = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::duration<double>(bond_timeout_s));double respawn_timeout_s;get_parameter("bond_respawn_max_duration", respawn_timeout_s);bond_respawn_max_duration_= rclcpp::Duration::from_seconds(respawn_timeout_s);get_parameter("attempt_respawn_reconnection",attempt_respawn_reconnection_);callback_group_ = create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive, false);manager_srv_ = create_service<ManageLifecycleNodes>(get_name() + std::string("/manage_nodes"),std::bind(&LifecycleManager::managerCallback, this, _1, _2, _3),rclcpp::ServicesQoS().get_rmw_qos_profile(),callback_group_);is_active_srv_ = create_service<std_srvs::srv::Trigger>(get_name() + std::string("/is_active"),std::bind(&LifecycleManager::isActiveCallback, this, _1, _2, _3),rclcpp::ServicesQoS().get_rmw_qos_profile(),callback_group_);transition_state_map_[Transition::TRANSITION_CONFIGURE]= State::PRIMARY_STATE_INACTIVE;transition_state_map_[Transition::TRANSITION_CLEANUP]= State::PRIMARY_STATE_UNCONFIGURED;transition_state_map_[Transition::TRANSITION_ACTIVATE]= State::PRIMARY_STATE_ACTIVE;transition_state_map_[Transition::TRANSITION_DEACTIVATE]= State::PRIMARY_STATE_INACTIVE;transition_state_map_[Transition::TRANSITION_UNCONFIGURED_SHUTDOWN]= State::PRIMARY_STATE_FINALIZED;transition_label_map_[Transition::TRANSITION_CONFIGURE]= std::string("Configuring ");transition_label_map_[Transition::TRANSITION_CLEANUP]= std::string("Cleaning up ");transition_label_map_[Transition::TRANSITION_ACTIVATE]= std::string("Activating ");transition_label_map_[Transition::TRANSITION_DEACTIVATE]= std::string("Deactivating ");transition_label_map_[Transition::TRANSITION_UNCONFIGURED_SHUTDOWN]= std::string("Shutting down ");init_timer_ = this->create_wall_timer(0s,[this]() -> void {init_timer_->cancel();createLifecycleServiceClients();if(autostart_) {init_timer_ = this->create_wall_timer(0s,[this]() -> void {init_timer_->cancel();startup();},callback_group_);}auto executor = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();executor->add_callback_group(callback_group_, get_node_base_interface());service_thread_ = std::make_unique<nav2_util::NodeThread>(executor);});diagnostics_updater_.setHardwareID("Nav2");diagnostics_updater_.add("Nav2 Health", this, &LifecycleManager::CreateActiveDiagnostic);}
ros2 service call /lifecycle_manager_navigation/manage_nodes nav2_msgs/srv/ManageLifecycleNodes "{command: 1}"代码块 8
init_timer_ = this->create_wall_timer(0s,[this]() -> void{init_timer_->cancel();createLifecycleServiceClients();if (autostart_){init_timer_ = this->create_wall_timer(0s,[this]() -> void{init_timer_->cancel();startup();},callback_group_);}auto executor= std::make_shared<rclcpp::executors::SingleThreadedExecutor>();executor->add_callback_group(callback_group_,get_node_base_interface());service_thread_= std::make_unique<nav2_util::NodeThread>(executor);});
create_wall_timer() 函数的第三个个参数是一个 callback-group,默认值是 nullptr。如果不指定第三个构造函数,那么定时器创建完毕之后,会 add 到 Node 的默认 callback-group。
构造函数结束之后,main.cpp 中的rclcpp::spin() 会触发默认 callback-group 里的这个 timer。
触发之后,会调用 timer 的回调。
这个 timer 的回调里,先是取消定时器,然后重新定义一个定时器,并且创建一个新的 executor 和之前创建的 callback-group 绑定,不在把定时器放在默认 callback-group 中。然后创建新的线程 service_thread_ ,新的线程里轮询这个 callback-group。
下次 rclcpp::spin() 轮询的时候,一样会触发新的 callback-group 中的新 timer。不过下次 spin() 不是 main.cpp 里的了,是 service_thread_ 里的。然后,在这次轮询中,触发新的定时器的回调,里面一样取消定时器,执行 startup(),激活所有的监控节点。
这里单独创建一个线程,从主线程脱离出来,还有一个好处,就是和其他的回调分开,比如,导航过程中的 TF,map 等模块,避免竞争。
真了不起,lifecycle-manager 的作者对 ros2 的框架了如指掌。这种操作我是想不到。还需要继续学习。
这代码非常的绕,得熟悉 create_wall_timer() 函数干了啥,Node 构造函数干了啥,callback-group 里面有啥,rclcpp::spin() 函数又是怎么激活 timer 的,不然,,真的不会理解这段代码。
幸亏我之前这些代码真的都看过,能理解一些,不然,就算让 AI 解答,都不会理解。
古法编程万岁!
这段代码真的精妙。它把初始化操作竟然融入到 spin() 的逻辑中,然后在 main.cpp 的医用层完全无感,也不用在 class 内部再单独定义函数。不过还是太绕了,要是我来做,我会把这些操作拆分到 2 个独立的函数,逻辑清晰,好维护。
嗯。。。好像不理解这段代码,也不影响基于 Nav2 做开发
5,激活被管理节点
lifecycle-node 有自己状态机,节点启动阶段,处于 unconfigered 状态,需要外部激活,才会切换到 inactive,乃至 active 状态。
我们之前提到过的服务 manager_srv_,它的名字是
get_name() + std::string("/manage_nodes")这个服务接收外部的命令,然后执行对应的操作。执行命令的函数是服务的回调,上面的代码有提到:
代码块 9
voidLifecycleManager::managerCallback(const std::shared_ptr<rmw_request_id_t>/*request_header*/,const std::shared_ptr<ManageLifecycleNodes::Request> request,std::shared_ptr<ManageLifecycleNodes::Response> response){switch (request->command) {case ManageLifecycleNodes::Request::STARTUP:response->success = startup();break;case ManageLifecycleNodes::Request::RESET:response->success = reset();break;case ManageLifecycleNodes::Request::SHUTDOWN:response->success = shutdown();break;case ManageLifecycleNodes::Request::PAUSE:response->success = pause();break;case ManageLifecycleNodes::Request::RESUME:response->success = resume();break;}}
第 9 行:startup() 这个函数看起来有点眼熟。没错,它在 代码块 8的第 16 行出现过,它是定时器的回调。
也就是说,startup() 函数要么在定时器内,由 lifecycle-manager 触发,要么接收外部命令,在服务的回调里触发。到这里,我们看到两种方式。其实,还有。
代码块 10
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;}















































































