ROS2-通信机制详解
ROS2 通信机制详解
ROS2 提供了多种通信机制,用于节点之间的数据交换。这些机制就像不同的通信工具,适用于不同的场景。
1. 主题(Topic)- 发布/订阅模式
主题是 ROS2 中最常用的通信机制,采用异步的发布/订阅模式。发布者(Publisher)将数据发布到特定主题,订阅者(Subscriber)从该主题接收数据。
特点:
- 异步通信:发布者和订阅者不需要同时运行
- 一对多/多对一/多对多:一个主题可以有多个发布者和订阅者
- 单向数据流:数据从发布者流向订阅者
适用场景:
- 传感器数据传输(如摄像头图像、激光雷达数据)
- 状态信息广播
- 连续数据流传输
数据类型定义
消息定义文件使用 .msg 扩展名,用于定义 Topic 通信中使用的数据结构。
基本语法:
# 这是注释
类型 字段名
类型[] 字段名 # 数组
类型[固定大小] 字段名 # 固定大小数组
常用数据类型:
bool: 布尔值int8,uint8,int16,uint16,int32,uint32,int64,uint64: 整数类型float32,float64: 浮点数类型string: 字符串类型byte: 字节数组builtin_interfaces/Time: 时间戳builtin_interfaces/Duration: 时间间隔
示例:
# 自定义消息示例
std_msgs/Header header
string name
int32 id
float64[3] position
geometry_msgs/Point[] points
常用标准消息类型
std_msgs/Boolstd_msgs/Int32std_msgs/Float64std_msgs/Stringstd_msgs/Headergeometry_msgs/Pointgeometry_msgs/Vector3geometry_msgs/Posegeometry_msgs/Twistsensor_msgs/Imagesensor_msgs/PointCloud2sensor_msgs/LaserScan
Python 代码示例:
发布者(Publisher)示例:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class Talker(Node):
def __init__(self):
super().__init__('talker')
# 创建发布者,发布String类型消息到chatter主题,队列大小为10
self.publisher_ = self.create_publisher(String, 'chatter', 10)
# 创建定时器,每1秒调用一次timer_callback函数
timer_period = 1.0
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
def timer_callback(self):
# 创建消息对象
msg = String()
# 设置消息内容
msg.data = f'Hello World: {self.i}'
# 发布消息
self.publisher_.publish(msg)
# 打印日志
self.get_logger().info(f'Publishing: "{msg.data}"')
self.i += 1
def main(args=None):
rclpy.init(args=args)
talker = Talker()
rclpy.spin(talker)
talker.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
订阅者(Subscriber)示例:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class Listener(Node):
def __init__(self):
super().__init__('listener')
# 创建订阅者,订阅chatter主题的String类型消息
# 当有新消息时,会调用listener_callback函数处理
self.subscription = self.create_subscription(
String,
'chatter',
self.listener_callback,
10)
# 防止未使用变量警告
self.subscription
def listener_callback(self, msg):
# 处理接收到的消息
self.get_logger().info(f'I heard: "{msg.data}"')
def main(args=None):
rclpy.init(args=args)
listener = Listener()
rclpy.spin(listener)
listener.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
2. 服务(Service)- 请求/响应模式
服务是一种同步的通信机制,采用请求/响应模式。客户端(Client)向服务端(Service)发送请求,服务端处理请求并返回响应。
特点:
- 同步通信:客户端会等待服务端的响应
- 双向通信:包含请求和响应两个部分
- 一对一:一个服务只有一个服务端,但可以有多个客户端
适用场景:
- 执行需要确认的命令(如启动/停止机器人)
- 查询状态信息
- 执行计算密集型任务并返回结果
数据类型定义
服务定义文件使用 .srv 扩展名,包含请求和响应两部分,用 --- 分隔。
基本语法:
# 请求部分
类型 字段名
---
# 响应部分
类型 字段名
示例:
# AddTwoInts.srv
int64 a
int64 b
---
int64 sum
Python 代码示例:
服务端(Service Server)示例:
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
class AddTwoIntsServer(Node):
def __init__(self):
super().__init__('add_two_ints_server')
# 创建服务端,服务类型为AddTwoInts,服务名为add_two_ints
# 当有请求到达时,会调用add_two_ints_callback函数处理
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
self.get_logger().info('AddTwoInts server has started')
def add_two_ints_callback(self, request, response):
# 处理请求并设置响应
response.sum = request.a + request.b
self.get_logger().info(f'Incoming request: {request.a} + {request.b} = {response.sum}')
# 必须返回响应对象
return response
def main(args=None):
rclpy.init(args=args)
node = AddTwoIntsServer()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
客户端(Service Client)示例:
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
class AddTwoIntsClient(Node):
def __init__(self):
super().__init__('add_two_ints_client')
# 创建客户端,服务类型为AddTwoInts,服务名为add_two_ints
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
# 等待服务端启动
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('Service not available, waiting again...')
# 创建请求对象
self.req = AddTwoInts.Request()
def send_request(self, a, b):
# 设置请求参数
self.req.a = a
self.req.b = b
# 异步调用服务
future = self.cli.call_async(self.req)
# 等待服务响应
rclpy.spin_until_future_complete(self, future)
# 返回响应结果
return future.result()
def main(args=None):
rclpy.init(args=args)
client = AddTwoIntsClient()
response = client.send_request(2, 3)
client.get_logger().info(f'Result of add_two_ints: {response.sum}')
client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
3. 动作(Action)- 目标/结果/反馈模式
动作是一种异步的通信机制,用于长时间运行的任务。它提供目标、结果和反馈三个部分,允许客户端发送目标,服务端执行任务并提供反馈。
特点:
- 异步通信:客户端不需要等待任务完成
- 三部分通信:包括目标、结果和反馈
- 可取消:客户端可以取消正在执行的任务
- 状态监控:客户端可以获取任务的实时状态
适用场景:
- 长时间运行的任务(如导航到目标点)
- 需要进度反馈的任务
- 可取消的任务
数据类型定义
动作定义文件使用 .action 扩展名,包含目标、结果和反馈三部分,用 --- 分隔。
基本语法:
# 目标部分
类型 字段名
---
# 结果部分
类型 字段名
---
# 反馈部分
类型 字段名
示例:
# Fibonacci.action
int32 order
---
int32[] sequence
---
int32[] sequence
Python 代码示例:
动作服务端(Action Server)示例:
import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer
from rclpy.action.server import ServerGoalHandle
from example_interfaces.action import Fibonacci
class FibonacciActionServer(Node):
def __init__(self):
super().__init__('fibonacci_action_server')
# 创建动作服务端
# 动作类型为Fibonacci,动作为fibonacci
# execute_callback为执行回调函数
self._action_server = ActionServer(
self,
Fibonacci,
'fibonacci',
self.execute_callback)
def execute_callback(self, goal_handle: ServerGoalHandle):
self.get_logger().info('Executing goal...')
# 创建反馈消息
feedback_msg = Fibonacci.Feedback()
feedback_msg.sequence = [0, 1]
# 获取目标值
order = goal_handle.request.order
# 执行任务并发送反馈
for i in range(1, order):
# 检查是否收到取消请求
if goal_handle.is_cancel_requested:
goal_handle.canceled()
self.get_logger().info('Goal canceled')
return Fibonacci.Result()
# 计算斐波那契数列
feedback_msg.sequence.append(
feedback_msg.sequence[i] + feedback_msg.sequence[i-1])
# 打印日志
self.get_logger().info(f'Feedback: {feedback_msg.sequence}')
# 发送反馈
goal_handle.publish_feedback(feedback_msg)
# 模拟耗时操作
# 注意:在实际代码中,应该使用其他方式实现延迟
# 任务完成
goal_handle.succeed()
# 创建结果消息
result = Fibonacci.Result()
result.sequence = feedback_msg.sequence
return result
def main(args=None):
rclpy.init(args=args)
fibonacci_action_server = FibonacciActionServer()
rclpy.spin(fibonacci_action_server)
fibonacci_action_server.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
动作客户端(Action Client)示例:
import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node
from example_interfaces.action import Fibonacci
class FibonacciActionClient(Node):
def __init__(self):
super().__init__('fibonacci_action_client')
self._action_client = ActionClient(self, Fibonacci, 'fibonacci')
def send_goal(self, order):
goal_msg = Fibonacci.Goal()
goal_msg.order = order
self._action_client.wait_for_server()
self._send_goal_future = self._action_client.send_goal_async(
goal_msg,
feedback_callback=self.feedback_callback)
self._send_goal_future.add_done_callback(self.goal_response_callback)
def goal_response_callback(self, future):
goal_handle = future.result()
if not goal_handle.accepted:
self.get_logger().info('Goal rejected :(')
return
self.get_logger().info('Goal accepted :)')
self._get_result_future = goal_handle.get_result_async()
self._get_result_future.add_done_callback(self.get_result_callback)
def get_result_callback(self, future):
result = future.result().result
self.get_logger().info(f'Result: {result.sequence}')
rclpy.shutdown()
def feedback_callback(self, feedback_msg):
feedback = feedback_msg.feedback
self.get_logger().info(f'Received feedback: {feedback.sequence}')
def main(args=None):
rclpy.init(args=args)
action_client = FibonacciActionClient()
action_client.send_goal(10)
rclpy.spin(action_client)
if __name__ == '__main__':
main()
6. 最佳实践
在使用ROS2通信机制时,遵循以下最佳实践可以提高代码质量和系统可靠性:
-
合理选择通信机制:
- 根据数据特性和使用场景选择合适的通信机制
- 避免滥用某种机制
-
正确处理资源:
- 在节点销毁时清理创建的发布者、订阅者、服务端、客户端等
- 避免资源泄露
-
异常处理:
- 添加适当的错误处理机制
- 记录有用的日志信息便于调试
-
性能优化:
- 合理设置消息队列大小
- 避免在回调函数中执行耗时操作
- 必要时使用多线程执行器
-
代码组织:
- 保持代码结构清晰
- 合理分离业务逻辑和ROS2接口代码
- 添加必要的注释说明
通过合理使用这些通信机制,你可以构建出功能强大且高效的ROS2应用程序。
4. 参数(Parameter)
参数是节点的配置值,可以在运行时动态修改。
Python 代码示例:
import rclpy
from rclpy.node import Node
from rclpy.parameter import Parameter
from rcl_interfaces.msg import SetParametersResult
class ParamNode(Node):
def __init__(self):
super().__init__('param_node')
# 声明参数
self.declare_parameter('my_parameter', 'world')
# 获取参数
my_param = self.get_parameter('my_parameter').get_parameter_value().string_value
self.get_logger().info(f'Hello {my_param}!')
# 设置参数回调
self.add_on_set_parameters_callback(self.parameters_callback)
def parameters_callback(self, params):
for param in params:
if param.name == 'my_parameter' and param.type_ == Parameter.Type.STRING:
self.get_logger().info(f'Parameter changed to: {param.value}')
return SetParametersResult(successful=True)
return SetParametersResult(successful=False)
def main(args=None):
rclpy.init(args=args)
param_node = ParamNode()
rclpy.spin(param_node)
param_node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
5. 通信机制选择指南
选择合适的通信机制对于构建高效、可靠的ROS2系统至关重要。以下是一个简单的选择指南:
| 通信机制 | 数据流向 | 同步/异步 | 实时性 | 适用场景 |
|---|---|---|---|---|
| Topic | 单向 | 异步 | 高 | 连续数据流、状态广播 |
| Service | 双向 | 同步 | 中 | 查询、命令执行 |
| Action | 双向 | 异步 | 低 | 长时间运行的任务 |
选择建议:
-
使用Topic 当你需要:
- 广播信息给多个接收者
- 处理连续的数据流(如传感器数据)
- 不需要确认信息是否被接收
-
使用Service 当你需要:
- 执行一个有明确输入和输出的操作
- 等待操作完成并获取结果
- 执行相对快速的操作
-
使用Action 当你需要:
- 执行长时间运行的任务
- 获取任务执行进度反馈
- 能够取消正在执行的任务
- 处理复杂的、有状态的操作
选择合适的通信机制对于构建高效、可靠的ROS2系统至关重要。在实际应用中,通常会结合使用多种通信机制来满足不同的需求。

浙公网安备 33010602011771号