第1章:ROS基础概念——节点、话题、服务、参数、消息
各位同学,欢迎来到ROS的世界。我是你们的讲师,一个在机器人堆里摸爬滚打了十来年的老工程师。
今天咱们要聊的,是ROS最核心的五个概念:节点、话题、服务、参数、消息。说白了,这五个东西就是ROS的“五脏六腑”。你搞懂了它们,后面搭建仿真环境、写控制程序,就会顺风顺水。要是没搞懂……嗯,后面你会回来重新看这一章的。
1.1 节点(Node)——机器人的“小工人”
节点是什么?我习惯把它想象成一个“小工人”。每个节点只干一件事,比如控制左轮、处理激光雷达数据、发布里程计信息。
在ROS里,一个机器人系统通常由几十个甚至上百个节点组成。每个节点都是独立的可执行程序。它们之间互不干扰,各司其职。
为什么要把程序拆成这么多节点?
你想想看,如果所有功能都写在一个程序里,一旦某个模块崩溃,整个机器人就挂了。但用节点的方式,某个节点挂了,其他节点还能继续跑。我在项目里遇到过激光雷达节点偶尔闪退的情况,但底盘控制节点还在正常工作,机器人至少能安全停下来。这就是节点化的好处。
创建一个节点很简单,用Python的话就几行代码:
#!/usr/bin/env python3
import rospy
rospy.init_node('my_first_node') # 初始化节点,名字叫my_first_node
rospy.loginfo("节点已启动!")
rospy.spin() # 让节点保持运行
注意节点名字不能重复。同一个ROS系统里,节点名是唯一的标识。
lidar_driver、motor_controller。这样一看名字就知道它是干什么的。
1.2 话题(Topic)——节点间的“广播站”
节点之间怎么通信?最常用的方式就是话题。
话题就像广播站。一个节点往话题里发消息(发布者),其他节点从话题里收消息(订阅者)。发布者和订阅者互相不知道对方是谁,它们只关心话题本身。
举个例子:
激光雷达节点不断发布/scan话题,里面装着激光扫描数据。导航节点订阅/scan,拿到数据后做路径规划。这两个节点完全解耦,互不依赖。
我曾经调试过一个项目,发现激光雷达数据时有时无。排查了半天,原来是发布者的频率设置得太低,订阅者等不及就超时了。所以,发布频率和订阅频率要匹配好。
发布话题的代码长这样:
pub = rospy.Publisher('chatter', String, queue_size=10)
rate = rospy.Rate(10) # 10Hz
while not rospy.is_shutdown():
pub.publish("hello world")
rate.sleep()
1.3 服务(Service)——节点间的“电话通话”
话题是单向广播,服务则是双向的请求-响应模式。就像你打电话:你问一句,对方答一句,有来有回。
什么时候用服务?当你需要某个节点执行一个操作,并且等待结果时。比如:
- 请求机器人拍照,返回图片数据
- 请求机械臂移动到某个位置,返回是否成功
- 请求获取当前电池电量,返回百分比
服务的定义包含两部分:请求(Request)和响应(Response)。
写一个服务端:
def handle_add_two_ints(req):
return AddTwoIntsResponse(req.a + req.b)
rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)
写一个客户端:
rospy.wait_for_service('add_two_ints')
try:
add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
resp = add_two_ints(3, 5)
print(resp.sum)
except rospy.ServiceException as e:
print("服务调用失败: %s" % e)
1.4 参数(Parameter)——机器人的“小本本”
参数服务器就像一个全局的小本本。节点可以把配置信息写进去,也可以从里面读出来。
比如:机器人的轮子直径、最大速度、PID参数、摄像头分辨率……这些都可以放在参数里。这样你改参数时不用重新编译代码,改一下配置文件就行。
我习惯把参数分成两类:
- 静态参数:基本不变,比如机器人型号、传感器类型
- 动态参数:调试时经常改,比如速度上限、控制增益
操作参数的常用命令:
# 设置参数
rospy.set_param('max_speed', 1.5)
# 读取参数
speed = rospy.get_param('max_speed')
# 检查参数是否存在
if rospy.has_param('max_speed'):
print("参数存在")
# 删除参数
rospy.delete_param('max_speed')
1.5 消息(Message)——节点间的“通用语言”
节点之间传数据,总得有个统一的格式吧?消息就是干这个的。
ROS定义了很多标准消息类型,比如:
| 消息类型 | 说明 | 常用场景 |
|---|---|---|
std_msgs/String |
字符串 | 日志、简单指令 |
geometry_msgs/Twist |
速度指令(线速度+角速度) | 控制机器人移动 |
sensor_msgs/LaserScan |
激光雷达扫描数据 | 避障、建图 |
nav_msgs/Odometry |
里程计数据(位置+速度) | 定位、导航 |
你也可以自定义消息。比如你做了一个特殊的传感器,需要传一些自定义数据,那就自己定义一个.msg文件。
自定义消息的格式:
# 文件: MySensorData.msg
float32 temperature
float32 humidity
uint16 light_intensity
string sensor_id
然后在代码里就能像使用标准消息一样使用它。
1.6 五个概念的关系——一张图说清楚
这五个概念不是孤立的,它们协同工作:
- 节点是执行者,负责干活
- 话题是广播通道,节点通过它发布和订阅消息
- 服务是双向通道,节点通过它发送请求和接收响应消息
- 参数是全局配置,所有节点都能读写
说白了,整个ROS系统就是一堆节点,通过话题和服务交换消息,同时从参数服务器读取配置。就这么简单。
我记得刚学ROS那会儿,总觉得这些概念很抽象。后来动手写了一个小机器人控制程序,把激光雷达数据通过话题发给导航节点,导航节点再通过话题发布速度指令给底盘节点——那一刻,我才真正理解了它们是怎么配合的。
所以,别光看,动手试试。下一章咱们就开始搭建Gazebo仿真环境,到时候这些概念都会用上。
roscore,然后新开一个终端运行rosrun turtlesim turtlesim_node。再用rostopic list看看有哪些话题在跑。这就是一个活生生的ROS系统。