第 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阻塞性非阻塞同步/异步异步取消能力❌❌✅进度反馈❌❌✅抢占能力❌❌✅典型应用传感器数据参数查询导航、抓取、长时间任务

相关新闻

rootfs

rootfs

注:本篇笔记只是在BusyBox Buildroot基础上进行讲解 BusyBox,Buildroot和rootfs的关系 Buildroot 造出 rootfs;BusyBox 住在 rootfs 里,负责开机后当 pid 1。 Buildroot(PC 上构建)│ 编译 BusyBox、拷贝…

2026/7/30 1:23:21阅读更多 →
AI撸代码越快越烂?真正大佬都在用的「三层约束工程法」

AI撸代码越快越烂?真正大佬都在用的「三层约束工程法」

文章目录一、靠AI快速撸项目,爽只是暂时的假象1. 代码膨胀到离谱,一个组件干到五百行2. 牵一发而动全身,修一个bug炸出俩新问题3. AI总擅自加功能,偷偷篡改数据结构4. 最后项目彻底烂尾,只能删库重来二、核心解法&…

2026/7/30 1:23:21阅读更多 →
如何快速搭建家庭游戏串流服务器:Sunshine完全配置指南

如何快速搭建家庭游戏串流服务器:Sunshine完全配置指南

如何快速搭建家庭游戏串流服务器:Sunshine完全配置指南 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine Sunshine是一款功能强大的自托管游戏串流服务器,专为…

2026/7/30 1:23:21阅读更多 →
Waves Ultimate 17一键安装完整版安装教程Waves 17最新版VR/R2R下载专用混音插件Win/Mac系统Waves 17/16/15/14视频安装教程一键安装完整版混音插件

Waves Ultimate 17一键安装完整版安装教程Waves 17最新版VR/R2R下载专用混音插件Win/Mac系统Waves 17/16/15/14视频安装教程一键安装完整版混音插件

Win/Mac Waves 17 / Waves16 最新中文完整版 ​ Waves 17 下载链接: Win系统 https://www.dygdu.com/16997.html Mac 系统 https://www.dygdu.com/17000.html 一、Waves Audio品牌简介 Waves Audio成立于1992年,总部位于美国纽约,是全球领…

2026/7/30 2:43:15阅读更多 →
技术方案的编写指南——从需求到设计文档的结构化表达方法

技术方案的编写指南——从需求到设计文档的结构化表达方法

技术方案的编写指南——从需求到设计文档的结构化表达方法 一、背景与动机 技术方案文档是架构师与团队、业务方、管理层沟通的核心载体。一份结构清晰、逻辑完整的技术方案,能让评审效率提升数倍,也能让后续实施减少歧义。然而,现实中大量…

2026/7/30 2:43:15阅读更多 →
js-mindmap:基于力导向布局的高性能JavaScript思维导图引擎

js-mindmap:基于力导向布局的高性能JavaScript思维导图引擎

js-mindmap:基于力导向布局的高性能JavaScript思维导图引擎 【免费下载链接】js-mindmap JavaScript Mindmap 项目地址: https://gitcode.com/gh_mirrors/js/js-mindmap 在复杂知识可视化领域,传统思维导图工具面临节点数量限制和渲染性能瓶颈。j…

2026/7/30 2:43:15阅读更多 →
AI 产品的技术可行性评估——如何判断一个 AI 需求是否值得投入

AI 产品的技术可行性评估——如何判断一个 AI 需求是否值得投入

AI 产品的技术可行性评估——如何判断一个 AI 需求是否值得投入 一、背景与动机 2026 年,AI 需求涌入各个业务线:"能不能用 AI 做智能客服?""能不能用 AI 自动生成报告?""能不能用 AI 辅助代码审查&am…

2026/7/30 2:43:15阅读更多 →
从 Loop 到 Graph:一次 Agent 架构的进化,以及 LangGraph 内核里藏着的那台状态机

从 Loop 到 Graph:一次 Agent 架构的进化,以及 LangGraph 内核里藏着的那台状态机

上周刷到一篇文章,开头引了 X 上的一个问题:“Are we still talking loops, or did we shift to graphs yet?”(我们还在聊 Loop 吗,还是已经进入 Graph 时代了?) 说实话,我第一反应是&#x…

2026/7/30 2:43:15阅读更多 →
AI数字人口型同步与小程序集成:从技术演示到工程化落地

AI数字人口型同步与小程序集成:从技术演示到工程化落地

最近在测试一些新的 AI 工具时,我发现一个很有意思的现象:很多团队在发布新功能时,往往只强调“我们增加了什么”,却很少说清楚“这个功能真正解决了什么实际问题”。比如最近上线的 PixVerse 语音功能升级,表面看是增…

2026/7/30 2:41:14阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/29 9:47:45阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/29 7:00:19阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/29 7:58:51阅读更多 →
3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 [特殊字符]

3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 [特殊字符]

3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 🚀 【免费下载链接】TrollInstallerX A TrollStore installer for iOS 14.0 - 16.6.1 项目地址: https://gitcode.com/gh_mirrors/tr/TrollInstallerX 你是否曾经因为iOS系统的严格…

2026/7/30 0:00:58阅读更多 →
[GESP202606 四级] 扫雷

[GESP202606 四级] 扫雷

B4557 [GESP202606 四级] 扫雷 https://www.luogu.com.cn/problem/B4557 中国计算机学会(CCF)2026年6月C四级讲解——扫雷 https://www.bilibili.com/video/BV1MCMg6AEXR/ B4557 [GESP202606 四级] 扫雷 https://www.bilibili.com/video/BV1ZKTj6ZEVh/ 2…

2026/7/30 0:00:58阅读更多 →
Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer 您是否曾因Windows系统盘空间不足而烦恼?是否遇到过设…

2026/7/30 0:00:58阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/30 0:27:26阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/29 4:31:51阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/29 14:26:42阅读更多 →