3、ROS基础回顾(一):节点(Node)、话题(Topic)、服务(Service)、消息(Message)的概念与实操

各位同学,欢迎来到《仿真迁移到真机全流程实战课程》的第三讲。

说实话,很多初学者一上来就急着写代码、跑仿真,结果遇到真机就懵了。为什么?因为最底层的通信机制没吃透。今天这节课,咱们就把ROS的四大核心概念——节点、话题、服务、消息,掰开揉碎了讲清楚。

我个人习惯,学任何东西先画个图,把骨架搭起来。来,先看这张图,你就明白它们之间是什么关系了。

ROS Master 节点1 (Node) /camera_node 节点2 (Node) /image_processor 节点3 (Node) /motor_controller 节点4 (Node) /display_node 话题 /image_raw 话题 /motor_cmd 服务 /set_speed 服务 /get_status 话题(异步通信) 服务(同步通信) 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()

运行方法:

  1. 先启动 roscore:roscore
  2. 运行发布者:rosrun your_package talker.py
  3. 运行订阅者: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就算入门了。

记住我的一句话:节点是骨架,话题是血管,消息是血液,服务是神经。这样想,是不是好记多了?

可可精品资料库