【多喝热水系列】从零开始的ROS2之旅------Day10 话题的订阅与发布1:Python
大家好,欢迎回到"多喝热水"ROS2系列专栏!经过前9天的铺垫,我们已经掌握了ROS2的基础环境配置、节点创建、日志打印等核心技能,今天终于要进入ROS2通信机制的核心------话题(Topic)。作为ROS2中最常用的异步通信方式,话题就像一个"广播电台",发布者(Publisher)往指定频道发消息,订阅者(Subscriber)监听频道收消息,完美适配"一方发送、多方接收"的场景。
一、核心目标
今天我们的核心任务是掌握:
- 学会通过ros2去发布小说
- 学会通过ros2去订阅小说
依赖工具/库:requests(下载小说)、espeakng(语音朗读)、queue(队列管理)、threading(多线程),以及ROS2内置的example_interfaces消息类型(用于文本传输)。
最重要的前提是要先启动本地HTTP服务器
二、Ros2发布小说
2.1 完整代码
python
import rclpy
from rclpy.node import Node
import requests
from example_interfaces.msg import String
#从example_interfaces包中导入String消息类型,用于发布文本消息,
from queue import Queue
class NovelPubNode(Node):
def __init__(self,node_name):
super().__init__(node_name)#继承父类的初始化方法
self.novels_queue_ = Queue()
#为什么要创建队列?因为下载的小说是多行的,发布消息时每次发布一行
self.publisher_ = self.create_publisher(String, 'novel', 10)
#创建发布者对象,发布String类型的消息,主题名为novel,队列长度10
self.timer_=self.create_timer(1,self.timer_callback)
#创建定时器对象,每1秒调用一次timer_callback函数
def download_novel(self,url):
response = requests.get(url)
#requests库发送HTTP GET请求,获取小说内容
response.encoding='utf-8'
#设置响应内容的编码格式为utf-8,防止中文乱码
self.get_logger().info(f'下载完成:{url}')
#打印日志,提示下载完成
for line in response.text.splitlines():
self.novels_queue_.put(line)
#将小说的每一行放入队列中
def timer_callback(self):
if self.novels_queue_.qsize()>0:
msg=String()#创建消息对象的作用是为了发布消息
msg.data=self.novels_queue_.get()
#从队列中获取一行小说内容将获取的小说内容赋值给消息对象的data属性
#data属性是String消息类型中用于存储文本内容的字段
self.publisher_.publish(msg)
#发布消息,将消息对象发布到主题上
self.get_logger().info(f'发布消息:{msg.data}')
def main():
rclpy.init()
node=NovelPubNode('novel_pub')
node.download_novel('http://localhost:8000/novel1.txt') # 下载小说
rclpy.spin(node)
rclpy.shutdown()
python
#setup.py添加节点
'novel_pub_node = demo_python_topic.novel_pub_node:main',

二、Ros2订阅小说
3.1依赖安装
bash
#apt安装Python3包管理工具
sudo apt install python3-pip -y
#apt安装espeak工具
sudo apt install espeak-ng -y
# 安装espeakng的Python库(语音朗读)
pip3 install espeakng
3.2 完整代码
python
import rclpy
from rclpy.node import Node
from example_interfaces.msg import String #从example_interfaces包中导入String消息类型
import threading
from queue import Queue
import time
import espeakng
class NovelSubNode(Node):
def __init__(self, node_name):
super().__init__(node_name)
self.get_logger().info('小说朗读节点启动')
self.novels_queue_= Queue()
self.get_logger().info('正在创建小说订阅者...')
self.novel_subscriber_ = self.create_subscription(
String,'novel',self.novel_callback,10
)
self.get_logger().info('小说订阅者创建成功,正在等待小说内容...')
self.speech_thread_=threading.Thread(target=self.speak_thread)
self.get_logger().info('启动小说朗读线程...')
self.speech_thread_.start()
self.get_logger().info('小说朗读线程启动成功')
def novel_callback(self, msg):
# if msg.data=='':
# self.get_logger().info('收到空小说内容,忽略该消息')
# else:
self.novels_queue_.put(msg.data)
self.get_logger().info('收到小说内容,已加入朗读队列')
def speak_thread(self):
speaker = espeakng.Speaker()
self.get_logger().info('初始化语音引擎...')
speaker.voice = 'zh'
self.get_logger().info('语音引擎初始化完成,开始朗读小说内容...')
while rclpy.ok() :
if self.novels_queue_.qsize() > 0:
self.get_logger().info('检测到朗读队列中有小说内容,开始朗读...')
text=self.novels_queue_.get()
self.get_logger().info(f'正在朗读小说内容:{text}')
speaker.say(text)
speaker.wait()
self.get_logger().info('小说内容朗读完成')
else:
time.sleep(1)
self.get_logger().info('朗读队列为空,等待新的小说内容...')
# 等待一段时间再检查队列,避免忙等待
def main(args=None):
rclpy.init(args=args)
node = NovelSubNode("novel_read")
rclpy.spin(node)
rclpy.shutdown()
python
#setup.py添加节点
'novel_sub_node = demo_python_topic.novel_sub_node:main',

小技巧总结:
解决订阅者队列接收不到数据的问题:

注释的快捷键:
ctrl+/
五、总结
通过今天的案例,我们不仅实现了话题的发布与订阅,还掌握了ROS2开发中的多个实用技巧。