第 6 章:Action 动作通信
本章目标理解 Action 的设计动机和架构掌握自定义 Action 接口编写完整的 Action Server 和 Client并通过导航到目标点的实战案例建立直观认识。6.1 为什么需要 Action6.1.1 Topic 和 Service 的局限性回顾前两章的内容通信机制优势局限性Topic持续流、松耦合、多对多无反馈、无结果确认、不适合离散任务Service请求-响应、有结果阻塞风险、无中间进度、无法取消考虑一个实际场景机器人导航到目标点如果用TopicClient 发布 Goal ──→ Server 接收 ↑ ↓ └──── ??? ───────────┘ ← 不知道进度、不知道是否到达、无法中途取消如果用ServiceClient ──Request(Goal)──→ Server │ ↓ │ [阻塞等待...] [长时间处理中...] │ [可能几秒到几分钟] [无法反馈进度] │ ↓ │ ←─Response(Result)── ← 只有最终结果中间一无所知Service 的问题导航可能需要几秒到几分钟同步调用会长时间阻塞用户想知道走到哪了还剩多远中途遇到障碍物需要重新规划用户想取消当前任务Service 无法提供这些能力6.1.2 Action 的设计目标Action 是为了解决长时间运行任务而设计的通信机制它结合了Service 的请求-响应模式发送 Goal接收 ResultTopic 的持续反馈机制实时报告进度Action 的核心能力表格能力说明发送目标GoalClient 向 Server 发送任务目标实时反馈FeedbackServer 持续报告执行进度获取结果ResultServer 返回最终执行结果取消任务CancelClient 可随时请求取消正在执行的任务目标抢占Preempt新 Goal 可覆盖旧 Goal6.1.3 Action vs Service 时序对比Service 同步调用时序Action 时序6.2 Action 的三部分组成Goal / Feedback / ResultAction 接口定义文件使用.action扩展名包含三个部分用---分隔plain# Goal: 客户端发送的目标 --- # Result: 服务器返回的最终结果 --- # Feedback: 执行过程中的实时反馈6.2.1 三部分详解部分方向次数用途GoalClient → Server一次定义任务目标Server 可选择接受或拒绝FeedbackServer → Client多次持续报告进度、中间状态ResultServer → Client一次返回最终执行结果6.2.2 实际案例导航到目标点创建~/ros2_ws/src/my_action_pkg/action/NavigateToPose.action# Goal geometry_msgs/PoseStamped target_pose float32 tolerance # 到达容忍距离米 bool avoid_obstacles # 是否启用避障 --- # Result bool success string message # 状态描述 float32 total_distance # 实际行驶距离 duration total_time # 实际耗时 --- # Feedback geometry_msgs/Pose current_pose float32 distance_remaining # 剩余距离 float32 estimated_time # 预计剩余时间 string current_state # 当前状态规划路径/行驶中/避障中6.2.3 另一个案例机械臂抓取plain# Goal string object_id # 目标物体 ID geometry_msgs/Pose grasp_pose # 抓取位姿 float32 force_limit # 最大夹持力 --- # Result bool success string message float32 actual_force # 实际夹持力 --- # Feedback string phase # 接近/抓取/提升/验证 float32 progress # 0.0 ~ 1.0 geometry_msgs/Pose current_pose6.3 自定义 Action 接口.action 文件6.3.1 创建 Action 功能包bashcd ~/ros2_ws/src ros2 pkg create --build-type ament_cmake --license Apache-2.0 my_action_pkg cd my_action_pkg mkdir action6.3.2 编写 .action 文件创建~/ros2_ws/src/my_action_pkg/action/Fibonacci.actionROS2 官方教学示例简单易懂plain# Goal: 计算斐波那契数列的前 n 个数 int32 order --- # Result: 完整的数列 int32[] sequence --- # Feedback: 当前已计算的部分数列 int32[] partial_sequence创建~/ros2_ws/src/my_action_pkg/action/NavigateToPose.action实战案例# Goal geometry_msgs/PoseStamped target_pose float32 tolerance --- # Result bool success string message float32 distance_traveled --- # Feedback geometry_msgs/Pose current_pose float32 distance_remaining string status6.3.3 配置 package.xml?xml version1.0? package format3 namemy_action_pkg/name version0.0.1/version descriptionAction definitions for robot tasks/description maintainer emailuserexample.comUser/maintainer licenseApache-2.0/license buildtool_dependament_cmake/buildtool_depend build_dependrosidl_default_generators/build_depend exec_dependrosidl_default_runtime/exec_depend member_of_grouprosidl_interface_packages/member_of_group dependgeometry_msgs/depend test_dependament_lint_auto/test_depend test_dependament_lint_common/test_depend export build_typeament_cmake/build_type /export /package6.3.4 配置 CMakeLists.txtcmake_minimum_required(VERSION 3.8) project(my_action_pkg) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES Clang) add_compile_options(-Wall -Wextra -Wpedantic) endif() find_package(ament_cmake REQUIRED) find_package(rosidl_default_generators REQUIRED) find_package(geometry_msgs REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} action/Fibonacci.action action/NavigateToPose.action DEPENDENCIES geometry_msgs ) ament_export_dependencies(rosidl_default_runtime) ament_package()6.3.5 编译与验证cd ~/ros2_ws colcon build --packages-select my_action_pkg source install/setup.bash # 查看生成的 Action 接口 ros2 interface show my_action_pkg/action/Fibonacci ros2 interface show my_action_pkg/action/NavigateToPose生成原理rosidl_generate_interfaces会在编译时自动生成 C 类Fibonacci::Goal、Fibonacci::Feedback、Fibonacci::Result和 Python 模块。这些生成的代码包含序列化逻辑使 Action 可以通过 DDS 传输。6.4 编写 Action Server 和 Client6.4.1 Action ServerCServer 负责接收 Goal、执行动作、发送 Feedback、返回 Result。创建~/ros2_ws/src/my_action_pkg/src/fibonacci_action_server.cpp#include functional #include memory #include thread #include rclcpp/rclcpp.hpp #include rclcpp_action/rclcpp_action.hpp #include my_action_pkg/action/fibonacci.hpp class FibonacciActionServer : public rclcpp::Node { public: using Fibonacci my_action_pkg::action::Fibonacci; using GoalHandleFibonacci rclcpp_action::ServerGoalHandleFibonacci; explicit FibonacciActionServer(const rclcpp::NodeOptions options rclcpp::NodeOptions()) : Node(fibonacci_action_server, options) { using namespace std::placeholders; // 创建 Action Server this-action_server_ rclcpp_action::create_serverFibonacci( this, fibonacci, // Action 名称 std::bind(FibonacciActionServer::handle_goal, this, _1, _2), // 处理 Goal std::bind(FibonacciActionServer::handle_cancel, this, _1), // 处理 Cancel std::bind(FibonacciActionServer::handle_accepted, this, _1)); // Goal 被接受后 } private: rclcpp_action::ServerFibonacci::SharedPtr action_server_; // 处理收到的 Goal决定是否接受 rclcpp_action::GoalResponse handle_goal( const rclcpp_action::GoalUUID uuid, std::shared_ptrconst Fibonacci::Goal goal) { RCLCPP_INFO(this-get_logger(), Received goal request with order %d, goal-order); // 拒绝不合理的 Goal if (goal-order 0) { RCLCPP_WARN(this-get_logger(), Rejecting goal: order must be 0); return rclcpp_action::GoalResponse::REJECT; } RCLCPP_INFO(this-get_logger(), Accepting goal); return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; } // 处理取消请求 rclcpp_action::CancelResponse handle_cancel( const std::shared_ptrGoalHandleFibonacci goal_handle) { RCLCPP_INFO(this-get_logger(), Received request to cancel goal); (void)goal_handle; return rclcpp_action::CancelResponse::ACCEPT; } // Goal 被接受后启动执行线程 void handle_accepted(const std::shared_ptrGoalHandleFibonacci goal_handle) { using namespace std::placeholders; // 在新线程中执行避免阻塞 Executor std::thread{std::bind(FibonacciActionServer::execute, this, _1), goal_handle}.detach(); } // 实际执行逻辑 void execute(const std::shared_ptrGoalHandleFibonacci goal_handle) { RCLCPP_INFO(this-get_logger(), Executing goal...); const auto goal goal_handle-get_goal(); auto feedback std::make_sharedFibonacci::Feedback(); auto result std::make_sharedFibonacci::Result(); // 初始化斐波那契数列 auto sequence feedback-partial_sequence; sequence.push_back(0); sequence.push_back(1); // 执行循环 rclcpp::Rate loop_rate(1); // 1Hz for (int i 1; i goal-order rclcpp::ok(); i) { // 检查是否收到取消请求 if (goal_handle-is_canceling()) { result-sequence sequence; goal_handle-canceled(result); RCLCPP_INFO(this-get_logger(), Goal canceled); return; } // 计算下一个数 sequence.push_back(sequence[i] sequence[i - 1]); // 发布 Feedback goal_handle-publish_feedback(feedback); RCLCPP_INFO(this-get_logger(), Publish feedback: [%s], sequence_to_string(sequence).c_str()); loop_rate.sleep(); } // 检查 Goal 是否仍然有效未被抢占 if (rclcpp::ok()) { result-sequence sequence; goal_handle-succeed(result); RCLCPP_INFO(this-get_logger(), Goal succeeded); } } std::string sequence_to_string(const std::vectorint32_t seq) { std::string s; for (auto num : seq) { s std::to_string(num) ; } return s; } }; int main(int argc, char ** argv) { rclcpp::init(argc, argv); auto node std::make_sharedFibonacciActionServer(); rclcpp::spin(node); rclcpp::shutdown(); return 0; }Action Server 核心 API方法作用create_server创建 Action Server注册三个回调handle_goal()收到 Goal 时调用返回 ACCEPT/REJECThandle_cancel()收到 Cancel 请求时调用handle_accepted()Goal 被接受后调用通常启动执行线程publish_feedback()发送 Feedbacksucceed(result)任务成功完成canceled(result)任务被取消abort(result)任务异常终止is_canceling()检查是否收到取消请求关键设计execute()在独立线程中运行避免阻塞 Executor 的事件循环。这样 Server 可以同时处理其他 Topic/Service 回调。6.4.2 Action Client — 异步版本C创建~/ros2_ws/src/my_action_pkg/src/fibonacci_action_client.cpp#include functional #include future #include memory #include string #include sstream #include rclcpp/rclcpp.hpp #include rclcpp_action/rclcpp_action.hpp #include my_action_pkg/action/fibonacci.hpp class FibonacciActionClient : public rclcpp::Node { public: using Fibonacci my_action_pkg::action::Fibonacci; using GoalHandleFibonacci rclcpp_action::ClientGoalHandleFibonacci; explicit FibonacciActionClient(const rclcpp::NodeOptions options rclcpp::NodeOptions()) : Node(fibonacci_action_client, options) { // 创建 Action Client this-client_ptr_ rclcpp_action::create_clientFibonacci(this, fibonacci); // 定时器等待 Server 就绪后发送 Goal this-timer_ this-create_wall_timer( std::chrono::milliseconds(500), std::bind(FibonacciActionClient::send_goal, this)); } void send_goal() { using namespace std::placeholders; // 取消定时器只发送一次 this-timer_-cancel(); // 等待 Server 就绪 if (!this-client_ptr_-wait_for_action_server(std::chrono::seconds(10))) { RCLCPP_ERROR(this-get_logger(), Action server not available after waiting); rclcpp::shutdown(); return; } // 构造 Goal auto goal_msg Fibonacci::Goal(); goal_msg.order 10; RCLCPP_INFO(this-get_logger(), Sending goal: order %d, goal_msg.order); // 配置 Goal 选项 auto send_goal_options rclcpp_action::ClientFibonacci::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); // 异步发送 Goal this-client_ptr_-async_send_goal(goal_msg, send_goal_options); } private: rclcpp_action::ClientFibonacci::SharedPtr client_ptr_; rclcpp::TimerBase::SharedPtr timer_; // Goal 被接受/拒绝的回调 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); } } // 收到 Feedback 的回调 void feedback_callback( GoalHandleFibonacci::SharedPtr, const std::shared_ptrconst Fibonacci::Feedback feedback) { std::stringstream ss; ss Next number in sequence received: ; for (auto num : feedback-partial_sequence) { ss num ; } RCLCPP_INFO(this-get_logger(), ss.str().c_str()); } // 收到 Result 的回调 void result_callback(const GoalHandleFibonacci::WrappedResult result) { switch (result.code) { case rclcpp_action::ResultCode::SUCCEEDED: RCLCPP_INFO(this-get_logger(), Goal was 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; ss Result sequence: ; for (auto num : result.result-sequence) { ss num ; } RCLCPP_INFO(this-get_logger(), ss.str().c_str()); rclcpp::shutdown(); } }; int main(int argc, char ** argv) { rclcpp::init(argc, argv); auto node std::make_sharedFibonacciActionClient(); rclcpp::spin(node); rclcpp::shutdown(); return 0; }Action Client 核心 API方法作用create_client创建 Action Clientwait_for_action_server()等待 Server 就绪async_send_goal()异步发送 Goalasync_cancel_goal()异步取消 Goalgoal_response_callbackGoal 被接受/拒绝时触发feedback_callback收到 Feedback 时触发result_callback收到 Result 时触发6.4.3 配置与编译CMakeLists.txtcmake_minimum_required(VERSION 3.8) project(my_action_pkg) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES Clang) add_compile_options(-Wall -Wextra -Wpedantic) endif() find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) find_package(rclcpp_action REQUIRED) find_package(rosidl_default_generators REQUIRED) find_package(geometry_msgs REQUIRED) # 生成 Action 接口 rosidl_generate_interfaces(${PROJECT_NAME} action/Fibonacci.action action/NavigateToPose.action DEPENDENCIES geometry_msgs ) ament_export_dependencies(rosidl_default_runtime) # 可执行文件 find_package(my_action_pkg REQUIRED) # 需要自身生成的接口 add_executable(fibonacci_action_server src/fibonacci_action_server.cpp) ament_target_dependencies(fibonacci_action_server rclcpp rclcpp_action my_action_pkg) add_executable(fibonacci_action_client src/fibonacci_action_client.cpp) ament_target_dependencies(fibonacci_action_client rclcpp rclcpp_action my_action_pkg) install(TARGETS fibonacci_action_server fibonacci_action_client DESTINATION lib/${PROJECT_NAME} ) ament_package()编译与运行cd ~/ros2_ws colcon build --packages-select my_action_pkg source install/setup.bash # 终端 1启动 Server ros2 run my_action_pkg fibonacci_action_server # 终端 2启动 Client ros2 run my_action_pkg fibonacci_action_client预期输出# Server [INFO] [fibonacci_action_server]: Received goal request with order 10 [INFO] [fibonacci_action_server]: Accepting goal [INFO] [fibonacci_action_server]: Executing goal... [INFO] [fibonacci_action_server]: Publish feedback: [0 1 1 ] [INFO] [fibonacci_action_server]: Publish feedback: [0 1 1 2 ] ... [INFO] [fibonacci_action_server]: Goal succeeded # Client [INFO] [fibonacci_action_client]: Sending goal: order 10 [INFO] [fibonacci_action_client]: Goal accepted by server, waiting for result [INFO] [fibonacci_action_client]: Next number in sequence received: 0 1 1 [INFO] [fibonacci_action_client]: Next number in sequence received: 0 1 1 2 ... [INFO] [fibonacci_action_client]: Goal was succeeded [INFO] [fibonacci_action_client]: Result sequence: 0 1 1 2 3 5 8 13 21 34 556.4.4 取消任务示例在 Client 中添加取消逻辑// 发送 Goal 后 3 秒取消 auto cancel_timer this-create_wall_timer( std::chrono::seconds(3), [this, goal_handle]() { RCLCPP_INFO(this-get_logger(), Canceling goal...); this-client_ptr_-async_cancel_goal(goal_handle); });6.4.5 Python 版本ServerPython# my_action_pkg/my_action_pkg/fibonacci_action_server.py import rclpy from rclpy.action import ActionServer from rclpy.node import Node from my_action_pkg.action import Fibonacci class FibonacciActionServer(Node): def __init__(self): super().__init__(fibonacci_action_server) self._action_server ActionServer( self, Fibonacci, fibonacci, self.execute_callback) async def execute_callback(self, goal_handle): self.get_logger().info(Executing goal...) feedback_msg Fibonacci.Feedback() feedback_msg.partial_sequence [0, 1] for i in range(1, goal_handle.request.order): if goal_handle.is_cancel_requested: goal_handle.canceled() self.get_logger().info(Goal canceled) return Fibonacci.Result() feedback_msg.partial_sequence.append( feedback_msg.partial_sequence[i] feedback_msg.partial_sequence[i-1]) self.get_logger().info(fFeedback: {feedback_msg.partial_sequence}) goal_handle.publish_feedback(feedback_msg) # 模拟耗时操作 import time time.sleep(1) goal_handle.succeed() result Fibonacci.Result() result.sequence feedback_msg.partial_sequence self.get_logger().info(Goal succeeded) return result def main(argsNone): rclpy.init(argsargs) node FibonacciActionServer() rclpy.spin(node) rclpy.shutdown() if __name__ __main__: main()ClientPython# my_action_pkg/my_action_pkg/fibonacci_action_client.py import rclpy from rclpy.action import ActionClient from rclpy.node import Node from my_action_pkg.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.get_logger().info(fSending goal: order {order}) self._send_goal_future self._action_client.send_goal_async( goal_msg, feedback_callbackself.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 feedback_callback(self, feedback_msg): sequence feedback_msg.feedback.partial_sequence self.get_logger().info(fFeedback: {sequence}) def get_result_callback(self, future): result future.result().result self.get_logger().info(fResult: {result.sequence}) rclpy.shutdown() def main(argsNone): rclpy.init(argsargs) node FibonacciActionClient() node.send_goal(10) rclpy.spin(node) if __name__ __main__: main()6.5 实战实现导航到目标点动作模拟 AGV 运动控制6.5.1 场景描述模拟一个 AGV自动导引车从当前位置导航到目标位置的过程收到 Goal目标位姿(x, y, theta)执行过程直线行驶 转向实时反馈当前位置和剩余距离可能事件遇到障碍物需暂停、用户取消任务、新目标抢占最终结果成功到达 / 失败 / 取消6.5.2 Action 接口定义使用前面定义的NavigateToPose.actiongeometry_msgs/PoseStamped target_pose float32 tolerance --- bool success string message float32 distance_traveled --- geometry_msgs/Pose current_pose float32 distance_remaining string status6.5.3 模拟 AGV Action Server创建~/ros2_ws/src/my_action_pkg/src/navigate_action_server.cpp#include cmath #include memory #include string #include thread #include rclcpp/rclcpp.hpp #include rclcpp_action/rclcpp_action.hpp #include geometry_msgs/msg/pose.hpp #include geometry_msgs/msg/pose_stamped.hpp #include my_action_pkg/action/navigate_to_pose.hpp class NavigateActionServer : public rclcpp::Node { public: using NavigateToPose my_action_pkg::action::NavigateToPose; using GoalHandleNavigate rclcpp_action::ServerGoalHandleNavigateToPose; NavigateActionServer() : Node(navigate_action_server), current_x_(0.0), current_y_(0.0), current_theta_(0.0) { using namespace std::placeholders; action_server_ rclcpp_action::create_serverNavigateToPose( this, navigate_to_pose, std::bind(NavigateActionServer::handle_goal, this, _1, _2), std::bind(NavigateActionServer::handle_cancel, this, _1), std::bind(NavigateActionServer::handle_accepted, this, _1)); RCLCPP_INFO(this-get_logger(), Navigate Action Server ready); } private: rclcpp_action::ServerNavigateToPose::SharedPtr action_server_; double current_x_, current_y_, current_theta_; rclcpp_action::GoalResponse handle_goal( const rclcpp_action::GoalUUID , std::shared_ptrconst NavigateToPose::Goal goal) { RCLCPP_INFO(this-get_logger(), Received navigation goal: [%.2f, %.2f, %.2f], goal-target_pose.pose.position.x, goal-target_pose.pose.position.y, get_yaw_from_quaternion(goal-target_pose.pose.orientation)); return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; } rclcpp_action::CancelResponse handle_cancel( const std::shared_ptrGoalHandleNavigate goal_handle) { RCLCPP_INFO(this-get_logger(), Received cancel request); (void)goal_handle; return rclcpp_action::CancelResponse::ACCEPT; } void handle_accepted(const std::shared_ptrGoalHandleNavigate goal_handle) { using namespace std::placeholders; std::thread{std::bind(NavigateActionServer::execute, this, _1), goal_handle}.detach(); } void execute(const std::shared_ptrGoalHandleNavigate goal_handle) { const auto goal goal_handle-get_goal(); double target_x goal-target_pose.pose.position.x; double target_y goal-target_pose.pose.position.y; double tolerance goal-tolerance; auto feedback std::make_sharedNavigateToPose::Feedback(); auto result std::make_sharedNavigateToPose::Result(); double total_distance 0.0; rclcpp::Rate loop_rate(10); // 10Hz 控制频率 while (rclcpp::ok()) { // 检查取消 if (goal_handle-is_canceling()) { result-success false; result-message Canceled by user; result-distance_traveled total_distance; goal_handle-canceled(result); RCLCPP_INFO(this-get_logger(), Navigation canceled); return; } // 计算距离 double dx target_x - current_x_; double dy target_y - current_y_; double distance std::sqrt(dx * dx dy * dy); // 检查是否到达 if (distance tolerance) { result-success true; result-message Goal reached successfully; result-distance_traveled total_distance; goal_handle-succeed(result); RCLCPP_INFO(this-get_logger(), Navigation succeeded); return; } // 模拟运动向目标移动一小步 double step 0.05; // 每周期移动 0.05m if (distance step) step distance; current_x_ step * dx / distance; current_y_ step * dy / distance; total_distance step; // 发布 Feedback feedback-current_pose.position.x current_x_; feedback-current_pose.position.y current_y_; feedback-distance_remaining distance; if (distance 2.0) { feedback-status 行驶中; } else if (distance 0.5) { feedback-status 接近目标; } else { feedback-status 精确定位中; } goal_handle-publish_feedback(feedback); loop_rate.sleep(); } } double get_yaw_from_quaternion(const geometry_msgs::msg::Quaternion q) { // 简化从四元数提取偏航角 return std::atan2(2.0 * (q.w * q.z q.x * q.y), 1.0 - 2.0 * (q.y * q.y q.z * q.z)); } }; int main(int argc, char ** argv) { rclcpp::init(argc, argv); auto node std::make_sharedNavigateActionServer(); rclcpp::spin(node); rclcpp::shutdown(); return 0; }6.5.4 导航客户端与 RViz 可视化// navigate_action_client.cpp关键片段 void result_callback(const GoalHandleNavigate::WrappedResult result) { if (result.code rclcpp_action::ResultCode::SUCCEEDED) { RCLCPP_INFO(this-get_logger(), Navigation succeeded! Traveled: %.2f m, result.result-distance_traveled); } } void feedback_callback( GoalHandleNavigate::SharedPtr, const std::shared_ptrconst NavigateToPose::Feedback feedback) { RCLCPP_INFO(this-get_logger(), Status: %s, Remaining: %.2f m, Pos: [%.2f, %.2f], feedback-status.c_str(), feedback-distance_remaining, feedback-current_pose.position.x, feedback-current_pose.position.y); }6.5.5 命令行测试# 查看 Action 列表 ros2 action list # /fibonacci # /navigate_to_pose # 查看 Action 信息 ros2 action info /navigate_to_pose # 发送 Goal命令行 ros2 action send_goal /navigate_to_pose my_action_pkg/action/NavigateToPose \ {target_pose: {pose: {position: {x: 5.0, y: 3.0, z: 0.0}}}, tolerance: 0.1} # 带反馈显示 ros2 action send_goal /navigate_to_pose my_action_pkg/action/NavigateToPose \ {target_pose: {pose: {position: {x: 5.0, y: 3.0}}}, tolerance: 0.1} \ --feedback6.6 Action 设计最佳实践6.6.1 Server 侧实践说明独立线程执行execute()必须在独立线程中运行避免阻塞 Executor定期检查取消在循环中频繁调用is_canceling()及时响应取消合理的发送频率Feedback 频率建议 10~100Hz过高会增加网络负载优雅处理抢占新 Goal 到来时正确终止旧任务ROS2 Action Server 自动处理状态机设计复杂任务使用状态机管理INIT → RUNNING → PAUSED → DONE6.6.2 Client 侧实践说明异步优先始终使用async_send_goal()避免阻塞处理所有回调必须注册goal_response、feedback、result三个回调超时处理设置合理的等待超时Server 未就绪时降级取消机制提供用户取消接口如 GUI 的停止按钮6.6.3 Action 底层实现Action 底层基于三个 Topic实现Topic方向用途/action_name/_action/send_goalClient → Server发送 Goal/action_name/_action/cancel_goalClient → Server发送 Cancel/action_name/_action/statusServer → Client发布 Goal 状态/action_name/_action/feedbackServer → Client发布 Feedback/action_name/_action/get_resultClient ↔ Server请求/返回 Result这些 Topic 由rclcpp_action自动管理开发者无需直接操作。6.7 三种通信机制对比总结特性TopicServiceAction通信模式Pub-SubReq-ResGoal-Feedback-Result方向性单向双向双向 持续反馈调用次数持续流一次一次 Goal 多次 Feedback 一次 Result阻塞性非阻塞同步/异步异步取消能力❌❌✅进度反馈❌❌✅抢占能力❌❌✅典型应用传感器数据参数查询导航、抓取、长时间任务

相关新闻

最新新闻

日新闻

周新闻

月新闻