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;
}
相关推荐
木卫二号Coding17 分钟前
jupyterlab-安装与启动
linux·运维·服务器
fengyehongWorld40 分钟前
Linux logger命令
linux·运维·服务器
南知意-1 小时前
GitHub 6K Star! 一款完全免费的音乐播放器,爽听VIP歌曲!
linux·windows·开源·github·开源软件·mac
好好学习啊天天向上1 小时前
最新:ubuntu上源码安装llvm,还没有成功
linux·运维·ubuntu
Stestack1 小时前
ssh批量机器免密操作
linux·python·ssh
jerryinwuhan2 小时前
1231_linux_shell_1
linux
Guistar~~2 小时前
【Linux驱动开发IMX6ULL】使用NXP MfgTool 烧写系统到eMMC
linux·运维·驱动开发
啵啵啵啵哲2 小时前
【输入法】Ubuntu 22.04 终极输入法方案:Fcitx5 + 雾凇拼音 (Flatpak版)
linux·运维·ubuntu
Y unes2 小时前
《uboot基础命令记录①》
linux·驱动开发·嵌入式硬件·mcu·ubuntu·uboot
渝妳学C2 小时前
深度解析Linux中编译器gcc/g++
linux·运维