3、ROS基础回顾(一):节点(Node)、话题(Topic)、服务(Service)、消息(Message)的概念与实操
各位同学,欢迎来到《仿真迁移到真机全流程实战课程》的第三讲。
说实话,很多初学者一上来就急着写代码、跑仿真,结果遇到真机就懵了。为什么?因为最底层的通信机制没吃透。今天这节课,咱们就把ROS的四大核心概念——节点、话题、服务、消息,掰开揉碎了讲清楚。
我个人习惯,学任何东西先画个图,把骨架搭起来。来,先看这张图,你就明白它们之间是什么关系了。
1. 节点(Node)—— 机器人的每一个“小兵”
节点是什么?说白了,节点就是ROS系统里的一个可执行程序。每个节点负责一个具体的功能。
我在项目中遇到过这样一个场景:一台移动机器人,有激光雷达节点、里程计节点、路径规划节点、电机控制节点……每个节点各司其职。你想想看,如果所有代码都写在一个程序里,那调试起来得多痛苦?
核心要点:
- 节点是ROS中的最小处理单元
- 每个节点只做一件事,并且把它做好
- 节点之间通过话题或服务通信
- 使用
rosrun或roslaunch启动节点
启动一个节点很简单,比如:
# 启动一个摄像头节点
rosrun usb_cam usb_cam_node
# 启动一个图像显示节点
rosrun image_view image_view image:=/usb_cam/image_raw
小技巧: 我个人习惯用 rosnode list 查看当前所有活跃节点。如果节点没启动成功,这个命令一看便知。
2. 话题(Topic)—— 机器人世界的“广播电台”
话题是ROS里最常用的通信方式。它是一种异步通信模型,说白了就是“发布-订阅”模式。
一个节点发布消息到某个话题,其他节点订阅这个话题就能收到消息。发布者和订阅者互不知道对方的存在,这就是解耦。
注意: 话题通信是单向的!发布者只管发,订阅者只管收。如果你需要请求-应答式的通信,请用服务。
来看一个实际例子。我在调试机器人底盘时,经常需要查看电机转速数据:
# 查看所有话题列表
rostopic list
# 查看某个话题的消息类型
rostopic info /cmd_vel
# 实时打印话题数据
rostopic echo /cmd_vel
# 发布一条速度指令(让机器人前进)
rostopic pub /cmd_vel geometry_msgs/Twist "linear:
x: 0.5
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.0"
话题的特点:
- 一对多通信:一个发布者,多个订阅者
- 异步:发布者不等待订阅者处理
- 实时性:适合传感器数据、控制指令等
- 使用
rostopic命令行工具调试
3. 消息(Message)—— 通信的“语言”
话题里传的是什么?就是消息。消息是ROS中定义的数据结构。
ROS自带了很多标准消息类型,比如:
| 消息类型 | 用途 | 常用字段 |
|---|---|---|
| std_msgs/String | 字符串消息 | data |
| geometry_msgs/Twist | 速度指令 | linear.x, angular.z |
| sensor_msgs/Image | 图像数据 | width, height, data |
| nav_msgs/Odometry | 里程计数据 | pose, twist |
你也可以自定义消息。比如我在做多传感器融合时,就定义过自己的消息类型:
# 自定义消息文件:my_msgs/msg/SensorFusion.msg
int32 sensor_id
float64 temperature
float64 humidity
float64 pressure
string location
经验之谈: 能用标准消息就别自己定义。标准消息兼容性好,别人一看就懂。我曾经因为自定义消息太复杂,导致团队协作时出了不少沟通问题。
4. 服务(Service)—— 一问一答的“对话”
服务是ROS中的另一种通信方式。它是同步的、双向的。客户端发送请求,服务端处理并返回响应。
什么时候用服务?比如你让机器人拍一张照片,然后等待照片返回。这种“请求-应答”的场景,用服务最合适。
# 查看所有可用服务
rosservice list
# 查看服务类型
rosservice info /gazebo/get_model_state
# 调用服务(请求数据)
rosservice call /gazebo/get_model_state "model_name: 'robot1'
relative_entity_name: 'world'"
服务 vs 话题:
- 话题:持续发布数据,适合传感器流
- 服务:按需调用,适合一次性操作
- 话题:异步,发布者不等待
- 服务:同步,客户端必须等待响应
避坑指南: 我曾经在控制机械臂时,用服务来发送每个关节的角度指令。结果因为服务是同步的,导致机械臂运动卡顿。后来改成话题发布,问题就解决了。记住:高频控制用话题,低频操作用服务。
5. 实操:写一个简单的发布-订阅程序
光说不练假把式。咱们写一个最简单的例子:一个节点发布字符串,另一个节点接收并打印。
发布者节点(talker.py):
#!/usr/bin/env python3
import rospy
from std_msgs.msg import String
def talker():
# 初始化节点
rospy.init_node('talker', anonymous=True)
# 创建发布者,发布到 /chatter 话题
pub = rospy.Publisher('/chatter', String, queue_size=10)
rate = rospy.Rate(1) # 1Hz
while not rospy.is_shutdown():
msg = String()
msg.data = "Hello ROS! 时间: %s" % rospy.get_time()
rospy.loginfo("发布消息: %s", msg.data)
pub.publish(msg)
rate.sleep()
if __name__ == '__main__':
try:
talker()
except rospy.ROSInterruptException:
pass
订阅者节点(listener.py):
#!/usr/bin/env python3
import rospy
from std_msgs.msg import String
def callback(msg):
rospy.loginfo("收到消息: %s", msg.data)
def listener():
rospy.init_node('listener', anonymous=True)
# 订阅 /chatter 话题
rospy.Subscriber('/chatter', String, callback)
rospy.spin() # 保持节点运行
if __name__ == '__main__':
listener()
运行方法:
- 先启动 roscore:
roscore - 运行发布者:
rosrun your_package talker.py - 运行订阅者:
rosrun your_package listener.py
你会看到订阅者终端不断打印发布者发来的消息。
6. 常用调试命令汇总
| 命令 | 作用 |
|---|---|
| rosnode list | 列出所有节点 |
| rosnode info /node_name | 查看节点详细信息 |
| rostopic list | 列出所有话题 |
| rostopic echo /topic_name | 打印话题数据 |
| rostopic hz /topic_name | 查看话题发布频率 |
| rosservice list | 列出所有服务 |
| rosservice call /service_name | 调用服务 |
| rosmsg show msg_type | 查看消息结构 |
嗯,到这里,ROS的四大核心概念就讲完了。节点是执行单元,话题是广播通道,消息是数据格式,服务是问答机制。这四个东西搞明白了,ROS就算入门了。
记住我的一句话:节点是骨架,话题是血管,消息是血液,服务是神经。这样想,是不是好记多了?