您现在的位置是:首页 >学无止境 >ros2教程【5】:话题通信(C++和python示例)网站首页学无止境

ros2教程【5】:话题通信(C++和python示例)

宋隽颢 2025-02-10 10:02:31
简介ros2教程【5】:话题通信(C++和python示例)

一、C++话题示例:

1.发布消息

1.2 工作空间+构建功能包+新建节点文件

这个是依托于小海龟的示例做的演示
首先还是
新建一个工作空间chapt3_ws,
在下面新建src源码库
在源码下面构建功能包,
在功能包的src文件夹下面新建turtle_circle.cpp文件
这些都是标准动作,具体看
链接: 构建一个功能包

mkdir -p ~/chapt3_ws/src
cd ~/chapt3_ws/src
ros2 pkg create demo_cpp_topic --build-type ament_cmake --dependencies rclcpp geometry_msgs turtlesim --license Apache-2.0
touch turtle_circle.cpp

在turtle_circle.cpp中输入:
代码内容:

// 包含 ROS 2 的核心库,用于创建节点、发布者、定时器等
#include "rclcpp/rclcpp.hpp" 
// 包含 geometry_msgs::msg::Twist 消息类型,用于发布速度指令
#include "geometry_msgs/msg/twist.hpp" 
// 包含标准库中的时间相关功能,用于定时器
#include <chrono> 

using namespace std::chrono_literals; // 使用 std::chrono 的字面量语法,如 1000ms

// 定义一个名为 TurtleCircleNode 的类,继承自 rclcpp::Node
class TurtleCircleNode : public rclcpp::Node
{
private:
		// 发布者对象,用于发布速度指令
    rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_; 
    // 定时器对象,用于周期性调用回调函数
    rclcpp::TimerBase::SharedPtr timer_; 
public:
    // 构造函数,初始化节点
    explicit TurtleCircleNode(const std::string &node_name) : Node(node_name)
    {
        // 创建一个发布者,发布速度指令到 "/turtle1/cmd_vel" 话题,队列大小为 10
        publisher_ = this->create_publisher<geometry_msgs::msg::Twist>("turtle1/cmd_vel", 10);

        // 创建一个定时器,每 1 秒调用一次 timer_callback 函数
        timer_ = this->create_wall_timer(1000ms, std::bind(&TurtleCircleNode::timer_callback, this));
    }

    // 定时器回调函数,用于发布速度指令
    void timer_callback()
    {
        // 创建一个 Twist 消息对象
        auto msg = geometry_msgs::msg::Twist();

        // 设置线速度为 1.0 m/s
        msg.linear.x = 1.0;

        // 设置角速度为 0.5 rad/s
        msg.angular.z = 0.5;

        // 发布速度指令
        publisher_->publish(msg);
    }
};

int main(int argc, char **argv)
{
    // 初始化 ROS 2 环境,设置日志系统、参数服务器等基础设施
    rclcpp::init(argc, argv);

    // 创建一个名为 "turtle_circle" 的 TurtleCircleNode 节点
    auto node = std::make_shared<TurtleCircleNode>("turtle_circle");

    // 运行节点的事件循环,处理定时器回调等事件
    rclcpp::spin(node);

    // 关闭 ROS 2 环境,清理资源
    rclcpp::shutdown();

    // 程序正常退出
    return 0;
}

1.2:CMakeList的处理

CMakeList.txt

add_executable(turtle_cirtle src/turtle_circle.cpp)

ament_target_dependencies(turtle_cirtle rclcpp geometry_msgs turtlesim)

install(TARGETS turtle_cirtle
DESTINATION lib/${PROJECT_NAME}
)

1.3:构建+测试

终端1:

cd ..
colcon build
source install/setup.bash 
ros2 run demo_cpp_topic turtle_circle

新建终端2:

ros2 run turtlesim turtlesim_node

这个时候就会出现一个小乌龟在画圆。

2.订阅消息:

2.1新建文件turtle_control.cpp

cd ~/chapt3_ws/src/demo_cpp_topic/src
touch turtle_control.cpp

输入代码:

#include "rclcpp/rclcpp.hpp"  // 包含 ROS 2 的核心库
#include "geometry_msgs/msg/twist.hpp"  // 包含 Twist 消息类型,用于发布速度指令
#include "turtlesim/msg/pose.hpp"  // 包含 Pose 消息类型,用于接收海龟的位置信息

using namespace std::chrono_literals;  // 使用 std::chrono 的字面量语法

