2.2 话题通信

2.1 使用C++实现话题通信

2.1.1 话题发布方实现

cpp 复制代码
// 1、包含头文件(补充字符串消息头文件)
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

// 3、定义节点类
class Talker : public rclcpp::Node
{
public:
    // 修正:派生类构造函数正确写法
    Talker() : Node("talker_node_cpp")
    {
        // 3-1 创建发布方(发布std_msgs/msg/String类型,话题名"chatter",队列大小10)
        pub_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);

        // 3-2 创建定时器(固定频率1Hz,绑定回调函数)
        /*
            参数:1、时间间隔
                 2、回调函数
                 返回一个指针对象
        */
        timer_ = this->create_wall_timer(
            std::chrono::seconds(1), 
            std::bind(&Talker::timer_callback, this)
        );
        

        // 日志打印,确认节点启动
        RCLCPP_INFO(this->get_logger(), "发布节点已启动,开始以1Hz发送hello world!");
    }

private:
    // 3-3 组织消息发布的回调函数
    void timer_callback()
    {
        // 初始化消息对象
        auto msg = std_msgs::msg::String();
        msg.data = "hello world"+std::to_string(count++); // 设置要发送的内容

        // 发布消息
        pub_->publish(msg);

        // 日志打印(可选,验证发送)
        RCLCPP_INFO(this->get_logger(), "发送消息:%s", msg.data.c_str());
    }

    // 成员变量:发布者、定时器(类内私有)
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
    size_t count=0;
    rclcpp::TimerBase::SharedPtr timer_;
}; // 修正:类定义末尾添加分号

// 主函数(实现ROS2客户端初始化、spin、资源释放)
int main(int argc, char *argv[])
{
    // 2、初始化ROS2客户端
    rclcpp::init(argc, argv);

    // 4、调用spin函数,传入节点对象指针
    rclcpp::spin(std::make_shared<Talker>());

    // 5、释放资源
    rclcpp::shutdown();
    return 0;
}

2.1.2 话题订阅方实现

cpp 复制代码
/*
    需求:订阅发布方发布的消息
    1、包含头文件
    2、初始化Ros2客户端
    3、自定义节点类
        3-1 创建订阅方
        3-2 解析并输出数据
    4、调用spin函数,并传入节点对象指针
    5、资源释放
*/
// 1、包含头文件
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

