)
本文用最通俗的方式讲清 ROS2 最核心的三个概念节点、话题、服务。读完你就能动手写第一个 ROS2 项目。适合零基础入门代码全在文末直接复制就能跑。 为什么要学 ROS2ROS2Robot Operating System 2是目前机器人开发领域的事实标准。不管你是做无人机、机械臂、智能小车还是工业机器人ROS2 都是绕不开的技术栈。但很多新手一开始就被各种概念劝退了节点、话题、服务、动作、参数、DDS…… 别急核心概念其实只有三个节点、话题、服务。把这三个搞明白ROS2 就算入门了。先上一张架构图帮你建立全局认知下面我们一个个来拆。 一、节点Node机器人的器官什么是节点节点就是一个独立的功能模块负责做一件具体的事。比如一个节点负责读取摄像头图像一个节点负责处理激光雷达数据一个节点负责路径规划一个节点负责控制电机转动每个节点各司其职通过 ROS2 的通信机制互相配合就像人体的各个器官协同工作一样。通俗理解节点就像一个公司里的员工每个人只干自己的活但大家通过聊天通信来协作完成一个大项目。为什么要用节点模块化每个功能独立出问题好找原因可复用同一个节点可以在不同项目里用分布式节点可以跑在不同电脑上甚至不同设备上动手写第一个节点我们用 Python 写一个最简单的节点它什么都不做就是活着打印一条日志importrclpyfromrclpy.nodeimportNodeclassMyFirstNode(Node):我的第一个 ROS2 节点def__init__(self):# 给节点起个名字my_first_nodesuper().__init__(my_first_node)# 调用父类的初始化self.get_logger().info(我的第一个 ROS2 节点启动了)defmain(argsNone):# 初始化 ROS2rclpy.init(argsargs)# 创建节点实例nodeMyFirstNode()# 让节点保持运行进入自旋循环相当于while(1)不断处理回调rclpy.spin(node)# 关闭时清理rclpy.shutdown()if__name____main__:main()运行效果[INFO] [my_first_node]: 我的第一个 ROS2 节点启动了节点跑起来了但一个节点没啥意思。节点的价值在于和其他节点通信。这就引出了第二个核心概念话题。 二、话题Topic节点间的广播电台什么是话题话题是 ROS2 里最常用的通信方式采用发布-订阅模式Publish-Subscribe。发布者Publisher往话题里发消息的节点订阅者Subscriber从话题里收消息的节点话题Topic消息的频道用名字标识比如/camera/image一个话题可以有多个发布者也可以有多个订阅者。发布者不需要知道谁在订阅订阅者也不需要知道谁在发布——完全解耦。通俗理解话题就像广播电台。电台发布者只管往外播节目收音机订阅者调到对应频率就能听。电台不知道有多少人在听听众也不需要认识电台主播。话题的特点特点说明单向通信发布者只管发订阅者只管收一对多一个话题可以被 N 个订阅者同时接收持续流适合高频、连续的数据传感器、控制指令异步发了就走不等待回应实战写一个温度传感器 显示器我们做两个节点temperature_publisher模拟温度传感器每秒发布一次温度数据temperature_subscriber订阅温度数据显示在屏幕上① 发布者节点importrclpyfromrclpy.nodeimportNodefromstd_msgs.msgimportFloat32importrandomclassTemperaturePublisher(Node):温度传感器发布者节点def__init__(self):super().__init__(temperature_publisher)# 创建发布者消息类型 Float32话题名 /temperature队列大小 10self.publisher_self.create_publisher(Float32,/temperature,10)# 创建定时器每 1 秒发布一次self.timerself.create_timer(1.0,self.publish_temperature)self.get_logger().info(️ 温度传感器节点已启动)defpublish_temperature(self):发布温度数据# 模拟温度25°C 左右浮动temp25.0random.uniform(-2.0,2.0)msgFloat32()msg.dataround(temp,2)self.publisher_.publish(msg)self.get_logger().info(f发布温度:{msg.data}°C)defmain(argsNone):rclpy.init(argsargs)nodeTemperaturePublisher()rclpy.spin(node)rclpy.shutdown()if__name____main__:main()② 订阅者节点importrclpyfromrclpy.nodeimportNodefromstd_msgs.msgimportFloat32classTemperatureSubscriber(Node):温度显示订阅者节点def__init__(self):super().__init__(temperature_subscriber)# 创建订阅者话题 /temperature回调函数 temperature_callbackself.subscriptionself.create_subscription(Float32,/temperature,self.temperature_callback,10# 队列大小)self.get_logger().info( 温度显示节点已启动等待数据...)deftemperature_callback(self,msg):收到温度数据后的回调函数tempmsg.data# 根据温度给不同提示iftemp28:status 有点热eliftemp22:status❄️ 有点凉else:status✅ 舒适self.get_logger().info(f收到温度:{temp}°C{status})defmain(argsNone):rclpy.init(argsargs)nodeTemperatureSubscriber()rclpy.spin(node)rclpy.shutdown()if__name____main__:main()运行效果# 终端1运行发布者 $ ros2 run my_package temperature_publisher [INFO] [temperature_publisher]: ️ 温度传感器节点已启动 [INFO] [temperature_publisher]: 发布温度: 24.35 °C [INFO] [temperature_publisher]: 发布温度: 26.12 °C # 终端2运行订阅者 $ ros2 run my_package temperature_subscriber [INFO] [temperature_subscriber]: 温度显示节点已启动等待数据... [INFO] [temperature_subscriber]: 收到温度: 24.35 °C ✅ 舒适 [INFO] [temperature_subscriber]: 收到温度: 26.12 °C ✅ 舒适你看两个节点完全独立只是通过/temperature这个话题就完成了通信非常优雅。 三、服务Service节点间的问答对话什么是服务服务是 ROS2 的第二种通信方式采用请求-响应模式Request-Response。服务端Server提供服务的节点收到请求后处理并返回响应客户端Client发起请求的节点等待服务端回应服务Service用名字标识比如/add_two_ints和话题不同服务是双向的客户端发一个请求服务端必须返回一个响应。通俗理解服务就像打电话问问题。你客户端打给客服服务端问一个问题请求客服查一下然后告诉你答案响应。是一对一的、有问有答的交互。服务 vs 话题怎么选对比项话题Topic服务Service通信模式发布-订阅单向请求-响应双向数据流向单向一来一回适用场景传感器数据、状态广播触发动作、查询状态实时性高持续流中一问一答可靠性可能丢数据保证有回应类比广播电台打电话 / REST API简单判断你需要持续不断发数据→ 用话题你需要调用一个功能并拿到结果→ 用服务实战写一个加法计算器服务我们做两个节点add_server提供加法服务接收两个数返回它们的和add_client调用加法服务传入两个数打印结果① 服务端节点importrclpyfromrclpy.nodeimportNodefromexample_interfaces.srvimportAddTwoIntsclassAddTwoIntsServer(Node):加法服务端节点def__init__(self):super().__init__(add_two_ints_server)# 创建服务服务类型 AddTwoInts服务名 /add_two_intsself.srvself.create_service(AddTwoInts,/add_two_ints,self.add_two_ints_callback)self.get_logger().info( 加法服务已就绪等待请求...)defadd_two_ints_callback(self,request,response):处理服务请求的回调函数# 从请求中取出两个数arequest.a brequest.b# 计算结果response.sumab self.get_logger().info(f收到请求:{a}{b}{response.sum})# 返回响应returnresponsedefmain(argsNone):rclpy.init(argsargs)nodeAddTwoIntsServer()rclpy.spin(node)rclpy.shutdown()if__name____main__:main()② 客户端节点importrclpyfromrclpy.nodeimportNodefromexample_interfaces.srvimportAddTwoIntsimportsysclassAddTwoIntsClient(Node):加法客户端节点def__init__(self):super().__init__(add_two_ints_client)# 创建客户端服务类型 AddTwoInts服务名 /add_two_intsself.cliself.create_client(AddTwoInts,/add_two_ints)# 等待服务上线最多等 5 秒whilenotself.cli.wait_for_service(timeout_sec1.0):self.get_logger().info(⏳ 等待服务上线...)self.get_logger().info(✅ 服务已连接)defsend_request(self,a,b):发送加法请求reqAddTwoInts.Request()req.aa req.bb# 异步发送请求self.futureself.cli.call_async(req)# 等待响应rclpy.spin_until_future_complete(self,self.future)returnself.future.result()defmain(argsNone):rclpy.init(argsargs)clientAddTwoIntsClient()# 从命令行参数读取两个数默认 3 和 5aint(sys.argv[1])iflen(sys.argv)1else3bint(sys.argv[2])iflen(sys.argv)2else5responseclient.send_request(a,b)client.get_logger().info(f 请求:{a}{b})client.get_logger().info(f 结果:{response.sum})rclpy.shutdown()if__name____main__:main()运行效果# 终端1启动服务端 $ ros2 run my_package add_server [INFO] [add_two_ints_server]: 加法服务已就绪等待请求... # 终端2运行客户端 $ ros2 run my_package add_client 10 20 [INFO] [add_two_ints_client]: ✅ 服务已连接 [INFO] [add_two_ints_client]: 请求: 10 20 [INFO] [add_two_ints_client]: 结果: 30 # 服务端会打印 [INFO] [add_two_ints_server]: 收到请求: 10 20 30 四、三大概念一张表总结概念作用模式类比适用场景节点 Node功能模块-员工 / 器官每个独立功能话题 Topic数据流通信发布-订阅广播电台传感器、状态、高频数据服务 Service请求式通信请求-响应打电话 / API触发动作、查询、配置 下一步学什么掌握了节点、话题、服务ROS2 的骨架就搭起来了。接下来可以继续学动作Action适合耗时任务比如机械臂运动、导航是服务的增强版支持中途反馈和取消参数Parameter节点的配置项可以运行时动态修改TF 坐标变换机器人各部件的位置关系导航 Navigation2自主移动机器人的完整导航方案MoveIt2机械臂运动规划 完整代码本文所有代码都可以直接复制运行前提是你已经装好 ROS2推荐 Humble 或 Jazzy 版本。如果需要完整的 ROS2 学习路线图、更多实战项目源码可以关注我后续持续更新。点赞 收藏 关注学 ROS2 不迷路有问题欢迎评论区交流本文为原创内容转载请注明出处。