// 定义一个名为 TurtleControl 的类,用于控制海龟移动到目标位置
class TurtleControl : public rclcpp::Node
{
private:
    rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_;  // 发布者对象,用于发布速度指令
    rclcpp::Subscription<turtlesim::msg::Pose>::SharedPtr Subscriber_;  // 订阅者对象,用于接收海龟的位置信息
    double target_x_{1.0};  // 目标 x 坐标
    double target_y_{1.0};  // 目标 y 坐标
    double k_{1.0};  // 控制增益,用于调节速度
    double max_speed_{3.0};  // 最大线速度限制

public:
    // 构造函数,初始化节点
    explicit TurtleControl(const std::string &node_name) : Node(node_name)
    {
        // 创建一个发布者,发布速度指令到 "/turtle1/cmd_vel" 话题
        publisher_ = this->create_publisher<geometry_msgs::msg::Twist>("turtle1/cmd_vel", 10);

        // 创建一个订阅者,订阅 "/turtle1/pose" 话题,接收海龟的位置信息
        Subscriber_ = this->create_subscription<turtlesim::msg::Pose>("/turtle1/pose", 10, std::bind(&TurtleControl::on_pose_reveived, this, std::placeholders::_1));
    }

    // 回调函数,处理接收到的海龟位置信息
    void on_pose_reveived(const turtlesim::msg::Pose::SharedPtr pose)
    {
        // 获取当前海龟的位置
        auto current_x = pose->x;
        auto current_y = pose->y;

        // 打印当前海龟的位置信息
        RCLCPP_INFO(get_logger(), "当前:x = %f,y = %f", current_x, current_y);

        // 计算当前海龟位置与目标位置之间的距离差
        auto distance = std::sqrt(
            (target_x_ - current_x) * (target_x_ - current_x) +
            (target_y_ - current_y) * (target_y_ - current_y));

        // 计算当前海龟位置与目标位置之间的角度差
        auto angle = std::atan2((target_y_ - current_y), (target_x_ - current_x)) - pose->theta;

        // 创建一个 Twist 消息对象,用于发布速度指令
        auto msg = geometry_msgs::msg::Twist();

        // 如果距离大于阈值(0.1),则进行控制
        if (distance > 0.1)
        {
            // 如果角度差大于阈值(0.2),则调整角度
            if (fabs(angle) > 0.2)
            {
                msg.angular.z = angle;  // 设置角速度
            }
            else
            {
                // 否则,设置线速度
                msg.linear.x = k_ * distance;
            }
        }

        // 限制线速度的最大值
        if (msg.linear.x > max_speed_)
        {
            msg.linear.x = max_speed_;
        }

        // 发布速度指令
        publisher_->publish(msg);
    }
};

int main(int args, char **argv)
{
    // 初始化 ROS 2 环境
    rclcpp::init(args, argv);

    // 创建一个名为 "turtle_control" 的 TurtleControl 节点
    auto node = std::make_shared<TurtleControl>("turtle_control");

    // 运行节点的事件循环
    rclcpp::spin(node);

    // 关闭 ROS 2 环境
    rclcpp::shutdown();

    // 程序正常退出
    return 0;
}

2.2:CMakeList的处理

CMakeList.txt

add_executable(turtle_cirtle src/turtle_circle.cpp)

add_executable(turtle_control src/turtle_control.cpp)

ament_target_dependencies(turtle_cirtle rclcpp geometry_msgs turtlesim)

ament_target_dependencies(turtle_control rclcpp geometry_msgs turtlesim)

install(TARGETS turtle_cirtle turtle_control
DESTINATION lib/${PROJECT_NAME}
)

2.3:构建+测试

终端1:

cd ~/chapt3_ws/src
colcon build
source install/setup.bash 
ros2 run demo_cpp_topic turtle_control

新建终端2:

ros2 run turtlesim turtlesim_node

这个时候就会出现一个小乌龟在跑步了。

二、python话题示例:

1.命名空间

新建一个chapt3_ws的命名空间,然后在下面新建src源码库,在chapt3_ws命名空间文件夹层面运行colcon build

cd ~/chapt3_ws/src
ros2 pkg create demo_python_topic --build-type ament_python --dependencies rclpy example_interfaces --license Apache-2.0
touch novel_pub_node.py

2.写入代码(发布消息)

touch novel_pub_node.py
看注释即可

import rclpy
from rclpy.node import Node
from example_interfaces.msg import String

