ROS2 从头开始:第 8 部分 - 使用 ROS2 生命周期节点简化机器人软件组件管理

一、说明

        欢迎来到我在 ROS2 上的系列的第八部分。对于那些可能不熟悉该系列的人,我已经涵盖了一系列主题,包括 ROS2 简介、如何创建发布者和订阅者、自定义消息和服务创建、组合和执行器以及 DDS 和 QoS 配置。如果您还没有机会查看以前的帖子,请务必查看!在这篇文章中,我们将探讨 ROS2 的另一个重要方面:生命周期节点

        在机器人领域,管理不同软件组件的生命周期可能是一项具有挑战性的任务。每个组件可能都有自己的依赖项配置参数要处理的运行时事件。这可能会导致复杂的启动文件和脚本,使管理和更新系统变得困难。而且,状态机设计在机器人系统中很重要,因为它们提供了一种结构化和可预测的方式来管理机器人软件组件的行为和功能。ROS1 没有用于管理软件组件生命周期的内置接口,这使得确保系统中的不同节点正常运行并有效相互通信变得具有挑战性。这就是 ROS2 的生命周期节点的用武之地——它们提供了一个标准化的接口来管理软件组件的状态,从它的创建和初始化到它的关闭和清理。

二、资源描述

        在这篇文章中,我们将仔细研究 ROS2 生命周期节点、它们的好处,以及如何使用它们来管理机器人系统中不同软件组件的生命周期。我还将提供一些如何实现 ROS2 生命周期节点的示例。在这篇文章结束时,您将更好地了解 ROS2 生命周期节点以及它们如何简化机器人软件组件的管理。

        在设计机器人系统时,某些组件对于系统的正常运行至关重要,可能需要特别注意管理其生命周期。在 ROS2 中,有两种类型的节点在高级别可用:

参考链接:节点

参考链接:生命周期节点

例如,考虑一个依靠摄像头进行物体检测和避障的机器人。相机配置对于确保系统接收可靠数据至关重要。要管理相机节点的生命周期,我们可以使用生命周期节点而不是标准节点。此专用节点将负责配置相机参数,例如分辨率帧速率曝光,以及将相机数据发布到检测和避障组件。通过这样做,我们可以确保相机节点设置正确并且其数据可靠,从而使我们能够更有效地管理其生命周期。

另一个例子,ROS导航栈,

参考:链接

其常用的机器人导航软件包,包括各种组件,例如地图服务器成本图生成器本地计划器和全局计划器。这些组件需要按特定顺序启动和停止,并且需要协调它们的生命周期,以确保整个导航系统的正常运行。以下是 ROS2 生命周期节点如何有益于管理导航堆栈:

通过对导航堆栈的每个组件使用 ROS2 生命周期节点,您可以确保组件以正确的顺序启动和关闭。例如,需要在成本图和规划器节点之前加载地图服务器和传感器主题。 使用 ROS2 生命周期节点 API,您可以为每个组件定义一个生命周期状态机,其中包含启动、关闭和状态之间转换所需的状态。通过定义每个节点状态之间的正确依赖关系,可以确保导航堆栈组件以正确的顺序启动和关闭。

除了管理每个组件的启动和关闭外,ROS2 生命周期节点还可用于处理运行时事件,例如导航堆栈的重新配置或对环境的更改。例如,如果机器人在导航时遇到新的障碍物,则可能需要重新配置成本图生成器以考虑新的障碍物。使用 ROS2 生命周期节点 API,您可以定义一个状态转换,以便在其他组件继续运行时处理此重新配置。

有 4 种主要状态:
1. 未配置
2.非活动
3.活动
4.完成

除了四个主要状态之外,节点的生命周期中还有六个转换状态,它们是请求转换期间的中间状态。这些转换状态是:
1.配置:正在配置节点,这通常涉及设置节点运行所需的参数和连接。
2. 清理:正在关闭节点并清理其资源。
3. 关闭:节点正在关闭,通常是为了响应这样做的请求。
4.激活:节点处于活动状态,例如通过初始化其硬件或连接到其他节点。
5. 停用:节点处于非活动状态,例如与其他节点断开连接或关闭其硬件。
6. 错误处理:转换过程中发生错误,节点正在处理错误

在转换状态期间,将执行特定逻辑以确定转换是否成功。然后,成功或失败通过生命周期管理界面传达给生命周期管理软件。

