(持续更新ing...)
1.Launch文件启动节点并更改传入执行文件参数
可执行文件中的实现:
ros::init(argc, argv, "api_moveJ_demo");
ros::NodeHandle nh;
//全局节点句柄,初始化节点
ros::NodeHandle private_nh_("~");
//私有节点句柄,用来访问ROS系统中的私有参数和命名空间。
//在ROS中,私有参数是在节点的命名空间下存储的,而不是在全局命名空间下。
//通过使用"~"作为命名空间前缀,可以确保这些参数仅对该特定节点可见。
private_nh_.param<int> ("Arm_Dof",arm_dof,6);
//从ROS参数服务器获取一个名为"Arm_Dof"的整型参数,arm_dof用于存储获取到的参数值,如果该参数不存在,它将使用默认值6。
ROS_INFO("*******arm_dof = %d", arm_dof);
Launch文件中的实现:
注意:type=""
指定的是 ROS 包 (pkg=""
) 中实际的 可执行文件名。该可执行文件名需要位于对应包的 CMakeLists.txt
中通过 add_executable()
注册。
即这里的那个api_moveJ_demo:add_executable(api_moveJ_demo src/rm_control.cpp)
<launch>
<arg name="Arm_Dof" default="6"/> <!-- 机械臂自由度设置 -->
<node name="api_moveJ_demo" pkg="control_arm_move" type="api_moveJ_demo" respawn="false" output="screen">
<!-- 运动参数 -->
<param name="Arm_Dof" value="$(arg Arm_Dof)"/> <!-- 机械臂自由度参数 -->
</node>
</launch>
2.创建异步spinner对象实现多线程并发执行回调函数
可执行文件中的实现:
// 声明spinner对象,参数2表示并发线程数,默认处理全局Callback队列
ros::AsyncSpinner spin(2);
// 启动两个spinner线程并发执行可用回调
spin.start();
创建多线程,实现多个回调函数的并发执行,提高程序的响应速度和效率。
3.调用多个launch文件实现多个节点结合的复杂功能
Launch文件实现:
<launch>
<include file="$(find rm_driver)/launch/rm_63_driver.launch">
</include>
<include file="$(find rml_63_moveit_config)/launch/demo_realrobot.launch">
</include>
</launch>
即在大型工程项目开发过程中可以建立一个类似于rm_bringup的功能包,其launch文件编写如上,为实现多个launch文件同时运行所设计,使用该功能包可用一条命令启动其他功能包的launch文件进而实现多个节点结合的复杂功能的启动。PS:在同一个工作空间下。
4.常用的ros指令
- rospack可以对包进行操作,比如rospack find <package>可以找到某个包的位置,rospack depend1 <package>可以列举出包的依赖等;
- roscd可以进入某个包里面,pwd可以输出目前的路径;
- cat 可以直接在终端显示某个文件的全部内容,如cat package.xml;
- 除了ubuntu系统自带的sudo nano <fileName>可以在终端直接修改文件之外,ros还可以提供rosed <package> <fileName>来直接修改包文件;
- 直接添加内容到指定文件:echo "int64 num" > msg/Num.msg;
- 复制命令:roscp <package> <file> <copy_path>,比如(roscd beginger_package)roscp rosTutorial AddTwoInts.srv srv/AddTwoInts.srv;
- 有时候运行某个包的节点提示依赖不足的问题时可以尝试下面命令:rosdep install [package_name]来下载包的相关外部依赖;
- 工作空间重新编译的命令:catkin_make clean & catkin_make
5.ROS发布/订阅节点
一般来说一个节点实现一个功能,而一个节点里面可以有很多个发布者和订阅者;比如机械臂开发中常有一个robot_driver功能包,里面有一个robot_driver节点,这个节点就是用来实现整个机械臂的驱动实现的,含有发布机械臂状态的publisher、发布关节错误信息的publisher等,也有接收运动指令的subscriber、接收关节角参数并设置的subscriber等。
在一个包里面的一个节点下的各种发布者并不是在启动节点后一股脑全部发布,而是应该设置发布条件的。
Publisher端:
//Publisher 端
#include<ros/ros.h>
#include<std_msgs/String.h>
int main(int argc, char**argv)
{
//初始化ros,并创建节点
ros::init(argc, argv, "node_name_pub");
//创建节点句柄并初始化节点
ros::NodeHandle nh;
//利用句柄创建发布者
ros::Publisher pub_name = nh.advertise<std_msgs::String>("topic_name", 10);
//设置循环频率
ros::Rate rate(10);
//创建循环体用于持续发布话题
while(ros::ok())
{
//创建数据对象
std_msgs::String msg_name;
//对象赋值
msg_name.data = "message to publish";
//发布信息
pub_name.publish(msg_name);
//处理回调函数
ros::spinOnce();
//按指定循环频率休眠
rate.sleep();
}
return 0;
}
Subscriber端:
//Subscriber端
#include<ros/ros.h>
#include<std_msgs/String.h>
//实现订阅者在接收到数据之后的回调函数
void topicCallback(const std_msgs::String::ConstPtr& msg_name)
{
//输出,注意这里是msg_name->data而不是msg_name.data;
//因为ConstPtr是智能指针(应该是可以根据类型不同而变的意思),这里加上&表示为msg_name为
//智能指针的引用,指针的引用还是指针
ROS_INFO(msg_name->data);
}
int main(int argc, char ** argv)
{
//初始化ros,并创建节点
ros::init(argc, argv, "node_name_sub");
//创建节点句柄并初始化节点
ros::NodeHandle nh;
//创建订阅者
ros::Subscriber sub_name = nh.subscribe("topic_name", 1000, topicCallback);
//循环运行ros节点
ros::spin();
return 0;
}
配置CMakeLists.txt编译文件:
#Version and package
cmake_minimum_required(VERSION 3.0.2)
project(beginner_tutorials)
## Find catkin also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# Declare ROS messages and services
## Generate messages in the 'msg' folder
add_message_files(
FILES
# Message1.msg
# Message2.msg
)
## Generate services in the 'srv' folder
add_service_files(
FILES
# Service1.srv
# Service2.srv
)
## Generate actions in the 'action' folder
# add_action_files(
# FILES
# Action1.action
# Action2.action
# )
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
std_msgs
)
)
#Declare a catkin package
catkin_package()
#Declare talker and listener
include_directories(
# include
${catkin_INCLUDE_DIRS}
)
add_executable(node_name_pub src/myTalker.cpp)
add_executable(node_name_sub src/myListener.cpp)
add_dependencies(node_name_pub beginner_tutorials_generate_messages_cpp)
add_dependencies(node_name_sub beginner_tutorials_generate_messages_cpp)
target_link_libraries(node_name_pub ${catkin_LIBRARIES})
target_link_libraries(node_name_sub ${catkin_LIBRARIES})
配置package.xml依赖文件:
<?xml version="1.0"?>
<package format="2">
<name>beginner_tutorials</name>
<version>0.0.0</version>
<description>The beginner_tutorials package</description>
<maintainer email="robot@todo.todo">robot</maintainer>
<license>BSD</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<export>
<!-- Other tools can request additional information be placed here -->
</export>
</package>
6.ROS服务端/客户端节点
Server 端:
//Server端
//计算两个int之和
#include<ros/ros.h>
#include<package_name/srv_name.h>
//srv_name.h是srv/srv_name.srv在编译的时候自动生成的(devel/include/pacakage_name/)
bool dealFunName(package_name::srv_name::Request &req,
package_name::srv_name::Response &res)
{
res.sum = res.a + res.b;
return true;
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "node_name_server");
ros::NodeHandle nh;
ros::ServiceServer server_name = nh.advertiseServer("service_name", dealFunName);
ROS_INFO("Server Ready...");
ros::spin();
return 0;
}
Client 端:
#Client 端
#include<ros/ros.h>
#include<package_name/srv_name.h>
#include<cstdlib>
int main(int argc, char** argv)
{
ros::init(argc, argv, "node_name_client");
ros::NodeHandle nh;
//rosrun [package_name] [node_name] X Y
if(argc != 3){
ROS_INFO("usage: node_name_client X Y");
return 1;
}
ros::ServiceClient client_name = nh.serviceClient<package_name::srv_name>("service_name");
//将命令行中的参数赋值于srv的请求数据中
package_name::srv_name my_srv;
my_srv.request.a = atoll(argv[1]);//将字符串转成long long数据类型
my_srv.request.b = atoll(argv[2]);
//客户端尝试请求,并发送数据
if(client_name.call(my_srv)){
ROS_INFO("Call successfully...")
ROS_INFO("The Result of this service is: %ld", (long int)my_srv.responde.sum);
}
else{
ROS_INFO("Service call faild...");
return 1;
}
return 0;
}
配置CMakeLists.txt编译文件:
#Version and package
cmake_minimum_required(VERSION 3.0.2)
project(package_name)
## Find catkin also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# Declare ROS messages and services
## Generate messages in the 'msg' folder
add_message_files(
FILES
# Message1.msg
# Message2.msg
)
## Generate services in the 'srv' folder
add_service_files(
FILES
# Service1.srv
# Service2.srv
srv_name.srv
)
## Generate actions in the 'action' folder
# add_action_files(
# FILES
# Action1.action
# Action2.action
# )
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
std_msgs
)
)
#Declare a catkin package
catkin_package()
#Auto declare client and server's head_directories
include_directories(
# include
${catkin_INCLUDE_DIRS}
)
add_executable(node_name_server src/myServer.cpp)
add_executable(node_name_client src/myClient.cpp)
add_dependencies(node_name_server package_name_gencpp)
add_dependencies(node_name_client package_name_gencpp)
target_link_libraries(node_name_server ${catkin_LIBRARIES})
target_link_libraries(node_name_client ${catkin_LIBRARIES})
配置package.xml依赖文件:
<?xml version="1.0"?>
<package format="2">
<name>package_name</name>
<version>0.0.0</version>
<description>The beginner_tutorials package</description>
<maintainer email="robot@todo.todo">robot</maintainer>
<license>BSD</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<export>
<!-- Other tools can request additional information be placed here -->
</export>
</package>
7. ROS action client & action server的编写
action client 端:
#include<ros/ros.h>
#include<actionlib/client/simple_action_client.h>
//simple_action_client库含有SimpleActionClient类,其对象可以用于创建action client并管理client
#include<actionlib/client/terminal_state.h>
//terminal_state.h定义了可能的目标状态,内含枚举类,用于定义action执行完成时的状态,如succeeded, aborted, rejected等
#include<beginner_tutorials/FibonacciAction.h>
typedef actionlib::SimpleActionClient<beginner_tutorials::FibonacciAction> Client;
int main(int argc, char ** argv)
{
ros::init(argc,argv,"fibonacci_client");
//通过创建actionlib作用域下的SimpleActionClient类的对象来生成和管理client,true表示自启动
Client ac_("fibonacci", true);
//等待
ROS_INFO("Waiting for action server....");
ac_.waitForServer();
//创建数据类型并发送
ROS_INFO("Server conneted...");
beginner_tutorials::FibonacciGoal my_goal;
my_goal.order = 20;
ac_.sendGoal(my_goal);
//等待sever处理并返回结果
bool finished_before_timeout = ac_.waitForResult(ros::Duration(30.0));
if(finished_before_timeout){
actionlib::SimpleClientGoalState state = ac_.getState();
ROS_INFO("Action finished: %s",state.toString().c_str());
}
else{
ROS_INFO("Action did not finished before the time out...");
ac_.cancelGoal();
}
return 0;
}
action server 端:
#include<ros/ros.h>
#include<beginner_tutorials/FibonacciAction.h>
//FibonacciAction.h由action/Fibonacci.action在编译的时候自动生成的(在devel/include/beginner_tutorials/)
#include<actionlib/server/simple_action_server.h>
//上面这个头文件是action库,用来执行简单的action
//包含 SimpleActionServer 类,用于管理和执行 ROS 的 action
typedef actionlib::SimpleActionServer<beginner_tutorials::FibonacciAction> Server;
//在类中实现server的创建和回调函数的处理
class FibonacciAction
{
protected: //定义action server类的属性
ros::NodeHandle nh_;
//实例化SimpleActionServer类的对象as_(其实就是这个action server的名字),用于处理ROS action
Server as_; // NodeHandle instance must be created before this line. Otherwise strange error occurs.
std::string action_name_;
// create messages that are used to published feedback/result
//FibonacciFeedback和FibonacciResult都是在编译时候自动生成的msg文件
beginner_tutorials::FibonacciFeedback feedback_;
beginner_tutorials::FibonacciResult result_;
public:
//构造函数中初始化as_对象,即创建action server端,参数包括节点句柄、行为名称、回调函数
//使用 boost::bind 将 executeCB 方法(回调函数)绑定到当前对象 (this),并传递一个占位符 _1,表示将来会有一个参数传递给 executeCB
//false指示是否自动启动服务器
//as_.start()表示启动服务器
FibonacciAction(std::string name) :
as_(nh_, name, boost::bind(&FibonacciAction::executeCB, this, _1), false),
action_name_(name)
{
as_.start();
}
~FibonacciAction(void)
{
}
//FibonacciGoalConstPtr这是一个 boost 共享指针,通过将“ConstPtr”附加到目标消息类型的末尾来给出
void executeCB(const beginner_tutorials::FibonacciGoalConstPtr &goal)
{
// helper variables
ros::Rate r(1);
bool success = true;
// push_back the seeds for the fibonacci sequence
feedback_.sequence.clear();
feedback_.sequence.push_back(0);
feedback_.sequence.push_back(1);
// publish info to the console for the user
ROS_INFO("%s: Executing, creating fibonacci sequence of order %i with seeds %i, %i", action_name_.c_str(), goal->order, feedback_.sequence[0], feedback_.sequence[1]);
// start executing the action
for(int i=1; i<=goal->order; i++)
{
//动作服务器的一个重要组成部分是能够允许动作客户端请求取消当前目标执行。
//当客户端请求抢占当前目标时,动作服务器应该取消目标,执行必要的清理,并调用函数 setPreempted(),该函数表示该动作已被用户请求抢占。
// check that preempt has not been requested by the client
if (as_.isPreemptRequested() || !ros::ok())
{
ROS_INFO("%s: Preempted", action_name_.c_str());
// set the action state to preempted
as_.setPreempted();
success = false;
break;
}
feedback_.sequence.push_back(feedback_.sequence[i] + feedback_.sequence[i-1]);
// publish the feedback
as_.publishFeedback(feedback_);
// this sleep is not necessary, the sequence is computed at 1 Hz for demonstration purposes
r.sleep();
}
if(success)
{
result_.sequence = feedback_.sequence;
ROS_INFO("%s: Succeeded", action_name_.c_str());
// set the action state to succeeded,and sent the result
as_.setSucceeded(result_);
}
}
};
int main(int argc, char** argv)
{
ros::init(argc, argv, "fibonacci_server");
FibonacciAction fibonacci("fibonacci");
ros::spin();
return 0;
}
Fibonacci.action数据文件定义如下:
#goal
int32 order
---
#result
int32[] sequence
---
#feedback
int32[] sequence
CMakeLists.txt编译文件如下:
cmake_minimum_required(VERSION 3.0.2)
project(beginner_tutorials)
## Find catkin macros and libraries
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
actionlib_msgs
actionlib
)
## System dependencies are found with CMake's conventions
find_package(Boost REQUIRED COMPONENTS system)
## Generate messages in the 'msg' folder
add_message_files(
FILES
# Message1.msg
# Message2.msg
)
## Generate services in the 'srv' folder
add_service_files(
FILES
# Service1.srv
# Service2.srv
)
## Generate actions in the 'action' folder
add_action_files(
DIRECTORY action
FILES
# Action1.action
# Action2.action
Fibonacci.action
)
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
std_msgs
actionlib_msgs
)
## CATKIN_DEPENDS: catkin_packages dependent projects also need
## DEPENDS: system dependencies of this project that dependent projects also need
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES beginner_tutorials
# CATKIN_DEPENDS roscpp rospy std_msgs
CATKIN_DEPENDS message_runtime actionlib_msgs actionlib control_msgs moveit_msgs
# DEPENDS system_lib
)
## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
# include
${catkin_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS}
)
# add_executable(${PROJECT_NAME}_node src/beginner_tutorials_node.cpp)
# add_executable(talker src/myTalker.cpp)
# add_executable(listener src/myListener.cpp)
# add_executable(ATIserver src/addTwoIntsServer.cpp)
# add_executable(ATIclient src/addTwoIntsClient.cpp)
add_executable(fibonacci_server src/FibonacciServer.cpp)
add_executable(fibonacci_client src/FibonacciClient.cpp)
# add_executable(moveit_action_server src/moveit_server.cpp)
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
# add_dependencies(talker beginner_tutorials_generate_messages_cpp)
# add_dependencies(listener beginner_tutorials_generate_messages_cpp)
# add_dependencies(ATIserver beginner_tutorials_gencpp)
# add_dependencies(ATIclient beginner_tutorials_gencpp)
add_dependencies(fibonacci_server ${beginner_tutorials_EXPORTED_TARGETS})
add_dependencies(fibonacci_client ${beginner_tutorials_EXPORTED_TARGETS})
# add_dependencies(moveit_action_server ${beginner_tutorials_EXPORTED_TARGETS})
## Specify libraries to link a library or executable target against
# target_link_libraries(${PROJECT_NAME}_node
# ${catkin_LIBRARIES}
# )
# target_link_libraries(talker ${catkin_LIBRARIES})
# target_link_libraries(listener ${catkin_LIBRARIES})
# target_link_libraries(ATIserver ${catkin_LIBRARIES})
# target_link_libraries(ATIclient ${catkin_LIBRARIES})
target_link_libraries(fibonacci_server ${catkin_LIBRARIES})
target_link_libraries(fibonacci_client ${catkin_LIBRARIES})
# target_link_libraries(moveit_action_server ${catkin_LIBRARIES})
package.xml依赖文件如下:
<?xml version="1.0"?>
<package format="2">
<name>beginner_tutorials</name>
<version>0.0.0</version>
<description>The beginner_tutorials package</description>
<maintainer email="robot@todo.todo">robot</maintainer>
<license>TODO</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>actionlib</build_depend>
<build_depend>actionlib_msgs</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<build_export_depend>actionlib</build_export_depend>
<build_export_depend>actionlib_msgs</build_export_depend>
<exec_depend>actionlib</exec_depend>
<exec_depend>actionlib_msgs</exec_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<!-- The customs msg file or srv file need these dependens to turned into -->
<!-- source code for C++,Python,and other languages -->
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<exec_depend>message_generation</exec_depend>
<!-- The export tag contains other, unspecified, tags -->
<export>
<!-- Other tools can request additional information be placed here -->
</export>
</package>
8. ROS工作空间编译的时候找不到头文件
注意,在ros工作空间中的ros包目录结构应该如下所示:
所以在包含头文件时,应该这样写:
#include "my_package/my_custom_header.h"
//而不是#include "include/my_header.h"
9.ros::spin()与ros::AsyncSpinner spinner(1)在程序中的作用与区别
首先在创建节点的程序中一般都需要ros::spin()这么一个函数在主程序的末尾,是用于启动回调处理循环的一个函数,它在ROS节点的主线程中执行,用于不断监听并处理订阅者、服务、定时器等的回调事件。即当您的节点订阅了某个话题、创建了服务或使用了定时器时,回调函数需要在某个线程中被调用,此时ros::spin()会让主线程进入一个死循环,不断检查是否有新的消息到达,并调用相应的回调函数;所以为什么这句一般会放在main函数的末尾了,因为当启动这个回调处理循环的时候,会阻塞主线程,即主线程不再执行ros::spin()后面的代码,直到节点被关闭;
其使用形式如下:
#include <ros/ros.h>
#include <std_msgs/String.h>
// 回调函数:用于处理接收到的话题消息
void chatterCallback(const std_msgs::String::ConstPtr& msg) {
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv) {
// 初始化ROS节点
ros::init(argc, argv, "listener");
ros::NodeHandle nh;
// 创建订阅者
ros::Subscriber sub = nh.subscribe("chatter", 1000, chatterCallback);
// 进入回调处理循环
ros::spin();
// 此处的代码不会被执行
ROS_INFO("Node is shutting down");
return 0;
}
而ros::AsyncSpinner是一种用于异步处理回调函数的机制,通常用于需要同时执行多个回调函数而不影响主线程的场景;即ros::AsyncSpinner会在后台启动线程进行回调处理而不阻塞主线程的执行,常用形式为:ros::AsyncSpinner spinner(2); spinner.start()。在调用 spinner.start()
之后,所有通过 ros::Subscriber
或 ros::ServiceServer
注册的回调函数将由这个异步线程处理,这意味着不需要在主线程中调用 ros::spin()
,主线程可以继续执行其他任务。
适用于需要在主线程继续执行其他任务,并且希望回调函数能够被后台线程并行处理的场景,可以提高系统的实时性和响应能力。
ros::shutdown(); //没有回调的话,执行完主程序记得把节点关闭
#include <ros/ros.h>
#include <std_msgs/String.h>
// 回调函数
void chatterCallback(const std_msgs::String::ConstPtr& msg) {
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv) {
ros::init(argc, argv, "async_spinner_example");
ros::NodeHandle nh;
// 创建一个Subscriber
ros::Subscriber sub = nh.subscribe("chatter", 1000, chatterCallback);
// 使用AsyncSpinner代替ros::spin()
ros::AsyncSpinner spinner(2); // 使用2个线程
spinner.start();
// 主线程可以继续执行其他任务
ros::Rate rate(10);
while (ros::ok()) {
ROS_INFO("Main thread is doing other work...");
rate.sleep();
}
return 0;
}
10.Lifecycle Nodes生命周期节点
在 ROS2 中,生命周期节点(lifecycle node) 是为了更好地控制节点的状态切换、资源管理和系统整体行为协调而引入的一种机制。
为什么需要生命周期节点?
普通节点一启动就会立刻运行,而在很多实际场景下,我们希望:
-
更精细地控制节点的初始化、激活、暂停和关闭。
-
在节点尚未准备好时避免发布消息或接收订阅数据。
-
在系统启动或切换状态时有统一的节点行为管理。
-
更安全、更有组织地管理资源,比如订阅、发布者、定时器等。
比如一个激光雷达节点,在系统启动时不希望它立刻开始采集数据,而是在地图加载完毕、导航系统准备好之后再激活采集。
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"
#include "std_msgs/msg/float32.hpp"
using rclcpp_lifecycle::LifecycleNode;
using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
class TemperaturePublisher : public LifecycleNode
{
public:
TemperaturePublisher()
: LifecycleNode("temperature_publisher")
{}
CallbackReturn on_configure(const rclcpp_lifecycle::State &)
{
RCLCPP_INFO(get_logger(), "Configuring...");
pub_ = this->create_publisher<std_msgs::msg::Float32>("temperature", 10);
timer_ = this->create_wall_timer(
std::chrono::seconds(1),
[this]() {
if (this->get_current_state().id() == rclcpp_lifecycle::State::PRIMARY_STATE_ACTIVE) {
auto msg = std_msgs::msg::Float32();
msg.data = 36.5 + static_cast<float>(rand()) / (static_cast<float>(RAND_MAX/1.0)); // 模拟体温
pub_->publish(msg);
RCLCPP_INFO(this->get_logger(), "Publishing: %.2f", msg.data);
}
}
);
timer_->cancel(); // 初始不运行
return CallbackReturn::SUCCESS;
}
CallbackReturn on_activate(const rclcpp_lifecycle::State &)
{
RCLCPP_INFO(get_logger(), "Activating...");
pub_->on_activate();
timer_->reset();
return CallbackReturn::SUCCESS;
}
CallbackReturn on_deactivate(const rclcpp_lifecycle::State &)
{
RCLCPP_INFO(get_logger(), "Deactivating...");
pub_->on_deactivate();
timer_->cancel();
return CallbackReturn::SUCCESS;
}
CallbackReturn on_cleanup(const rclcpp_lifecycle::State &)
{
RCLCPP_INFO(get_logger(), "Cleaning up...");
pub_.reset();
timer_.reset();
return CallbackReturn::SUCCESS;
}
CallbackReturn on_shutdown(const rclcpp_lifecycle::State &)
{
RCLCPP_INFO(get_logger(), "Shutting down...");
return CallbackReturn::SUCCESS;
}
private:
rclcpp_lifecycle::LifecyclePublisher<std_msgs::msg::Float32>::SharedPtr pub_;
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<TemperaturePublisher>();
rclcpp::spin(node->get_node_base_interface());
rclcpp::shutdown();
return 0;
}
运行后,它默认是 unconfigured
状态,你需要用 lifecycle 工具控制状态切换:
ros2 lifecycle set /temperature_publisher configure
ros2 lifecycle set /temperature_publisher activate
就会看到开始发布温度数据啦。