// 3、自定义节点类
class Listener: public rclcpp::Node{
public:
    // 修正:构造函数末尾去掉多余的分号
    Listener():Node("listener_node_cpp"){
        RCLCPP_INFO(this->get_logger(),"订阅方创建!");
        // 3-1 创建订阅方
        // 修正:占位符添加下划线 _1
        subscription_= this->create_subscription<std_msgs::msg::String>(
            "chatter",
            10,
            std::bind(&Listener::do_cb,this,std::placeholders::_1)
        );
    }
private:
    // 3-2 解析数据并输出数据
    // 修正:RCLCPP::INFO → RCLCPP_INFO
    void do_cb(const std_msgs::msg::String &msg){
        RCLCPP_INFO(this->get_logger(),"订阅到的消息:%s",msg.data.c_str());
    }
    // 修正:Sharedptr → SharedPtr(P大写)
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

// 修正:main函数参数 char const *argv[] → char *argv[]
int main(int argc, char *argv[])
{
    // 2、初始化Ros2客户端
    rclcpp::init(argc,argv);
    // 4、调用spin函数,并传入节点对象指针
    rclcpp::spin(std::make_shared<Listener>());
    // 5、资源释放
    rclcpp::shutdown();
    return 0;
}

运行结果:

2.2 使用python实现话题通信

2.2.1 话题发布

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ROS2 Python 发布方实现:以1Hz频率发布带计数的hello world字符串
对应C++功能:
1. 初始化ROS2节点
2. 创建发布者
3. 定时器1秒触发一次
4. 发布带计数的hello world消息
"""

# 1、导入ROS2核心模块和消息类型
import rclpy
from rclpy.node import Node
# 字符串消息类型(和C++的std_msgs/msg/String对应)
from std_msgs.msg import String
# 用于定时器(可选,也可以直接用rclpy的定时器)
import time


# 3、定义发布方节点类(继承ROS2的Node类)
class TalkerNode(Node):
    def __init__(self):
        # 初始化父类Node,节点名和C++保持一致:talker_node_cpp
        super().__init__("talker_node_cpp")
        self.count = 0  # 计数变量(对应C++的size_t count=0)
        
        # 3-1 创建发布者(参数:消息类型、话题名、队列大小)
        self.pub_ = self.create_publisher(
            msg_type=String,    # 消息类型:std_msgs/String
            topic="chatter",    # 话题名:和C++一致
            qos_profile=10      # 队列大小:和C++一致
        )

        # 3-2 创建定时器(参数:定时周期(秒)、回调函数)
        # timer_period=1.0 对应C++的std::chrono::seconds(1)
        self.timer_ = self.create_timer(
            1.0,
            callback=self.timer_callback  # 绑定回调函数
        )

        # 日志打印(对应C++的RCLCPP_INFO)
        self.get_logger().info("发布节点已启动,开始以1Hz发送hello world!")

    # 3-3 定时器回调函数(组织消息并发布)
    def timer_callback(self):
        # 初始化消息对象(对应C++的auto msg = std_msgs::msg::String())
        msg = String()
        # 拼接计数(对应C++的"hello world"+std::to_string(count++))
        msg.data = f"hello world{self.count}"
        self.count += 1  # 计数自增

        # 发布消息(对应C++的pub_->publish(msg))
        self.pub_.publish(msg)

        # 日志打印发送的消息(可选)
        self.get_logger().info(f"发送消息:{msg.data}")


# 主函数(程序入口)
def main(args=None):
    # 2、初始化ROS2客户端(对应C++的rclcpp::init(argc, argv))
    rclpy.init(args=args)

    # 创建节点对象(对应C++的std::make_shared<Talker>())
    talker_node = TalkerNode()

    # 4、调用spin函数,让节点持续运行(对应C++的rclcpp::spin)
    rclpy.spin(talker_node)

    # 5、释放资源(对应C++的rclcpp::shutdown())
    talker_node.destroy_node()  # 销毁节点(Python特有,更安全)
    rclpy.shutdown()            # 关闭ROS2客户端


# 程序入口判断(Python规范)
if __name__ == "__main__":
    main()

2.2.2 话题订阅

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ROS2 Python 订阅方实现:订阅chatter话题的hello world计数消息
对应发布方功能:
1. 初始化ROS2节点
2. 创建订阅者,监听chatter话题
3. 收到消息后解析并打印
"""

# 1、导入ROS2核心模块和消息类型
import rclpy
from rclpy.node import Node
# 和发布方一致的字符串消息类型
from std_msgs.msg import String


# 3、定义订阅方节点类(继承ROS2的Node类)
class ListenerNode(Node):
    def __init__(self):
        # 初始化父类Node,节点名:listener_node_cpp(和C++订阅方命名风格一致)
        super().__init__("listener_node_cpp")

        # 3-1 创建订阅者(参数:消息类型、话题名、回调函数、队列大小)
        self.sub_ = self.create_subscription(
            msg_type=String,          # 消息类型:和发布方完全一致
            topic="chatter",          # 订阅的话题名:必须和发布方一致
            callback=self.msg_callback,  # 收到消息后执行的回调函数
            qos_profile=10            # 队列大小:和发布方一致
        )

        # 日志打印:确认订阅方启动
        self.get_logger().info("订阅节点已启动,等待接收chatter话题消息...")

    # 3-2 消息回调函数(解析并打印收到的消息)
    def msg_callback(self, msg):
        # msg是收到的消息对象,msg.data对应发布方的消息内容
        self.get_logger().info(f"订阅到消息:{msg.data}")


# 主函数(程序入口)
def main(args=None):
    # 2、初始化ROS2客户端
    rclpy.init(args=args)

    # 创建订阅方节点对象
    listener_node = ListenerNode()

    # 4、调用spin函数,让节点持续运行(等待接收消息)
    rclpy.spin(listener_node)

