ROS2 话题通信实战:消息对象、Publisher 发布器与 Subscriber 订阅器保姆级教程
目录
摘要
一、ROS2 消息对象的常见写法
1.1 创建消息对象的基本格式
1.2 以 Twist 为例创建速度消息
1.3 给消息字段赋值
(1)让机器人向前运动
(2)让机器人原地旋转
(3)让机器人停止
二、ROS2 发布器 Publisher 的写法
2.1 发布器的基本定义方式
2.2 创建发布器
2.3 发布消息
2.4 完整发布器示例代码
2.5 发布器代码重点解释
(1)包含头文件
(2)创建发布器
(3)创建定时器
(4)发布速度消息
三、ROS2 订阅器 Subscriber 的写法
3.1 订阅器的基本定义方式
3.2 创建订阅器
3.3 回调函数中接收消息
3.4 完整订阅器示例代码
3.5 发布端和订阅端访问字段的区别
(1)发布端通常使用点号
(2)订阅端通常使用箭头
摘要
上一篇文章主要讲解了ROS2 中常见的消息类型,例如std_msgs、geometry_msgs、nav_msgs、sensor_msgs,并重点说明了geometry_msgs::msg::Twist、Pose、PoseStamped、TransformStamped等消息的含义。链接如下:
ROS2 常见消息类型保姆级教程-CSDN博客https://blog.csdn.net/m0_58954356/article/details/161693378?spm=1001.2014.3001.5502这一篇开始进入 ROS2 话题通信实战,重点讲清楚几个问题:
1. ROS2 中如何创建消息对象?
2. Publisher 发布器怎么写?
3. Subscriber 订阅器怎么写?
本文仍然以移动机器人常用的速度控制消息:
geometry_msgs::msg::Twist为例,带大家从代码层面真正看懂 ROS2 的话题发布、话题订阅和消息字段查看方法。
一、ROS2 消息对象的常见写法
1.1 创建消息对象的基本格式
ROS2 中创建消息对象,一般采用下面这种格式:
包名::msg::消息类型 变量名;
例如:
std_msgs::msg::String msg; geometry_msgs::msg::Twist vel_cmd; nav_msgs::msg::Odometry odom; sensor_msgs::msg::LaserScan scan;这和普通 C++ 定义变量非常像。
普通 C++ 中可以这样定义变量:
int age; double speed; std::string name;ROS2 中定义消息对象则是:
geometry_msgs::msg::Twist vel_cmd;其中:
geometry_msgs::msg::Twist 消息类型 vel_cmd 消息对象名也就是说,vel_cmd是一个变量,只不过它的类型不是int、double,而是 ROS2 中的Twist消息类型。
1.2 以 Twist 为例创建速度消息
Twist 常用于 /cmd_vel 速度控制话题
移动机器人控制中,经常会看到:
geometry_msgs::msg::Twist vel_cmd;这句代码的意思是:
创建一个 Twist 类型的速度消息对象,名字叫 vel_cmd。
Twist里面主要包含两部分:
Vector3 linear; // 线速度 Vector3 angular; // 角速度完整字段如下:
vel_cmd.linear.x; vel_cmd.linear.y; vel_cmd.linear.z; vel_cmd.angular.x; vel_cmd.angular.y; vel_cmd.angular.z;对于大多数地面移动机器人来说,最常用的是:
vel_cmd.linear.x; // 前进 / 后退速度 vel_cmd.angular.z; // 左转 / 右转角速度1.3 给消息字段赋值
(1)让机器人向前运动
例如:
geometry_msgs::msg::Twist vel_cmd; vel_cmd.linear.x = 0.2; vel_cmd.angular.z = 0.0;含义是:
机器人以 0.2 m/s 的速度向前运动,不旋转。这里:
linear.x = 0.2表示沿机器人前方方向前进。
angular.z = 0.0表示不绕 z 轴旋转。
(2)让机器人原地旋转
如果写成:
vel_cmd.linear.x = 0.0; vel_cmd.angular.z = 0.5;含义是:
机器人不前进,只原地旋转。其中:
angular.z = 0.5表示绕 z 轴旋转,单位通常是:
rad/s也就是弧度每秒。
(3)让机器人停止
如果想让机器人停止,一般写成:
vel_cmd.linear.x = 0.0; vel_cmd.angular.z = 0.0;并且发布出去:
pub_vel->publish(vel_cmd);需要注意的是,机器人停止时,最好不是简单地“不发消息”,而是主动发布一个零速度消息。
也就是:
linear.x = 0.0 angular.z = 0.0这样底盘控制节点才能明确知道机器人需要停止。
二、ROS2 发布器 Publisher 的写法
2.1 发布器的基本定义方式
Publisher 需要指定消息类型
如果要发布Twist消息,发布器一般这样定义:
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr pub_vel;可以拆开理解:
rclcpp::Publisher<geometry_msgs::msg::Twist>表示这是一个发布Twist消息的发布器。
SharedPtr表示智能指针类型。
pub_vel是发布器对象的名字。
完整意思就是:
定义一个发布 geometry_msgs::msg::Twist 消息的发布器,名字叫 pub_vel。
2.2 创建发布器
使用 create_publisher 创建发布器
在 ROS2 节点类里面,通常使用:
create_publisher创建发布器。
例如:
pub_vel = this->create_publisher<geometry_msgs::msg::Twist>("/cmd_vel", 10);这句代码可以拆成:
this->create_publisher<geometry_msgs::msg::Twist>表示创建一个发布Twist消息的发布器。
"/cmd_vel"表示发布到/cmd_vel话题。
10表示队列深度。
所以这句代码整体含义是:
创建一个发布器,这个发布器向 /cmd_vel 话题发布 Twist 类型消息,队列深度为 10。
2.3 发布消息
先创建消息,再赋值,最后 publish
发布消息的基本流程是:
1. 创建消息对象
2. 给消息字段赋值
3. 调用 publish 发布出去
代码如下:
geometry_msgs::msg::Twist vel_cmd; vel_cmd.linear.x = 0.1; vel_cmd.angular.z = 0.0; pub_vel->publish(vel_cmd);其中:
pub_vel->publish(vel_cmd);表示把vel_cmd这条速度消息发布出去。
如果pub_vel发布的是/cmd_vel话题,那么底盘控制节点订阅到这个话题后,就可以控制机器人运动。
2.4 完整发布器示例代码
周期发布 /cmd_vel 速度消息
下面是一个简单的 ROS2 C++ 发布器节点,功能是周期性发布速度消息,让机器人向前运动。
#include "rclcpp/rclcpp.hpp" #include "geometry_msgs/msg/twist.hpp" using namespace std::chrono_literals; class CmdVelPublisher : public rclcpp::Node { public: CmdVelPublisher() : Node("cmd_vel_publisher") { pub_vel = this->create_publisher<geometry_msgs::msg::Twist>("/cmd_vel", 10); timer = this->create_wall_timer( 500ms, std::bind(&CmdVelPublisher::timer_callback, this) ); } private: void timer_callback() { geometry_msgs::msg::Twist vel_cmd; vel_cmd.linear.x = 0.1; vel_cmd.linear.y = 0.0; vel_cmd.linear.z = 0.0; vel_cmd.angular.x = 0.0; vel_cmd.angular.y = 0.0; vel_cmd.angular.z = 0.0; pub_vel->publish(vel_cmd); RCLCPP_INFO( this->get_logger(), "Publishing: linear.x = %.2f, angular.z = %.2f", vel_cmd.linear.x, vel_cmd.angular.z ); } private: rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr pub_vel; rclcpp::TimerBase::SharedPtr timer; }; int main(int argc, char * argv[]) { rclcpp::init(argc, argv); auto node = std::make_shared<CmdVelPublisher>(); rclcpp::spin(node); rclcpp::shutdown(); return 0; }2.5 发布器代码重点解释
(1)包含头文件
#include "rclcpp/rclcpp.hpp" #include "geometry_msgs/msg/twist.hpp"其中:
rclcpp/rclcpp.hpp是ROS2 C++ 客户端库头文件。
geometry_msgs/msg/twist.hpp是Twist消息类型对应的头文件。
只要代码里使用:
geometry_msgs::msg::Twist就需要包含:
#include "geometry_msgs/msg/twist.hpp"(2)创建发布器
pub_vel = this->create_publisher<geometry_msgs::msg::Twist>("/cmd_vel", 10);这句表示创建/cmd_vel话题发布器。
消息类型是:
geometry_msgs::msg::Twist话题名字是:
/cmd_vel(3)创建定时器
timer = this->create_wall_timer( 500ms, std::bind(&CmdVelPublisher::timer_callback, this) );这句表示每隔500ms执行一次:
timer_callback()所以这个节点会周期性发布速度消息。
(4)发布速度消息
pub_vel->publish(vel_cmd);这句才是真正把速度消息发布出去。
如果没有这句,前面只是创建了消息、给消息赋值,并没有真正发到话题中。
三、ROS2 订阅器 Subscriber 的写法
3.1 订阅器的基本定义方式
Subscriber 也需要指定消息类型
如果要订阅Twist消息,订阅器一般这样定义:
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr sub_vel;可以拆成:
rclcpp::Subscription<geometry_msgs::msg::Twist>表示这是一个订阅Twist消息的订阅器。
SharedPtr表示智能指针类型。
sub_vel是订阅器对象名。
完整含义是:
定义一个订阅 geometry_msgs::msg::Twist 消息的订阅器,名字叫 sub_vel。
3.2 创建订阅器
使用 create_subscription 创建订阅器
创建订阅器一般这样写:
sub_vel = this->create_subscription<geometry_msgs::msg::Twist>( "/cmd_vel", 10, std::bind(&CmdVelSubscriber::cmd_vel_callback, this, std::placeholders::_1) );其中:
geometry_msgs::msg::Twist表示订阅的消息类型。
"/cmd_vel"表示订阅的话题名字。
10表示队列深度。
std::bind(&CmdVelSubscriber::cmd_vel_callback, this, std::placeholders::_1)表示收到消息后,执行cmd_vel_callback回调函数。
3.3 回调函数中接收消息
回调函数一般这样写:
void cmd_vel_callback(const geometry_msgs::msg::Twist::SharedPtr msg) { RCLCPP_INFO( this->get_logger(), "Received: linear.x = %.2f, angular.z = %.2f", msg->linear.x, msg->angular.z ); }这里需要特别注意:
msg是一个指针,所以访问字段时使用:
msg->linear.x msg->angular.z而不是:
msg.linear.x msg.angular.z3.4 完整订阅器示例代码
订阅 /cmd_vel 并打印速度
下面代码订阅/cmd_vel话题,并打印收到的速度信息。
#include "rclcpp/rclcpp.hpp" #include "geometry_msgs/msg/twist.hpp" class CmdVelSubscriber : public rclcpp::Node { public: CmdVelSubscriber() : Node("cmd_vel_subscriber") { sub_vel = this->create_subscription<geometry_msgs::msg::Twist>( "/cmd_vel", 10, std::bind(&CmdVelSubscriber::cmd_vel_callback, this, std::placeholders::_1) ); } private: void cmd_vel_callback(const geometry_msgs::msg::Twist::SharedPtr msg) { RCLCPP_INFO( this->get_logger(), "Received: linear.x = %.2f, angular.z = %.2f", msg->linear.x, msg->angular.z ); } private: rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr sub_vel; }; int main(int argc, char * argv[]) { rclcpp::init(argc, argv); auto node = std::make_shared<CmdVelSubscriber>(); rclcpp::spin(node); rclcpp::shutdown(); return 0; }3.5 发布端和订阅端访问字段的区别
(1)发布端通常使用点号
发布端一般是普通对象:
geometry_msgs::msg::Twist vel_cmd;所以访问字段使用:
vel_cmd.linear.x vel_cmd.angular.z(2)订阅端通常使用箭头
订阅端回调函数中收到的msg通常是智能指针:
const geometry_msgs::msg::Twist::SharedPtr msg所以访问字段使用:
msg->linear.x msg->angular.z可以简单记成:
- 普通对象用 .
- 指针对象用 ->
对比如下:
| 场景 | 写法 | 说明 |
|---|---|---|
| 发布端消息对象 | vel_cmd.linear.x | vel_cmd是普通对象 |
| 订阅端消息指针 | msg->linear.x | msg是指针 |
