ROS2 -06-动作

文章目录

    • [1. 引言](#1. 引言)
    • [2. 动作与话题、服务的对比](#2. 动作与话题、服务的对比)
    • [3. 动作的应用场景](#3. 动作的应用场景)
    • [4. 动作接口定义(.action 文件)](#4. 动作接口定义(.action 文件))
    • [5. 动作核心概念与流程](#5. 动作核心概念与流程)
      • [5.1 动作服务器 (Action Server)](#5.1 动作服务器 (Action Server))
      • [5.2 动作客户端 (Action Client)](#5.2 动作客户端 (Action Client))
      • [5.3 通信流程](#5.3 通信流程)
    • [6. 动作服务器详解(Python & C++)](#6. 动作服务器详解(Python & C++))
      • [6.1 Python 动作服务器](#6.1 Python 动作服务器)
        • 关键函数/类说明
        • [完整代码示例(Fibonacci 动作服务器)](#完整代码示例(Fibonacci 动作服务器))
      • [6.2 C++ 动作服务器](#6.2 C++ 动作服务器)
        • 关键函数/类说明
        • [完整代码示例(Fibonacci 动作服务器)](#完整代码示例(Fibonacci 动作服务器))
    • [7. 动作客户端详解(Python & C++)](#7. 动作客户端详解(Python & C++))
      • [7.1 Python 动作客户端](#7.1 Python 动作客户端)
        • 关键函数/类说明
        • [完整代码示例(Fibonacci 动作客户端)](#完整代码示例(Fibonacci 动作客户端))
      • [7.2 C++ 动作客户端](#7.2 C++ 动作客户端)
        • 关键函数/类说明
        • [完整代码示例(Fibonacci 动作客户端)](#完整代码示例(Fibonacci 动作客户端))
    • [8. 功能测试方法](#8. 功能测试方法)
      • [8.1 命令行工具测试](#8.1 命令行工具测试)
      • [8.2 Python 脚本同时运行服务器和客户端](#8.2 Python 脚本同时运行服务器和客户端)
    • [9. 高级主题](#9. 高级主题)
      • [9.1 并发处理多个目标](#9.1 并发处理多个目标)
      • [9.2 取消的最佳实践](#9.2 取消的最佳实践)
      • [9.3 目标状态码](#9.3 目标状态码)
      • [9.4 超时处理](#9.4 超时处理)
      • [9.5 QoS 设置](#9.5 QoS 设置)
      • [9.6 在组件(Component)中使用动作](#9.6 在组件(Component)中使用动作)
    • [10. 最佳实践与常见问题](#10. 最佳实践与常见问题)
      • [10.1 服务器端](#10.1 服务器端)
      • [10.2 客户端](#10.2 客户端)
      • [10.3 常见陷阱](#10.3 常见陷阱)
    • [11. 总结](#11. 总结)

1. 引言

ROS2 动作(Action)是一种基于目标(Goal) 、**反馈(Feedback) 结果(Result)的异步通信机制,专门用于处理长时间运行的任务。它内部使用 话题(Topic)服务(Service)**的组合来实现,提供了目标、反馈、结果和取消等完整功能,是机器人导航、机械臂控制等复杂场景的核心通信方式。

本文将从零开始,全面覆盖 ROS2 动作编程的所有知识点,包括与其他通信机制的对比、应用场景、接口定义、服务器与客户端的详细实现(Python 和 C++,每行代码带注释)、测试方法、高级主题及最佳实践。

2. 动作与话题、服务的对比

特性 话题 (Topic) 服务 (Service) 动作 (Action)
通信模式 发布/订阅 (Publisher/Subscriber) 请求/响应 (Request/Response) 目标/结果/反馈 (Goal/Result/Feedback)
同步性 异步,无阻塞 同步或异步,客户端等待响应 异步,客户端可监控进度
反馈 有,可定期返回进度
取消 不适用 通常不支持(可自行实现) 内置取消机制
适用场景 持续数据流,如传感器数据 一次性计算或任务,如查询状态 长时间任务,需反馈和可取消,如导航
通信方向 单向流 双向一次 双向多次(目标、反馈、结果)
底层实现 基于 DDS 主题 基于 DDS 服务 基于话题+服务组合

详细说明

  • 话题:发布者持续发送数据,订阅者被动接收,无确认和反馈。
  • 服务:客户端发送请求,服务器处理后返回响应,适合短时操作,客户端通常阻塞等待。
  • 动作:客户端发送目标,服务器执行过程中发布反馈,最终返回结果,并可随时取消,适合耗时任务。

3. 动作的应用场景

  • 机器人导航:发送目标点,机器人规划路径并移动,期间反馈当前位置、状态,可随时取消。
  • 机械臂运动规划:请求机械臂移动到某个姿态,规划过程可能耗时,执行过程中反馈关节角度。
  • 文件下载/上传:请求下载一个大文件,反馈下载进度,可取消。
  • 长时间计算的算法:如 SLAM 建图、点云配准,需要中间结果或进度。
  • 交互式任务:需要用户确认或干预的任务,如"移动到A点,然后等待指令"。

4. 动作接口定义(.action 文件)

动作接口使用 .action 文件定义,包含三个部分,用 --- 分隔:

复制代码
# Goal(目标)
int32 order  # 例如:请求计算斐波那契数列的阶数

---
# Result(结果)
int32[] sequence  # 返回完整的斐波那契数列

---
# Feedback(反馈)
int32[] partial_sequence  # 当前计算的中间序列

编译后会在对应语言中生成消息类:

  • Python:Fibonacci.GoalFibonacci.ResultFibonacci.Feedback
  • C++:Fibonacci::GoalFibonacci::ResultFibonacci::Feedback

支持所有 ROS2 消息类型(基本类型、数组、嵌套消息)。

5. 动作核心概念与流程

5.1 动作服务器 (Action Server)

  • 职责:接收客户端的目标、执行任务、发布反馈、返回结果、处理取消请求。
  • 关键组件
    • 目标回调:决定是否接受新目标。
    • 执行回调:实际执行任务的函数。
    • 取消回调:处理取消请求。
    • 反馈发布:定期发布进度。
    • 结果返回:任务完成时返回结果,并标记状态。

5.2 动作客户端 (Action Client)

  • 职责:发送目标、接收反馈、获取最终结果、取消目标。
  • 关键组件
    • 等待服务器:确保服务器可用。
    • 发送目标:可附带反馈回调和结果回调。
    • 反馈回调:处理服务器发来的反馈。
    • 结果回调:处理最终结果。
    • 取消目标:主动取消正在执行的目标。

5.3 通信流程

  1. 客户端发送目标(Goal)到服务器。
  2. 服务器通过目标回调决定接受或拒绝。
  3. 若接受,服务器开始执行任务,并定期发布反馈(Feedback)。
  4. 客户端通过反馈回调接收进度。
  5. 任务完成时,服务器返回结果(Result),客户端通过结果回调接收。
  6. 客户端可随时发送取消请求,服务器应响应。

6. 动作服务器详解(Python & C++)

6.1 Python 动作服务器

关键函数/类说明
  • ActionServer 构造函数

    python 复制代码
    ActionServer(node, action_type, action_name, execute_callback,
                 goal_callback=None, cancel_callback=None,
                 handle_accepted_callback=None)
    • node: ROS2 节点实例。
    • action_type: 动作类型,如 Fibonacci
    • action_name: 动作名称,字符串。
    • execute_callback: 执行回调函数,接收 goal_handle,返回 Result 对象。
    • goal_callback: 可选,目标回调,接收 goal_request,返回 GoalResponse.ACCEPTREJECT
    • cancel_callback: 可选,取消回调,接收 goal_handle,返回 CancelResponse.ACCEPTREJECT
    • handle_accepted_callback: 可选,目标被接受后回调,接收 goal_handle,通常用于异步启动执行。
  • ServerGoalHandle 对象方法

    • is_cancel_requested: 属性,检查是否收到取消请求。
    • publish_feedback(feedback_msg): 发布反馈。
    • succeed(result=None): 标记目标成功,可选附带结果。
    • abort(result=None): 标记目标中止。
    • canceled(result=None): 标记目标取消。
    • get_result(): 获取结果(一般不直接调用)。
  • asyncio :用于异步操作,如 asyncio.sleep() 模拟耗时。

完整代码示例(Fibonacci 动作服务器)
python 复制代码
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer
from rclpy.action.server import ServerGoalHandle
from example_interfaces.action import Fibonacci
import asyncio

class FibonacciActionServer(Node):
    def __init__(self):
        super().__init__('fibonacci_action_server')
        # 创建动作服务器
        self._action_server = ActionServer(
            self,
            Fibonacci,
            'fibonacci',
            execute_callback=self.execute_callback,
            goal_callback=self.goal_callback,
            cancel_callback=self.cancel_callback,
            handle_accepted_callback=self.handle_accepted_callback
        )
        self.get_logger().info('Fibonacci action server started')

    def goal_callback(self, goal_request: Fibonacci.Goal):
        """目标回调:决定是否接受目标"""
        self.get_logger().info(f'Received goal request with order: {goal_request.order}')
        if goal_request.order < 0:
            self.get_logger().warn('Rejecting goal: order must be >= 0')
            return rclpy.action.GoalResponse.REJECT
        return rclpy.action.GoalResponse.ACCEPT

    def cancel_callback(self, goal_handle: ServerGoalHandle):
        """取消回调:决定是否接受取消请求"""
        self.get_logger().info('Received cancel request')
        return rclpy.action.CancelResponse.ACCEPT

    def handle_accepted_callback(self, goal_handle: ServerGoalHandle):
        """目标被接受后的回调:异步启动执行"""
        self.get_logger().info('Goal accepted, starting execution...')
        asyncio.create_task(self.execute_callback(goal_handle))

    async def execute_callback(self, goal_handle: ServerGoalHandle):
        """执行回调:实际执行任务"""
        self.get_logger().info('Executing goal...')
        goal_request = goal_handle.request
        order = goal_request.order

        feedback_msg = Fibonacci.Feedback()
        feedback_msg.partial_sequence = [0, 1]
        sequence = [0, 1]

        if order <= 1:
            result = Fibonacci.Result()
            result.sequence = sequence[:order+1]
            goal_handle.succeed()
            return result

        for i in range(1, order):
            if goal_handle.is_cancel_requested:
                self.get_logger().info('Goal canceled by client')
                result = Fibonacci.Result()
                result.sequence = sequence
                goal_handle.canceled(result)
                return result

            next_num = sequence[i] + sequence[i-1]
            sequence.append(next_num)
            feedback_msg.partial_sequence = sequence
            goal_handle.publish_feedback(feedback_msg)
            self.get_logger().info(f'Publishing feedback: {sequence}')
            await asyncio.sleep(1.0)

        result = Fibonacci.Result()
        result.sequence = sequence
        goal_handle.succeed()
        self.get_logger().info('Goal succeeded')
        return result

def main(args=None):
    rclpy.init(args=args)
    node = FibonacciActionServer()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

6.2 C++ 动作服务器

关键函数/类说明
  • rclcpp_action::create_server<ActionT>

    cpp 复制代码
    auto server = rclcpp_action::create_server<ActionT>(
        node, name,
        std::bind(&handle_goal, this, _1, _2),
        std::bind(&handle_cancel, this, _1),
        std::bind(&handle_accepted, this, _1));
    • node: 节点共享指针。
    • name: 动作名称。
    • 三个回调:handle_goalhandle_cancelhandle_accepted
  • GoalHandleFibonaccirclcpp_action::ServerGoalHandle<Fibonacci> 的别名。

  • goal_handle 方法

    • get_goal(): 获取目标消息(const Fibonacci::Goal&)。
    • publish_feedback(feedback): 发布反馈(std::shared_ptr<Fibonacci::Feedback>)。
    • is_canceling(): 检查是否收到取消请求。
    • succeed(result): 标记成功(std::shared_ptr<Fibonacci::Result>)。
    • canceled(result): 标记取消。
    • abort(result): 标记中止。
  • rclcpp::Rate:用于固定频率循环。

完整代码示例(Fibonacci 动作服务器)
cpp 复制代码
#include <memory>
#include <thread>
#include <chrono>
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <example_interfaces/action/fibonacci.hpp>

using Fibonacci = example_interfaces::action::Fibonacci;
using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle<Fibonacci>;
using namespace std::placeholders;

class FibonacciActionServer : public rclcpp::Node
{
public:
    FibonacciActionServer() : Node("fibonacci_action_server")
    {
        action_server_ = rclcpp_action::create_server<Fibonacci>(
            this,
            "fibonacci",
            std::bind(&FibonacciActionServer::handle_goal, this, _1, _2),
            std::bind(&FibonacciActionServer::handle_cancel, this, _1),
            std::bind(&FibonacciActionServer::handle_accepted, this, _1));
        RCLCPP_INFO(this->get_logger(), "Fibonacci action server started");
    }

private:
    rclcpp_action::Server<Fibonacci>::SharedPtr action_server_;

    rclcpp_action::GoalResponse handle_goal(
        const rclcpp_action::GoalUUID & uuid,
        std::shared_ptr<const Fibonacci::Goal> goal)
    {
        RCLCPP_INFO(this->get_logger(), "Received goal request with order %d", goal->order);
        if (goal->order < 0) {
            RCLCPP_WARN(this->get_logger(), "Rejecting goal: order must be >= 0");
            return rclcpp_action::GoalResponse::REJECT;
        }
        return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
    }

    rclcpp_action::CancelResponse handle_cancel(
        const std::shared_ptr<GoalHandleFibonacci> goal_handle)
    {
        RCLCPP_INFO(this->get_logger(), "Received cancel request");
        return rclcpp_action::CancelResponse::ACCEPT;
    }

    void handle_accepted(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
    {
        std::thread{std::bind(&FibonacciActionServer::execute, this, _1), goal_handle}.detach();
    }

    void execute(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
    {
        RCLCPP_INFO(this->get_logger(), "Executing goal...");
        const auto goal = goal_handle->get_goal();
        auto feedback = std::make_shared<Fibonacci::Feedback>();
        auto result = std::make_shared<Fibonacci::Result>();

        std::vector<int> sequence = {0, 1};
        feedback->partial_sequence = sequence;
        goal_handle->publish_feedback(feedback);

        rclcpp::Rate rate(1);
        for (int i = 1; i < goal->order; ++i) {
            if (goal_handle->is_canceling()) {
                result->sequence = sequence;
                goal_handle->canceled(result);
                RCLCPP_INFO(this->get_logger(), "Goal canceled");
                return;
            }

            int next = sequence[i] + sequence[i-1];
            sequence.push_back(next);
            feedback->partial_sequence = sequence;
            goal_handle->publish_feedback(feedback);
            RCLCPP_INFO(this->get_logger(), "Publishing feedback: [%s]",
                        vector_to_string(sequence).c_str());
            rate.sleep();
        }

        result->sequence = sequence;
        goal_handle->succeed(result);
        RCLCPP_INFO(this->get_logger(), "Goal succeeded");
    }

    std::string vector_to_string(const std::vector<int> &vec)
    {
        std::stringstream ss;
        for (size_t i = 0; i < vec.size(); ++i) {
            if (i != 0) ss << ", ";
            ss << vec[i];
        }
        return ss.str();
    }
};

int main(int argc, char ** argv)
{
    rclcpp::init(argc, argv);
    auto node = std::make_shared<FibonacciActionServer>();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

7. 动作客户端详解(Python & C++)

7.1 Python 动作客户端

关键函数/类说明
  • ActionClient 构造函数

    python 复制代码
    ActionClient(node, action_type, action_name)
    • node: ROS2 节点实例。
    • action_type: 动作类型。
    • action_name: 动作名称。
  • wait_for_server(timeout_sec):等待服务器可用,返回布尔值。

  • send_goal_async(goal_msg, feedback_callback=None)

    • 异步发送目标,返回 Future,当服务器接受/拒绝目标时完成。
    • goal_msg: 目标消息实例。
    • feedback_callback: 可选,反馈回调函数,接收 feedback_msg
  • ClientGoalHandle 对象方法

    • accepted: 属性,布尔值,表示目标是否被接受。
    • get_result_async(): 异步获取最终结果,返回 Future,结果可用时完成。
    • cancel_goal_async(): 异步取消目标。
  • 反馈回调

    python 复制代码
    def feedback_callback(feedback_msg):
        feedback = feedback_msg.feedback  # 获取实际的反馈消息
  • 结果回调

    通过 future.result() 得到 WrappedResult,包含 statusresult

完整代码示例(Fibonacci 动作客户端)
python 复制代码
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from rclpy.action import ActionClient
from example_interfaces.action import Fibonacci

class FibonacciActionClient(Node):
    def __init__(self):
        super().__init__('fibonacci_action_client')
        self._action_client = ActionClient(self, Fibonacci, 'fibonacci')
        self.get_logger().info('Fibonacci action client started')

    def send_goal(self, order: int):
        self.get_logger().info('Waiting for action server...')
        if not self._action_client.wait_for_server(timeout_sec=5.0):
            self.get_logger().error('Action server not available after waiting')
            return

        goal_msg = Fibonacci.Goal()
        goal_msg.order = order
        self.get_logger().info(f'Sending goal with order: {order}')

        send_goal_future = self._action_client.send_goal_async(
            goal_msg,
            feedback_callback=self.feedback_callback
        )
        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 by server')
            return
        self.get_logger().info('Goal accepted by server')
        self._get_result_future = goal_handle.get_result_async()
        self._get_result_future.add_done_callback(self.get_result_callback)

    def feedback_callback(self, feedback_msg):
        feedback = feedback_msg.feedback
        self.get_logger().info(f'Received feedback: {feedback.partial_sequence}')

    def get_result_callback(self, future):
        wrapped_result = future.result()
        result = wrapped_result.result
        status = wrapped_result.status
        if status == 4:
            self.get_logger().info(f'Goal succeeded with result: {result.sequence}')
        elif status == 5:
            self.get_logger().error('Goal aborted')
        elif status == 6:
            self.get_logger().warn('Goal canceled')
        else:
            self.get_logger().info(f'Goal finished with status: {status}')
        rclpy.shutdown()

def main(args=None):
    rclpy.init(args=args)
    node = FibonacciActionClient()
    node.send_goal(10)
    rclpy.spin(node)

if __name__ == '__main__':
    main()

7.2 C++ 动作客户端

关键函数/类说明
  • rclcpp_action::create_client<ActionT>(node, name):创建客户端。
  • wait_for_action_server(timeout):等待服务器可用,返回布尔值。
  • async_send_goal(goal_msg, options)
    • goal_msg: 目标消息(如 Fibonacci::Goal)。
    • options: SendGoalOptions 结构,可包含:
      • goal_response_callback: 目标响应回调。
      • feedback_callback: 反馈回调。
      • result_callback: 结果回调。
  • 回调签名
    • goal_response_callback: void (const GoalHandleFibonacci::SharedPtr &)
    • feedback_callback: void (GoalHandleFibonacci::SharedPtr, const std::shared_ptr<const Fibonacci::Feedback>)
    • result_callback: void (const GoalHandleFibonacci::WrappedResult &)
  • GoalHandleFibonacci::WrappedResult :包含 resultstatus 字段。
完整代码示例(Fibonacci 动作客户端)
cpp 复制代码
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <example_interfaces/action/fibonacci.hpp>

using Fibonacci = example_interfaces::action::Fibonacci;
using GoalHandleFibonacci = rclcpp_action::ClientGoalHandle<Fibonacci>;
using namespace std::placeholders;

class FibonacciActionClient : public rclcpp::Node
{
public:
    FibonacciActionClient() : Node("fibonacci_action_client")
    {
        client_ = rclcpp_action::create_client<Fibonacci>(this, "fibonacci");
        RCLCPP_INFO(this->get_logger(), "Fibonacci action client started");
    }

    void send_goal(int order)
    {
        if (!client_->wait_for_action_server(std::chrono::seconds(10))) {
            RCLCPP_ERROR(this->get_logger(), "Action server not available after waiting");
            return;
        }

        auto goal_msg = Fibonacci::Goal();
        goal_msg.order = order;
        RCLCPP_INFO(this->get_logger(), "Sending goal with order %d", order);

        auto send_goal_options = rclcpp_action::Client<Fibonacci>::SendGoalOptions();
        send_goal_options.goal_response_callback =
            std::bind(&FibonacciActionClient::goal_response_callback, this, _1);
        send_goal_options.feedback_callback =
            std::bind(&FibonacciActionClient::feedback_callback, this, _1, _2);
        send_goal_options.result_callback =
            std::bind(&FibonacciActionClient::result_callback, this, _1);

        client_->async_send_goal(goal_msg, send_goal_options);
    }

private:
    rclcpp_action::Client<Fibonacci>::SharedPtr client_;

    void goal_response_callback(const GoalHandleFibonacci::SharedPtr & goal_handle)
    {
        if (!goal_handle) {
            RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server");
        } else {
            RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result...");
        }
    }

    void feedback_callback(
        GoalHandleFibonacci::SharedPtr,
        const std::shared_ptr<const Fibonacci::Feedback> feedback)
    {
        std::stringstream ss;
        for (auto num : feedback->partial_sequence) {
            if (ss.tellp() != 0) ss << ", ";
            ss << num;
        }
        RCLCPP_INFO(this->get_logger(), "Received feedback: [%s]", ss.str().c_str());
    }

    void result_callback(const GoalHandleFibonacci::WrappedResult & result)
    {
        switch (result.code) {
            case rclcpp_action::ResultCode::SUCCEEDED:
                break;
            case rclcpp_action::ResultCode::ABORTED:
                RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
                return;
            case rclcpp_action::ResultCode::CANCELED:
                RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
                return;
            default:
                RCLCPP_ERROR(this->get_logger(), "Unknown result code");
                return;
        }

        std::stringstream ss;
        for (auto num : result.result->sequence) {
            if (ss.tellp() != 0) ss << ", ";
            ss << num;
        }
        RCLCPP_INFO(this->get_logger(), "Goal completed with result: [%s]", ss.str().c_str());
        rclcpp::shutdown();
    }
};

int main(int argc, char ** argv)
{
    rclcpp::init(argc, argv);
    auto client = std::make_shared<FibonacciActionClient>();
    client->send_goal(10);
    rclcpp::spin(client);
    rclcpp::shutdown();
    return 0;
}

8. 功能测试方法

8.1 命令行工具测试

bash 复制代码
# 启动服务器(任选语言)
python3 fibonacci_action_server.py

# 查看动作列表
ros2 action list

# 查看动作类型
ros2 action list -t

# 发送目标并查看反馈
ros2 action send_goal /fibonacci example_interfaces/action/Fibonacci "{order: 5}" --feedback

# 获取动作信息
ros2 action info /fibonacci

8.2 Python 脚本同时运行服务器和客户端

python 复制代码
#!/usr/bin/env python3
import rclpy
from rclpy.executors import MultiThreadedExecutor
from fibonacci_action_server import FibonacciActionServer
from fibonacci_action_client import FibonacciActionClient

def main(args=None):
    rclpy.init(args=args)
    server_node = FibonacciActionServer()
    client_node = FibonacciActionClient()
    executor = MultiThreadedExecutor()
    executor.add_node(server_node)
    executor.add_node(client_node)
    client_node.send_goal(10)
    try:
        executor.spin()
    finally:
        executor.shutdown()
        server_node.destroy_node()
        client_node.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

9. 高级主题

9.1 并发处理多个目标

  • Python :在 handle_accepted_callback 中将目标放入 asyncio.Queue,使用多个工作协程处理。
  • C++:使用线程池,每个目标分配一个线程执行,注意线程安全。

9.2 取消的最佳实践

  • 服务器定期检查取消标志(is_cancel_requested / is_canceling()),尽快停止工作。
  • 客户端可通过 goal_handle.cancel_goal_async() 发送取消请求。
  • 取消后服务器应调用 canceled(result) 并返回部分结果。

9.3 目标状态码

预定义状态码(rclpy.action.GoalStatusrclcpp_action::GoalStatus):

  • STATUS_UNKNOWN = 0
  • STATUS_ACCEPTED = 1
  • STATUS_EXECUTING = 2
  • STATUS_CANCELING = 3
  • STATUS_SUCCEEDED = 4
  • STATUS_ABORTED = 5
  • STATUS_CANCELED = 6

9.4 超时处理

  • 客户端等待服务器可设置超时:wait_for_server(timeout)
  • 执行超时:客户端可启动定时器,超时后取消目标。

9.5 QoS 设置

动作底层话题和服务可使用自定义 QoS。在 Python 中通过 ActionServer 的参数设置,C++ 中通过重载的 create_server / create_client 指定。

9.6 在组件(Component)中使用动作

动作服务器和客户端均可用于节点组件,只需在组件的构造函数中创建。注意多线程环境下的安全性。

10. 最佳实践与常见问题

10.1 服务器端

  • 目标回调:快速返回,避免阻塞。
  • 执行回调:设计为可中断的循环,定期检查取消标志。
  • 反馈频率:不宜过高,避免网络拥塞。
  • 并发:如需并发,使用线程池或协程,并注意数据竞争。
  • 资源清理:取消或失败时清理资源。

10.2 客户端

  • 等待服务器:发送目标前确保服务器可用。
  • 处理拒绝:目标可能被拒绝,需有备选逻辑。
  • 取消处理:在长时间等待时提供取消选项。
  • 超时:自行实现超时机制。

10.3 常见陷阱

  • 服务器未检查取消标志:导致取消无法及时响应。
  • 反馈发布过快:可能引发网络问题。
  • 目标回调中执行耗时操作:阻塞服务器处理其他目标。
  • C++ 线程未分离handle_accepted 中启动线程后需 detach 或管理生命周期。
  • Python 协程未正确处理 :使用 asyncio.create_task 而非 await,避免阻塞回调。
  • 多目标并发时的资源竞争:使用互斥锁保护共享数据。

11. 总结

ROS2 动作是一种功能强大的通信机制,专门用于长时间运行的任务,提供了目标、反馈、结果和取消等完整功能。本文全面介绍了动作与话题、服务的区别、应用场景、接口定义、服务器与客户端的详细实现(Python 和 C++,每行代码带注释)、测试方法、高级主题及最佳实践。掌握这些知识点,开发者可以轻松实现复杂的机器人行为,并构建高效、可靠的 ROS2 应用。

参考文献:ROS2 官方文档(https://docs.ros.org/)

相关推荐
无限进步_2 小时前
【C++】字符串中的字母反转算法详解
开发语言·c++·ide·git·算法·github·visual studio
qyzm2 小时前
Codeforces Round 927 (Div. 3)
数据结构·python·算法
2401_857865232 小时前
用Python破解简单的替换密码
jvm·数据库·python
重庆兔巴哥2 小时前
如何在Windows上配置Java环境变量?
java·开发语言·windows
2401_891482172 小时前
C++中的状态模式实战
开发语言·c++·算法
小江的记录本2 小时前
【PageHelper】 【Spring Boot + MyBatis + PageHelper】 完整项目示例+PageHelper核心原理深度解析
java·前端·spring boot·后端·sql·spring·mybatis
LSL666_2 小时前
Redis值数据类型——String
数据库·redis·缓存·数据类型
weixin_704266052 小时前
Spring AOP事务控制实战指南
java·后端·spring
Frostnova丶2 小时前
LeetCode 1727.重新排列后的最大子矩阵
算法·leetcode