ROS2 话题通信实战:消息对象、Publisher 发布器与 Subscriber 订阅器保姆级教程

目录

摘要

[一、ROS2 消息对象的常见写法](#一、ROS2 消息对象的常见写法)

[1.1 创建消息对象的基本格式](#1.1 创建消息对象的基本格式)

[1.2 以 Twist 为例创建速度消息](#1.2 以 Twist 为例创建速度消息)

[1.3 给消息字段赋值](#1.3 给消息字段赋值)

(1)让机器人向前运动

(2)让机器人原地旋转

(3)让机器人停止

[二、ROS2 发布器 Publisher 的写法](#二、ROS2 发布器 Publisher 的写法)

[2.1 发布器的基本定义方式](#2.1 发布器的基本定义方式)

[2.2 创建发布器](#2.2 创建发布器)

[2.3 发布消息](#2.3 发布消息)

[2.4 完整发布器示例代码](#2.4 完整发布器示例代码)

[2.5 发布器代码重点解释](#2.5 发布器代码重点解释)

(1)包含头文件

(2)创建发布器

(3)创建定时器

(4)发布速度消息

[三、ROS2 订阅器 Subscriber 的写法](#三、ROS2 订阅器 Subscriber 的写法)

[3.1 订阅器的基本定义方式](#3.1 订阅器的基本定义方式)

[3.2 创建订阅器](#3.2 创建订阅器)

[3.3 回调函数中接收消息](#3.3 回调函数中接收消息)

[3.4 完整订阅器示例代码](#3.4 完整订阅器示例代码)

[3.5 发布端和订阅端访问字段的区别](#3.5 发布端和订阅端访问字段的区别)

(1)发布端通常使用点号

(2)订阅端通常使用箭头


摘要

本篇是 ROS2 话题通信保姆级教程专题 的第二篇文章。

上一篇文章主要讲解了 ROS2 中常见的消息类型,例如 std_msgsgeometry_msgsnav_msgssensor_msgs,并重点说明了 geometry_msgs::msg::TwistPosePoseStampedTransformStamped 等消息的含义。

为了方便大家按照顺序学习,ROS2 话题通信专题文章整理如下:

第一篇:ROS2 常见消息类型保姆级教程

ROS2 常见消息类型保姆级教程-CSDN博客https://blog.csdn.net/m0_58954356/article/details/161693378?spm=1001.2014.3001.5501第三篇:ROS2 常用命令保姆级教程:node、topic、interface、run、launch、param 一文搞懂

史上最全 ROS2 常用命令保姆级教程:节点、话题、消息结构、参数、服务和动作一篇讲透-CSDN博客https://blog.csdn.net/m0_58954356/article/details/161726502?spm=1001.2014.3001.5501这一篇开始进入 ROS2 话题通信实战,重点讲清楚几个问题:

1. ROS2 中如何创建消息对象?
2. Publisher 发布器怎么写?
3. Subscriber 订阅器怎么写?

本文仍然以移动机器人常用的速度控制消息:

复制代码
geometry_msgs::msg::Twist

为例,带大家从代码层面真正看懂 ROS2 的话题发布、话题订阅和消息字段查看方法,为后续学习 ros2 topicros2 noderos2 interface 等常用调试命令打好基础。


一、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 是一个变量,只不过它的类型不是 intdouble,而是 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 发布出去

代码如下:

cpp 复制代码
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++ 发布器节点,功能是周期性发布速度消息,让机器人向前运动。

cpp 复制代码
#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)包含头文件

cpp 复制代码
#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)创建发布器

cpp 复制代码
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 创建订阅器

创建订阅器一般这样写:

cpp 复制代码
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 回调函数中接收消息

回调函数一般这样写:

cpp 复制代码
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.z

3.4 完整订阅器示例代码

订阅 /cmd_vel 并打印速度

下面代码订阅 /cmd_vel 话题,并打印收到的速度信息。

cpp 复制代码
#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

可以简单记成:

  1. 普通对象用 .
  2. 指针对象用 ->

对比如下:

场景 写法 说明
发布端消息对象 vel_cmd.linear.x vel_cmd 是普通对象
订阅端消息指针 msg->linear.x msg 是指针
相关推荐
艾iYYY2 小时前
string 类的模拟实现
android·服务器·c语言·c++·算法
为何创造硅基生物2 小时前
C++ virtual void StartNetwork() = 0; // 纯虚:子类必须实现,否则不能 new。
c++
知无不研3 小时前
对套接字的深入理解
linux·服务器·网络·c++·socket·网络套接字
hai3152475434 小时前
FlashAttention C语言(C++)实现(展示版)
c语言·开发语言·c++·人工智能·算法
wuminyu5 小时前
Java锁机制之Java对象重量级锁源码剖析
java·linux·c语言·jvm·c++
郝学胜_神的一滴6 小时前
Qt 高级开发 026:QTabWidget御道,从筑基到化境
c++·qt
apocelipes6 小时前
GNU GCC 多版本函数扩展
c语言·c++·linux编程
代码中介商6 小时前
C++完美转发与引用折叠全解析
开发语言·c++
雪度娃娃7 小时前
ASIO异步通信——多线程模型
开发语言·网络·c++·php