class NovelPubNode(Node):
    def __init__(self,node_name):
        #调用父类方法,节点初始化工作
        super().__init__(node_name)
        self.get_logger().info(f'{node_name},启动!')
        self.i = 0  # 初始化变量 i
        self.novel_pub()

        
    def novel_pub(self):      
        self.novel_publisher = self.create_publisher(String,'novel',10)#创建发布者,分别对应数据类型,话题名,qos
        #ros2创建一个定时器,时间间隔5秒,回调函数和qos
        self.create_timer(2.0,self.novel_pub_timer)

    def novel_pub_timer(self):
        #创建发布数据类型  
        self.msg = String()
        self.i += 1  # 自增操作
        self.msg.data = f'你好!{self.i}'
        #发布数据
        self.novel_publisher.publish(self.msg)
 
def main():
    rclpy.init()
    node = NovelPubNode('novel_pub')
    rclpy.spin(node)
    rclpy.shutdown()

3.接收文字并朗读(订阅消息)

3.1:准备工作 (安装语音包)

//安装pip功能包
sudo apt install python3-pip -y
//安装语音朗读包
pip3 install espeakng
sudo apt install espeak-ng
cd ~/chapt3/src/demo_python_topic/demo_python_topic
touch novel_sub_node.py

novel_sub_node.py代码编辑:

import espeakng  # 导入espeakng库,用于文本转语音

import rclpy  # 导入rclpy库,ROS 2的Python客户端库
from rclpy.node import Node  # 从rclpy.node导入Node类,用于创建ROS 2节点
from example_interfaces.msg import String  # 从example_interfaces.msg导入String消息类型
from queue import Queue  # 从queue模块导入Queue类,用于创建线程安全的队列
import threading  # 导入threading模块,用于创建和管理线程
import time  # 导入time模块,用于执行时间相关的操作

class NovelSubNode(Node):
    def __init__(self, node_name):
        super().__init__(node_name)  # 调用父类Node的构造函数,初始化节点
        # 新建一个队列,用于存储从话题订阅接收到的文本信息
        self.novel_queue_ = Queue()
        self.get_logger().info(f'{node_name},启动!')  # 打印节点启动日志
        # 新建一个话题订阅,订阅名为'novel'的String类型话题,回调函数为sub_callback,队列长度为10
        self.novel_sub = self.create_subscription(String, 'novel', self.sub_callback, 10)
        # 绑定一个线程,用于处理队列中的文本信息并朗读
        self.novel_thread = threading.Thread(target=self.speaker_thread)
        # 启动线程,使其开始执行speaker_thread方法
        self.novel_thread.start()

    # 回调函数,当订阅的话题有新消息时,此方法会被调用
    def sub_callback(self, msg):
        # 把话题订阅到的信息(文本)存到队列里面,用于解决解读文本和发送文本速度不一致的问题
        self.novel_queue_.put(msg.data)
    
    def speaker_thread(self):
        # 新建朗读对象,用于将文本转换为语音
        speaker = espeakng.Speaker()
        # 设置朗读语言为中文
        speaker.voice = 'zh'
        # 判断rclpy环境是否正常,即ROS 2节点是否仍在运行
        while rclpy.ok():
            # 如果队列中有文本信息,则取出并朗读
            if self.novel_queue_.qsize() > 0:
                text = self.novel_queue_.get()  # 从队列中获取文本信息
                self.get_logger().info(f'启动朗读:{text}')  # 打印即将朗读的文本信息
                speaker.say(text)  # 使用espeakng朗读文本
                speaker.wait()  # 等待朗读完成
            else:
                time.sleep(1)  # 如果队列为空,则等待1秒后再检查队列,避免频繁检查浪费资源
 
def main():
    rclpy.init()  # 初始化rclpy环境
    node = NovelSubNode('novel_sub')  # 创建NovelSubNode节点实例
    rclpy.spin(node)  # 让节点保持运行状态,等待接收和处理消息
    rclpy.shutdown()  # 关闭rclpy环境,清理资源

4.编辑package.xml

 <depend>rclpy</depend>
  <depend>example_interfaces</depend>

5.编辑setup.py

entry_points={
    'console_scripts': [
        'novel_pub_python = demo_python.novel_pub_node:main',
        'novel_sub_python = demo_python.novel_sub_node:main',
        
    ],
},

6.构建+测试

终端1:

cd ~/chapt3/chapt3_ws
colcon build
source install/setup.bash
ros2 run demo_python_topic novel_pub_node

终端2:

cd ~/chapt3/chapt3_ws
source install/setup.bash
ros2 run demo_python_topic novel_pub_node

这个时候就听见有个机器人在读数了

三.命令行关于话题的各种查询

ros2 topic list 列出话题列表
ros2 topic echo /novel(话题名)   //输出话题内容
ros2 topic hz /novel(话题名)   //输出话题频率
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。