    # 5、释放资源
    listener_node.destroy_node()  # 销毁节点
    rclpy.shutdown()              # 关闭ROS2客户端


# 程序入口判断(Python规范)
if __name__ == "__main__":
    main()

2.3 话题通信之自定义消息接口

1、创建.msg文件

在功能包下创建msg文件夹,msg文件夹下创建Student.msg文件,文件内容如下:

string name

int32 age

float64 heightS

2、编辑配置文件

(1)、package.xml文件配置

新增以下依赖:

<!-- 3. 消息编译/运行依赖 -->

<build_depend>rosidl_default_generators</build_depend>

<exec_depend>rosidl_default_runtime</exec_depend>

<!-- 4. 声明为消息接口包 -->

<member_of_group>rosidl_interface_packages</member_of_group>

自定义.msg文件是纯文本格式 (比如int32 age; string name),ROS2 无法直接识别和使用,必须通过工具把它转换成C++/Python 可调用的代码 (比如 C++ 的Student.hpp、Python 的Student.py)。

而你添加的这些依赖 / 声明,就是为了让 ROS2 完成 "文本→代码" 的转换,以及后续能正常使用生成的代码。

1. <build_depend>rosidl_default_generators</build_depend>
  • 中文含义 :"编译阶段依赖" → rosidl_default_generators(ROS2 默认消息生成器)
  • 核心作用 :提供编译.msg文件的工具(比如rosidl_generate_interfaces),负责把你的Student.msg转换成 C++/Python 代码。
  • 没有它会怎样CMakeLists.txt里的rosidl_generate_interfaces会报错 "找不到该工具",.msg文件无法编译,生成不了对应的代码。
2. <exec_depend>rosidl_default_runtime</exec_depend>
  • 中文含义 :"运行阶段依赖" → rosidl_default_runtime(ROS2 消息运行时库)
  • 核心作用 :生成的消息代码(比如 C++ 的Student类)需要依赖这个库才能运行(比如消息的序列化 / 反序列化、网络传输)。
  • 没有它会怎样 :编译消息包可能成功,但其他节点调用Student.msg时会报 "找不到消息运行时库",无法发布 / 订阅自定义消息。
3. <member_of_group>rosidl_interface_packages</member_of_group>
  • 中文含义:"声明该包属于'消息接口包组'"
  • 核心作用:告诉 ROS2:"这个包是用来定义消息 / 服务接口的,需要按接口包的规则编译和导出"。ROS2 有专门的逻辑处理接口包(比如把生成的消息代码安装到系统路径、让其他包能找到)。
  • 没有它会怎样 :编译可能成功,但其他功能包无法通过find_package(base_interfaces_demo)找到你的自定义消息,调用时会报 "找不到消息类型"。

完整的packages.xml:

XML 复制代码
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <!-- 1. 基础信息 -->
  <name>base_interfaces_demo</name>
  <version>0.0.0</version>
  <description>自定义消息接口包(Student.msg)</description>
  <maintainer email="moweiduo@todo.todo">moweiduo</maintainer>
  <license>Apache-2.0</license>

  <!-- 2. 构建工具依赖 -->
  <buildtool_depend>ament_cmake</buildtool_depend>

  <!-- 3. 消息编译/运行依赖 -->
  <build_depend>rosidl_default_generators</build_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>

  <!-- 4. 声明为消息接口包 -->
  <member_of_group>rosidl_interface_packages</member_of_group>