参考:ROS2设计

好吧,让我们从一个基本的camera_node包开始。

ros2 pkg create --build-type ament_cmake camera_node --dependencies rclcpp sensor_msgs rcutils rclcpp_lifecycle lifecycle_msgs

以下是此软件包中使用的 ROS 2 软件包和消息的简要说明:
这是 ROS 2 的C++客户端库,它为编写 ROS 2 节点提供了一个瘦C++式接口。它允许用户创建发布者和订阅者、服务和客户端、操作和服务器等
:这是提供“生命周期”概念实现的扩展。生命周期节点是一种特殊类型的节点,可以经历不同的状态,例如“未配置”、“非活动”、“活动”和“已完成”。这些状态允许对节点进行更受控的初始化和关闭过程。
:这是一个 C 库,它提供了一些基本的、低级的功能,这些功能在 ROS 2 开发中很有用。例如,它提供了一个日志记录 API,允许用户记录具有不同严重性级别的消息。
:这是一个 ROS 2 软件包,为常用传感器(如摄像头、激光雷达和 IMU)提供消息类型。在提供的代码段中,使用消息类型,但它可以替换为包中的任何消息类型,例如相机节点。
:这是一个 ROS 2 包,为生命周期概念提供消息类型。例如,消息类型用于传达从节点到生命周期管理软件的生命周期转换的成功或失败。rclcpprclcpp_lifecyclerclcpprcutilssensor_msgsstd_msgs/msg/Stringsensor_msgssensor_msgs/msg/Imagelifecycle_msgslifecycle_msgs/msg/Transition

