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/Bool
  • std_msgs/Int32
  • std_msgs/Float64
  • std_msgs/String
  • std_msgs/Header
  • geometry_msgs/Point
  • geometry_msgs/Vector3
  • geometry_msgs/Pose
  • geometry_msgs/Twist
  • sensor_msgs/Image
  • sensor_msgs/PointCloud2
  • sensor_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通信机制时,遵循以下最佳实践可以提高代码质量和系统可靠性:

  1. 合理选择通信机制

    • 根据数据特性和使用场景选择合适的通信机制
    • 避免滥用某种机制
  2. 正确处理资源

    • 在节点销毁时清理创建的发布者、订阅者、服务端、客户端等
    • 避免资源泄露
  3. 异常处理

    • 添加适当的错误处理机制
    • 记录有用的日志信息便于调试
  4. 性能优化

    • 合理设置消息队列大小
    • 避免在回调函数中执行耗时操作
    • 必要时使用多线程执行器
  5. 代码组织

    • 保持代码结构清晰
    • 合理分离业务逻辑和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 双向 异步 长时间运行的任务

选择建议:

  1. 使用Topic 当你需要:

    • 广播信息给多个接收者
    • 处理连续的数据流(如传感器数据)
    • 不需要确认信息是否被接收
  2. 使用Service 当你需要:

    • 执行一个有明确输入和输出的操作
    • 等待操作完成并获取结果
    • 执行相对快速的操作
  3. 使用Action 当你需要:

    • 执行长时间运行的任务
    • 获取任务执行进度反馈
    • 能够取消正在执行的任务
    • 处理复杂的、有状态的操作

选择合适的通信机制对于构建高效、可靠的ROS2系统至关重要。在实际应用中,通常会结合使用多种通信机制来满足不同的需求。

posted @ 2025-08-28 19:03  aaooli  阅读(217)  评论(0)    收藏  举报