  <!-- 5. 测试依赖(修正尖括号) -->
  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <!-- 6. 导出配置 -->
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

(2)、CMakeLists.txt文件

find_package(rosidl_default_generators REQUIRED)

为接口文件生成源代码

rosidl_generate_interfaces(

${PROJECT_NAME}

"msg/Student.msg"

)

类比 :就像你要做蛋糕,先找 "烤箱 + 模具"(rosidl_default_generators),find_package就是 "确认厨房有这些工具",REQUIRED是 "没工具就不做了,直接告诉用户"。

3、编译

colcon build --packages-select base_interfaces_demo

4、测试

ros2 interface show base_interfaces_demo/msg/Student

同时,在install的include下会生成对应的包(Student.h/Student.hpp)

在linstall的ocal下会生成student.py

2.3.1 话题通信之自定义消息(C++)

1、发布方

cpp 复制代码
/*
需求:以某个固定频率发送文本学生信息,包含学生的姓名、年龄、身高等数据。
*/

// 1. 包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"

// 命名空间简化
using namespace std::chrono_literals;
using base_interfaces_demo::msg::Student;

// 3. 定义节点类
class MinimalPublisher : public rclcpp::Node
{
public:
    MinimalPublisher()
    : Node("student_publisher"), count_(0)
    {
        // 3-1. 创建发布方
        publisher_ = this->create_publisher<Student>("topic_stu", 10);

        // 3-2. 创建定时器(500ms触发一次)
        timer_ = this->create_wall_timer(
            500ms, 
            std::bind(&MinimalPublisher::timer_callback, this)
        );
    }

private:
    // 3-3. 组织消息并发布
    void timer_callback()
    {
        auto stu = Student();
        stu.name = "张三";
        stu.age = count_++;
        stu.height = 1.65;

        // 打印日志
        RCLCPP_INFO(
            this->get_logger(), 
            "学生信息: name=%s, age=%d, height=%.2f",
            stu.name.c_str(), stu.age, stu.height
        );

        // 发布消息
        publisher_->publish(stu);
    }

    // 成员变量
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher<Student>::SharedPtr publisher_;
    size_t count_;
};


int main(int argc, char * argv[])
{
    // 2. 初始化ROS2客户端
    rclcpp::init(argc, argv);

    // 4. 调用spin函数,传入节点对象指针
    rclcpp::spin(std::make_shared<MinimalPublisher>());

    // 5. 释放资源
    rclcpp::shutdown();
    return 0;
}

2、接收方

cpp 复制代码
/*
需求:订阅发布方发布的学生消息,并输出到终端。
*/

// 1. 包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"

// 命名空间简化
using std::placeholders::_1;
using base_interfaces_demo::msg::Student;

// 3. 定义节点类
class MinimalSubscriber : public rclcpp::Node
{
public:
    MinimalSubscriber()
    : Node("student_subscriber")
    {
        // 3-1. 创建订阅方
        subscription_ = this->create_subscription<Student>(
            "topic_stu", 
            10, 
            std::bind(&MinimalSubscriber::topic_callback, this, _1)
        );
    }

private:
    // 3-2. 处理订阅到的消息
    void topic_callback(const Student & msg) const
    {
        RCLCPP_INFO(
            this->get_logger(), 
            "订阅的学生消息: name=%s, age=%d, height=%.2f",
            msg.name.c_str(), msg.age, msg.height
        );
    }

    // 成员变量:订阅方对象
    rclcpp::Subscription<Student>::SharedPtr subscription_;
};


int main(int argc, char * argv[])
{
    // 2. 初始化ROS2客户端
    rclcpp::init(argc, argv);

    // 4. 调用spin函数,传入节点对象指针
    rclcpp::spin(std::make_shared<MinimalSubscriber>());

    // 5. 释放资源
    rclcpp::shutdown();
    return 0;
}
相关推荐
码农12138号3 小时前
Bugku HackINI 2022 Whois 详解
linux·web安全·ctf·命令执行·bugku·换行符
Joren的学习记录4 小时前
【Linux运维进阶知识】Nginx负载均衡
linux·运维·nginx
用户2190326527354 小时前
Java后端必须的Docker 部署 Redis 集群完整指南
linux·后端
胡先生不姓胡4 小时前
如何获取跨系统调用的函数调用栈
linux
里纽斯5 小时前
RK平台Watchdog硬件看门狗验证
android·linux·rk3588·watchdog·看门狗·rk平台·wtd
chem41116 小时前
魔百盒 私有网盘seafile搭建
linux·运维·网络
早睡的叶子6 小时前
VM / IREE 的调度器架构
linux·运维·架构
兄台の请冷静6 小时前
linux 安装sentinel 并加入systemctl
linux·运维·sentinel
skywalk81636 小时前
postmarketos一个专为智能手机和平板设备设计的开源 Linux 发行版 支持红米2
linux·智能手机·电脑
青梅煮久6 小时前
RK3566 Linux实例应用(1)——环境编译与烧录
linux·数据库·php