cd camera_node && touch src/camera_node.cpp
// camera_node.cpp
#include <chrono>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <utility>#include "lifecycle_msgs/msg/transition.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp/publisher.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"
#include "rclcpp_lifecycle/lifecycle_publisher.hpp"
#include "rcutils/logging_macros.h"
#include "sensor_msgs/msg/image.hpp"#include "utils.cpp"using namespace std::chrono_literals;class CameraNode : public rclcpp_lifecycle::LifecycleNode
{
public:/* The constructor takes in a node_name and a boolean flag for whether to * use intra-process communication or not. The captureImage() method is * used to capture an image from the camera and process it as necessary. * It first checks whether the publisher is active or not and then creates * an image message to publish using the publisher. The image message is of * type sensor_msgs::msg::Image. The publisher is a lifecycle publisher which * means it can be activated or deactivated during runtime using the ROS2 lifecycle interface.*/explicit CameraNode(const std::string & node_name, bool intra_process_comms = false): rclcpp_lifecycle::LifecycleNode(node_name,rclcpp::NodeOptions().use_intra_process_comms(intra_process_comms)){}void publish(){static size_t count = 0;cv::Mat frame_;if (!cap_.read(frame_)) {// If the camera is disconnected, stop publishing frames.RCLCPP_ERROR(get_logger(), "Failed to read frame from camera.");return;}if (!publisher_->is_activated()) {RCLCPP_INFO(get_logger(), "Lifecycle publisher is currently inactive. Messages are not published.");} else {RCLCPP_INFO(get_logger(), "Lifecycle publisher is active. Publishing:");}sensor_msgs::msg::Image::UniquePtr msg(new sensor_msgs::msg::Image());// Pack the OpenCV image into the ROS image.msg->header.frame_id = "camera_frame";msg->height = frame_.rows;msg->width = frame_.cols;msg->encoding = mat_type2encoding(frame_.type());msg->is_bigendian = false;msg->step = static_cast<sensor_msgs::msg::Image::_step_type>(frame_.step);msg->data.assign(frame_.datastart, frame_.dataend);publisher_->publish(std::move(msg));}/* The on_configure function is called when the node is in the Configuring state of the lifecycle.* In this function, the camera is opened and checked if it is successfully opened. * Then, a publisher is created for publishing the camera image with a queue size of 10. Additionally, * a timer is created to trigger the publish function of the CameraNode class every 1 second. * Finally, an informational message is printed to indicate that the node has been configured * and the callback returns rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS. * If there is an error opening the camera, an error message is printed and the callback returns * rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::FAILURE, * indicating that the node failed to configure. */rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturnon_configure(const rclcpp_lifecycle::State &){// Open the camera.cap_.open(0);if (!cap_.isOpened()) {RCLCPP_ERROR(get_logger(), "Failed to open camera.");return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::FAILURE;}publisher_ = this->create_publisher<sensor_msgs::msg::Image>("camera_image", 10);timer_ = this->create_wall_timer(1s, std::bind(&CameraNode::publish, this));RCLCPP_INFO(get_logger(), "CameraNode configured.");return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;}/* During the activate lifecycle transition, the on_activate function is called, * which activates the publisher associated with the node. The publisher_->on_activate() * call in this function activates the publisher by notifying its subscribers that the node is * ready to publish data.* In addition, this code block also includes a log message indicating that the CameraNode * has been activated. Finally, a std::this_thread::sleep_for(2s) call is used to introduce a * delay of 2 seconds to simulate a time-consuming activation process.* The function returns rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS * to indicate that the transition was successful. */rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturnon_activate(const rclcpp_lifecycle::State &){publisher_->on_activate();RCUTILS_LOG_INFO_NAMED(get_name(), "CameraNode activated.");std::this_thread::sleep_for(2s);return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;}/* on_deactivate function, which is a callback function called when the node is in the "inactive" state.* In this function, the on_deactivate function of the publisher is called to deactivate it. * Then, a log message is printed to indicate that the node has been deactivated.* The function returns rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS * to indicate that the deactivation process has been completed successfully. */rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturnon_deactivate(const rclcpp_lifecycle::State &){publisher_->on_deactivate();RCUTILS_LOG_INFO_NAMED(get_name(), "CameraNode deactivated.");return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;}/* The on_cleanup function is a callback function that is executed when the node is being * cleaned up or shutdown. In this function, the timer_ and publisher_ are reset, * which means they are destroyed and any resources they were holding are released. * Then an information message is logged to indicate that the cleanup has been completed.* The return type of this function is rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn, * which is an enumeration that indicates whether the callback function was successful or not. * In this case, it always returns SUCCESS, which means that the cleanup was completed successfully.*/rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturnon_cleanup(const rclcpp_lifecycle::State &){timer_.reset();publisher_.reset();RCUTILS_LOG_INFO_NAMED(get_name(), "CameraNode cleanup.");return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;}/* This is the on_shutdown callback function for the CameraNode class, * which is called when the node is being shut down. * The function takes in a state argument of type rclcpp_lifecycle::State, which represents the current state * of the node when it is being shut down.* The function first resets the timer_ and publisher_ objects, and then logs an information message * indicating that the on_shutdown function is being called and prints the label of the state that the node is in.* Finally, the function returns rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS * to indicate that the shutdown procedure has completed successfully.*/rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturnon_shutdown(const rclcpp_lifecycle::State & state){timer_.reset();publisher_.reset();RCUTILS_LOG_INFO_NAMED(get_name(),"on shutdown is called from state %s.",state.label().c_str());return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;}
private:std::shared_ptr<rclcpp_lifecycle::LifecyclePublisher<sensor_msgs::msg::Image>> publisher_;std::shared_ptr<rclcpp::TimerBase> timer_;cv::VideoCapture cap_;
};int main(int argc, char * argv[])
{setvbuf(stdout, NULL, _IONBF, BUFSIZ);rclcpp::init(argc, argv);rclcpp::executors::SingleThreadedExecutor exe;std::shared_ptr<CameraNode> camera_node =std::make_shared<CameraNode>("camera_node");exe.add_node(camera_node->get_node_base_interface());exe.spin();rclcpp::shutdown();return 0;
}

让我们更新 CMakeLists.txt 并编译,

cmake_minimum_required(VERSION 3.5)
project(camera_node)# Default to C99
if(NOT CMAKE_C_STANDARD)set(CMAKE_C_STANDARD 99)
endif()# Default to C++14
if(NOT CMAKE_CXX_STANDARD)set(CMAKE_CXX_STANDARD 14)
endif()if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")add_compile_options(-Wall -Wextra -Wpedantic)
endif()# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(lifecycle_msgs REQUIRED)
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(OpenCV REQUIRED COMPONENTS core highgui imgproc videoio)include_directories(include${sensor_msgs_INCLUDE_DIRS}${lifecycle_msgs_INCLUDE_DIRS}${rclcpp_lifecycle_INCLUDE_DIRS}${rclcpp_INCLUDE_DIRS})add_executable(camera_nodesrc/camera_node.cpp)target_link_libraries(camera_node${rclcpp_lifecycle_LIBRARIES}${lifecycle_msgs_LIBRARIES}${sensor_msgs_LIBRARIES}${OpenCV_LIBRARIES})install(TARGETScamera_nodeDESTINATION lib/${PROJECT_NAME})ament_package()
# Compile the package
colcon build --packages-select camera_node# Now lets run the camera_node
source install/setup.zsh# list running ros2 topics
ros2 topic list# Output
/camera_node/transition_event
/parameter_events
/rosout# listen to messages on the /camera_node/transition_event
ros2 topic echo /camera_node/transition_event # You don't see any logs in the terminal since no transitions 
# have been made yet.

camera_node的当前状态是什么,如何修改节点状态?


# First lets get the service list
ros2 service list# output
/camera_node/change_state
/camera_node/describe_parameters
/camera_node/get_available_states
/camera_node/get_available_transitions
/camera_node/get_parameter_types
/camera_node/get_parameters
/camera_node/get_state
/camera_node/get_transition_graph
/camera_node/list_parameters
/camera_node/set_parameters
/camera_node/set_parameters_atomically# This is a list of services that the camera node created.
# Now lets get the current state of the camera_node
ros2 service call /camera_node/get_state lifecycle_msgs/srv/GetState {}# Output
ros2 service call /camera_node/get_state lifecycle_msgs/srv/GetState {}
requester: making request: lifecycle_msgs.srv.GetState_Request()response:
lifecycle_msgs.srv.GetState_Response(current_state=lifecycle_msgs.msg.State(id=1, label='unconfigured'))# So the camera node in unconfigured state# Change the camera_node from unconfigured to configured state
ros2 lifecycle set /camera_node configure# Now you could see the camera node changes to configured state

更改相机节点状态以配置后显示输出

我们还有另一个命令来查看camera_node的完整状态机。

ros2 lifecycle list camera_node -a # Output 
- configure [1]Start: unconfiguredGoal: configuring
- transition_success [10]Start: configuringGoal: inactive
- transition_failure [11]Start: configuringGoal: unconfigured
- transition_error [12]Start: configuringGoal: errorprocessing
- cleanup [2]Start: inactiveGoal: cleaningup
- transition_success [20]Start: cleaningupGoal: unconfigured
- transition_failure [21]Start: cleaningupGoal: inactive
- transition_error [22]Start: cleaningupGoal: errorprocessing
- activate [3]Start: inactiveGoal: activating
- transition_success [30]Start: activatingGoal: active
- transition_failure [31]Start: activatingGoal: inactive
- transition_error [32]Start: activatingGoal: errorprocessing
- deactivate [4]Start: activeGoal: deactivating
- transition_success [40]Start: deactivatingGoal: inactive
- transition_failure [41]Start: deactivatingGoal: active
- transition_error [42]Start: deactivatingGoal: errorprocessing
- shutdown [5]Start: unconfiguredGoal: shuttingdown
- shutdown [6]Start: inactiveGoal: shuttingdown
- shutdown [7]Start: activeGoal: shuttingdown
- transition_success [50]Start: shuttingdownGoal: finalized
- transition_failure [51]Start: shuttingdownGoal: finalized
- transition_error [52]Start: shuttingdownGoal: errorprocessing
- transition_success [60]Start: errorprocessingGoal: unconfigured
- transition_failure [61]Start: errorprocessingGoal: finalized
- transition_error [62]Start: errorprocessingGoal: finalized

现在让我们编写订阅者节点,

cd camera_node && touch src/display_node.cpp
// display_node.cpp
#include <memory>
#include <string>#include "lifecycle_msgs/msg/transition_event.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rcutils/logging_macros.h"
#include "sensor_msgs/msg/image.hpp"#include "utils.cpp"class DisplayNode : public rclcpp::Node
{
public:explicit DisplayNode(const std::string & node_name): Node(node_name){subscriber_ = this->create_subscription<sensor_msgs::msg::Image>("camera_image", 10,std::bind(&DisplayNode::data_callback, this, std::placeholders::_1));sub_notification_ = this->create_subscription<lifecycle_msgs::msg::TransitionEvent>("/camera_image/transition_event",10,std::bind(&DisplayNode::notification_callback, this, std::placeholders::_1));}void data_callback(const sensor_msgs::msg::Image::UniquePtr msg){cv::Mat frame(msg->height, msg->width,encoding2mat_type(msg->encoding),msg->data.data());cv::imshow("window", frame);cv::waitKey(1);}void notification_callback(const lifecycle_msgs::msg::TransitionEvent::SharedPtr msg){RCLCPP_INFO(get_logger(), "notify callback: Transition from state %s to %s",msg->start_state.label.c_str(), msg->goal_state.label.c_str());}private:std::shared_ptr<rclcpp::Subscription<sensor_msgs::msg::Image>> subscriber_;std::shared_ptr<rclcpp::Subscription<lifecycle_msgs::msg::TransitionEvent>>sub_notification_;cv::Mat frame_;
};int main(int argc, char ** argv)
{setvbuf(stdout, NULL, _IONBF, BUFSIZ);rclcpp::init(argc, argv);auto display_node = std::make_shared<DisplayNode>("display_node");rclcpp::spin(display_node);rclcpp::shutdown();return 0;
}

更新 CMakeLists.txt,

add_executable(display_nodesrc/display_node.cpp)target_link_libraries(display_node${rclcpp_lifecycle_LIBRARIES}${lifecycle_msgs_LIBRARIES}${sensor_msgs_LIBRARIES}${OpenCV_LIBRARIES})install(TARGETScamera_nodedisplay_nodeDESTINATION lib/${PROJECT_NAME})

到目前为止,我们已经从命令行更改了节点状态。现在,我们将以编程方式执行此操作。我们将演示如何使用 ROS 2 生命周期服务 API 来更改节点的状态。

cd camera_node && touch src/lc_service_client.cpp
#include <chrono>
#include <memory>
#include <string>
#include <thread>#include "lifecycle_msgs/msg/state.hpp"
#include "lifecycle_msgs/msg/transition.hpp"
#include "lifecycle_msgs/srv/change_state.hpp"
#include "lifecycle_msgs/srv/get_state.hpp"#include "rclcpp/rclcpp.hpp"#include "rcutils/logging_macros.h"using namespace std::chrono_literals;static constexpr char const * lifecycle_node = "camera_node";
static constexpr char const * node_get_state_topic = "camera_node/get_state";
static constexpr char const * node_change_state_topic = "camera_node/change_state";template<typename FutureT, typename WaitTimeT>
std::future_status
wait_for_result(FutureT & future,WaitTimeT time_to_wait)
{auto end = std::chrono::steady_clock::now() + time_to_wait;std::chrono::milliseconds wait_period(100);std::future_status status = std::future_status::timeout;do {auto now = std::chrono::steady_clock::now();auto time_left = end - now;if (time_left <= std::chrono::seconds(0)) {break;}status = future.wait_for((time_left < wait_period) ? time_left : wait_period);} while (rclcpp::ok() && status != std::future_status::ready);return status;
}class LifecycleServiceClient : public rclcpp::Node
{
public:explicit LifecycleServiceClient(const std::string & node_name): Node(node_name){}voidinit(){client_get_state_ = this->create_client<lifecycle_msgs::srv::GetState>(node_get_state_topic);client_change_state_ = this->create_client<lifecycle_msgs::srv::ChangeState>(node_change_state_topic);}unsigned intget_state(std::chrono::seconds time_out = 3s){auto request = std::make_shared<lifecycle_msgs::srv::GetState::Request>();if (!client_get_state_->wait_for_service(time_out)) {RCLCPP_ERROR(get_logger(),"Service %s is not available.",client_get_state_->get_service_name());return lifecycle_msgs::msg::State::PRIMARY_STATE_UNKNOWN;}auto future_result = client_get_state_->async_send_request(request);auto future_status = wait_for_result(future_result, time_out);if (future_status != std::future_status::ready) {RCLCPP_ERROR(get_logger(), "Server time out while getting current state for node %s", lifecycle_node);return lifecycle_msgs::msg::State::PRIMARY_STATE_UNKNOWN;}// We have an succesful answer. So let's print the current state.if (future_result.get()) {RCLCPP_INFO(get_logger(), "Node %s has current state %s.",lifecycle_node, future_result.get()->current_state.label.c_str());return future_result.get()->current_state.id;} else {RCLCPP_ERROR(get_logger(), "Failed to get current state for node %s", lifecycle_node);return lifecycle_msgs::msg::State::PRIMARY_STATE_UNKNOWN;}}boolchange_state(std::uint8_t transition, std::chrono::seconds time_out = 3s){auto request = std::make_shared<lifecycle_msgs::srv::ChangeState::Request>();request->transition.id = transition;if (!client_change_state_->wait_for_service(time_out)) {RCLCPP_ERROR(get_logger(),"Service %s is not available.",client_change_state_->get_service_name());return false;}auto future_result = client_change_state_->async_send_request(request);auto future_status = wait_for_result(future_result, time_out);if (future_status != std::future_status::ready) {RCLCPP_ERROR(get_logger(), "Server time out while getting current state for node %s", lifecycle_node);return false;}if (future_result.get()->success) {RCLCPP_INFO(get_logger(), "Transition %d successfully triggered.", static_cast<int>(transition));return true;} else {RCLCPP_WARN(get_logger(), "Failed to trigger transition %u", static_cast<unsigned int>(transition));return false;}}private:std::shared_ptr<rclcpp::Client<lifecycle_msgs::srv::GetState>> client_get_state_;std::shared_ptr<rclcpp::Client<lifecycle_msgs::srv::ChangeState>> client_change_state_;
};void
callee_script(std::shared_ptr<LifecycleServiceClient> lc_client)
{rclcpp::WallRate time_between_state_changes(0.1);  // 10s// configure{if (!lc_client->change_state(lifecycle_msgs::msg::Transition::TRANSITION_CONFIGURE)) {return;}if (!lc_client->get_state()) {return;}}// activate{time_between_state_changes.sleep();if (!rclcpp::ok()) {return;}if (!lc_client->change_state(lifecycle_msgs::msg::Transition::TRANSITION_ACTIVATE)) {return;}if (!lc_client->get_state()) {return;}}// deactivate{time_between_state_changes.sleep();if (!rclcpp::ok()) {return;}if (!lc_client->change_state(lifecycle_msgs::msg::Transition::TRANSITION_DEACTIVATE)) {return;}if (!lc_client->get_state()) {return;}}// activate it again{time_between_state_changes.sleep();if (!rclcpp::ok()) {return;}if (!lc_client->change_state(lifecycle_msgs::msg::Transition::TRANSITION_ACTIVATE)) {return;}if (!lc_client->get_state()) {return;}}// and deactivate it again{time_between_state_changes.sleep();if (!rclcpp::ok()) {return;}if (!lc_client->change_state(lifecycle_msgs::msg::Transition::TRANSITION_DEACTIVATE)) {return;}if (!lc_client->get_state()) {return;}}// we cleanup{time_between_state_changes.sleep();if (!rclcpp::ok()) {return;}if (!lc_client->change_state(lifecycle_msgs::msg::Transition::TRANSITION_CLEANUP)) {return;}if (!lc_client->get_state()) {return;}}{time_between_state_changes.sleep();if (!rclcpp::ok()) {return;}if (!lc_client->change_state(lifecycle_msgs::msg::Transition::TRANSITION_UNCONFIGURED_SHUTDOWN)){return;}if (!lc_client->get_state()) {return;}}
}int main(int argc, char ** argv)
{setvbuf(stdout, NULL, _IONBF, BUFSIZ);rclcpp::init(argc, argv);auto lc_client = std::make_shared<LifecycleServiceClient>("lc_client");lc_client->init();rclcpp::executors::SingleThreadedExecutor exe;exe.add_node(lc_client);std::shared_future<void> script = std::async(std::launch::async,std::bind(callee_script, lc_client));exe.spin_until_future_complete(script);rclcpp::shutdown();return 0;
}

修改 CMakeLists.txt,

add_executable(lc_service_clientsrc/lc_service_client.cpp)target_link_libraries(lc_service_client${rclcpp_lifecycle_LIBRARIES}${std_msgs_LIBRARIES})install(TARGETScamera_nodedisplay_nodelc_service_clientDESTINATION lib/${PROJECT_NAME})

编译并运行,

# Build
colcon build --packages-select camera_node# Run camera node - Terminal 1
ros2 run camera_node camera_node# Run display node - Terminal 2
ros2 run camera_node display_node # Run service_client - Terminal 3
ros2 run camera_node lc_service_client 

六、总结

        在这篇文章中,我们讨论了 ROS2 生命周期接口。生命周期界面可以更轻松地管理不同软件组件(如节点)的生命周期。

        提供了使用生命周期接口实现相机节点的示例,演示了此接口如何更好地控制相机节点的状态,并允许依赖于它的其他组件监视其状态并采取相应的操作。总体而言,在 ROS2 中使用生命周期接口可以大大简化软件组件的管理,并提高基于 ROS 的系统的整体健壮性和可靠性。

        我希望这些信息对您有所帮助,并且您发现它很有用。如果您有任何问题或意见,请随时告诉我。感谢您的反馈,并很乐意帮助回答您可能遇到的任何问题。感谢您抽出宝贵时间阅读本文。

        我总是在LinkedIn上分享有趣的文章和更新,所以如果您想随时了解情况,请随时在平台上关注我。杰加特桑·尚穆甘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/139841.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

2023华为杯数学建模D题第三问——区域双碳目标情景设计样例

在第二问建立好预测模型的基础上&#xff0c;如何设计第三问所说的区域双碳路径&#xff0c;以对宏观政策进行指导&#xff01; 采用STIRPA的基本模型对中国碳达峰时间进行预测&#xff0c;对该模型公式两边取对数得到&#xff1a; 其中&#xff1a;P为人口&#xff0c;A为GDP…

异常记录-VS

1.文件加载失败 无法找到指定路径 Frame GUID: a6c744a8-0e4a-4fc6- 886a-064283054674 Frame mode: VSFM_ MdiChild Error code: 0x80131515 未理会这个提示&#xff0c;可以打开运行项目&#xff0c;只是会跳出这个弹窗。 无法关闭这个异常的窗口。

十四、MySql的用户管理

文章目录 一、用户管理二、用户&#xff08;一&#xff09;用户信息&#xff08;二&#xff09;创建用户1.语法&#xff1a;2.案例&#xff1a; &#xff08;三&#xff09; 删除用户1.语法&#xff1a;2.示例&#xff1a; &#xff08;四&#xff09;修改用户密码1.语法&#…

【MT7628AN】IOT | MT7628AN OpenWRT开发与学习

IOT | MT7628AN OpenWRT开发与学习 时间:2023-06-21 文章目录 `IOT` | `MT7628AN` `OpenWRT`[开发与学习](https://blog.csdn.net/I_feige/article/details/132911634?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22132911634…

【C语言】指针的进阶(四)—— 企业笔试题解析

笔试题1&#xff1a; int main() {int a[5] { 1, 2, 3, 4, 5 };int* ptr (int*)(&a 1);printf("%d,%d", *(a 1), *(ptr - 1));return 0; } 【答案】在x86环境下运行 【解析】 &a是取出整个数组的地址&#xff0c;&a就表示整个数组&#xff0c;因此…

vue项目打包优化

首先第一步通过浏览器看首次加载的问题大小&#xff0c;时间跨度等方面入手 1. Coverage观察 Coverage是chrome开发者工具的一个新功能&#xff0c;从字面意思上可以知道它是可以用来检测代码在网站运行时有哪些js和css是已经在运行&#xff0c;而哪些js和css是还没有用到的&a…

WEB使用VUE3实现地图导航跳转

我们在用手机查看网页时可以通过传入经纬度去设置目的地然后跳转到对应的地图导航软件&#xff0c;如果没有下载软件则会跳转到下载界面 注意&#xff1a; 高德地图是一定会跳转到一个新网页然后去询问用户是否需要打开软件百度和腾讯地图是直接调用软件的这个方法有缺陷&…

【c#-Nuget 包“在此源中不可用”】 Nuget package “Not available in this source“

标题c#-Nuget 包“在此源中不可用”…但 VS 仍然知道它吗&#xff1f; (c# - Nuget package “Not available in this source”… but VS still knows about it?) 背景&#xff1a; 今日从公司svn 上拉取很久很久以前的代码&#xff0c;拉取下来200报错&#xff0c;进一步发…

【机器学习】文本多分类

声明&#xff1a;这只是浅显的一个小试验&#xff0c;且借助了AI。使用的是jupyter notebook,所以代码是一块一块&#xff0c;从上往下执行的 知识点&#xff1a;正则删除除数字和字母外的所有字符、高频词云、混淆矩阵 参考&#xff1a;使用python和sklearn的中文文本多分类…

资源分享 | 情绪脑电研究公开数据集

SEED SEED数据集是由上海交大类脑计算与机器智能研究中心(BCMI)开发的。该数据集是基于脑电的情绪分类任务而设计的数据集。该数据集记录了15名被试在观看积极、中性和消极情绪电影片段时的EEG信号&#xff0c;每个视频片段的时间为3-5分钟。每个参与者重复采集三天&#xff0…

多分类中混淆矩阵的TP,TN,FN,FP计算

关于混淆矩阵&#xff0c;各位可以在这里了解&#xff1a;混淆矩阵细致理解_夏天是冰红茶的博客-CSDN博客 上一篇中我们了解了混淆矩阵&#xff0c;并且进行了类定义&#xff0c;那么在这一节中我们将要对其进行扩展&#xff0c;在多分类中&#xff0c;如何去计算TP&#xff0…

IDEA中创建Java Web项目方法1

以下过程使用IntelliJ IDEA 2021.3 一、File-> New -> Project... 1. 项目类型中选择 Java Enterprise 项目 2. Name&#xff1a;填写自己的项目名称 3. Project template&#xff1a;选择项目的模板&#xff0c;Web application。支持JSP和Servlet的项目 4. Applica…

Nginx location 精准匹配URL = /

Location是什么&#xff1f; Location是Nginx中的块级指令(block directive)&#xff0c;通过配置Location指令块&#xff0c;可以决定客户端发过来的请求URI如何处理&#xff08;是映射到本地文件还是转发出去&#xff09;及被哪个location处理。 匹配模式 分为两种模式&…

位段 联合体 枚举

Hello好久不见&#xff0c;今天分享的是接上次结构体没有分享完的内容&#xff0c;这次我们讲讲位段 枚举和联合体的概念以及他们的用法。 2.1 什么是位段 位段的声明和结构是类似的&#xff0c;有两个不同&#xff1a; 1.位段的成员必须是 int、unsigned int 或signed int 。 …

微信开放平台第三方开发,实现代小程序备案申请

大家好&#xff0c;我是小悟 微信小程序备案整体流程总共分为五个环节&#xff1a;备案信息填写、平台初审、工信部短信核验、通管局审核和备案成功。 服务商可以代小程序发起备案申请。在申请小程序备案之前&#xff0c;需要确保小程序基本信息已填写完成、小程序至少存在一个…

com.google.gson.internal.LinkedTreeMap cannot be cast to XXX

起因是在对google商品做本地缓存时&#xff0c;上线后发现的bug 刚开始非常自信&#xff0c;debug没问题线上有问题&#xff0c;大概率就是混淆文件没有添加keep&#xff0c;于是本地添加对SDK中类的keep&#xff0c;本地打包release验证&#xff0c;不出意外还是崩溃 仔细看…

C语言指针变量的引用距离

本段代码&#xff0c;测试&#xff0c;C的函数传参中&#xff0c;形参是基础类型参数和地址参数&#xff0c;对于实参的值影响。 #include <stdio.h> add(int a,int b){a;b;printf("add副本a%d\n",a);printf("add副本b%d\n",b);printf("副本ca…

Interceptor的使用场景:拦截请求中的租户信息,注入到租户上下文中

业务场景 在SaaS环境中&#xff0c;租户是最重要的隔离业务数据的属性了&#xff0c;在自己的项目体系环境中&#xff0c;租户id能保证有值。但有个特殊场景&#xff0c;某些特殊权限的账号需要修改指定租户的内容&#xff0c;也即前端会携带租户信息过来&#xff0c;并且内部涉…

在github上设置不同分支,方便回滚

在github上设置不同分支&#xff0c;方便回滚 步骤可能出现的问题couldnt find remote ref gpuVersion1. 确保您处于正确的分支2. 添加并提交更改&#xff08;如果还未进行&#xff09;3. 推送本地分支到远程仓库4. 验证操作 步骤 之前在github上上传了一个项目代码&#xff0c…

【马蹄集】—— 数论专题:筛法

数论专题 目录 MT2213 质数率MT2214 元素共鸣MT2215 小码哥的喜欢数MT2216 数的自我MT2217 数字游戏 MT2213 质数率 难度&#xff1a;黄金    时间限制&#xff1a;1秒    占用内存&#xff1a;256M 题目描述 请求出 [ 1 , n ] \left[1,n\right] [1,n] 范围内质数占比率。…