【ROS2】动作通信机制 Action 代码示例(C++版)
ROS1 系列教程(总目录)ROS2 系列教程(总目录)ROS2 系列教程源码、学习群目录一、创建功能包二、Action服务端三、Action客户端四、编辑编译配置文件CMakeList.txt五、编译运行下面通过代码演示自定义 Action 的基本实现。一、创建功能包cdros2_learning/src ros2 pkg create --build-type ament_cmake action_learning_cpp其中使用--build-type指定编译系统为ament_cmakeaction_learning_cpp自定义功能包名称生成的目录结构如下action_learning_cpp ├── CMakeLists.txt ├── include │ └── action_learning_cpp ├── LICENSE ├── package.xml └── src二、Action服务端我们编写一个服务端、客户端实现计算斐波那契数列。在action_learning_cpp/src目录下新增action_server_base.cpp文件文件内容如下/** * file action_server_basic.cpp * brief Action Server 基础 —— 斐波那契数列服务端 * * 知识点 * - rclcpp_action::create_server() 创建 Action Server * - handle_goal决定接受/拒绝目标 * - handle_cancel决定允许/拒绝取消 * - handle_accepted启动执行线程 * - execute执行逻辑 发布 Feedback 返回 Result * * 功能 * 客户端指定 order项数服务端逐步计算斐波那契数列 * 每计算出一项就通过 Feedback 发布当前已生成的部分数列 * 最终通过 Result 返回完整的斐波那契数列。 * * 运行 * ros2 run action_learning_cpp action_server_basic * ros2 action send_goal /fibonacci action_learning_cpp/action/Fibonacci {order: 10} */#includerclcpp/rclcpp.hpp#includerclcpp_action/rclcpp_action.hpp#includeaction_learning_cpp/action/fibonacci.hpp#includethreadusingFibonacciaction_learning_cpp::action::Fibonacci;classFibonacciServer:publicrclcpp::Node{public:FibonacciServer():Node(fibonacci_server){action_server_rclcpp_action::create_serverFibonacci(this,fibonacci,std::bind(FibonacciServer::handle_goal,this,std::placeholders::_1,std::placeholders::_2),std::bind(FibonacciServer::handle_cancel,this,std::placeholders::_1),std::bind(FibonacciServer::handle_accepted,this,std::placeholders::_1));RCLCPP_INFO(this-get_logger(), Fibonacci Action Server started );RCLCPP_INFO(thresult_callbackis-get_logger(),Action name: /fibonacci);RCLCPP_INFO(this-get_logger(),Waiting for client to send goal...);}private:rclcpp_action::GoalResponsehandle_goal(constrclcpp_action::GoalUUIDuuid,std::shared_ptrconstFibonacci::Goalgoal){(void)uuid;RCLCPP_INFO(this-get_logger(),Received goal request with order: %ld,goal-order);// ── 目标合法性校验 ──// 斐波那契数列至少需要 1 项if(goal-order0){RCLCPP_WARN(this-get_logger(),Goal rejected: order must be 0);returnrclcpp_action::GoalResponse::REJECT;}RCLCPP_INFO(this-get_logger(),Goal accepted with order: %ld,goal-order);returnrclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;}rclcpp_action::CancelResponsehandle_cancel(conststd::shared_ptrrclcpp_action::ServerGoalHandleFibonaccigoal_handle){RCLCPP_INFO(this-get_logger(),Cancel request received, cancel accepted);(void)goal_handle;returnrclcpp_action::CancelResponse::ACCEPT;}voidhandle_accepted(conststd::shared_ptrrclcpp_action::ServerGoalHandleFibonaccigoal_handle){// 在新线程中执行避免阻塞 executorstd::thread{std::bind(FibonacciServer::execute,this,std::placeholders::_1),goal_handle}.detach();}voidexecute(conststd::shared_ptrrclcpp_action::ServerGoalHandleFibonaccigoal_handle){RCLCPP_INFO(this-get_logger(),Executing goal...);// 从 GoalHandle 获取目标autogoalgoal_handle-get_goal();// 创建 Result 和 Feedback 对象autoresultstd::make_sharedFibonacci::Result();autofeedbackstd::make_sharedFibonacci::Feedback();rclcpp::Rateloop_rate(1);// 1Hz — 每秒计算一项// 初始化斐波那契数列的前两项int64_ta0,b1;for(int64_ti0;igoal-order;i){// ── 检查是否被取消 ──if(goal_handle-is_canceling()){// 返回已计算出的部分数列result-sequencefeedback-current_sequence;goal_handle-canceled(result);RCLCPP_INFO(this-get_logger(),Goal canceled, computed %ld/%ld terms,i,goal-order);return;}// ── 计算当前项 ──int64_tcurrent_value;if(i0){current_value0;}elseif(i1){current_value1;}else{current_valueab;ab;bcurrent_value;}// ── 更新并发布 Feedback当前已生成的部分数列──feedback-current_sequence.push_back(current_value);goal_handle-publish_feedback(feedback);RCLCPP_INFO(this-get_logger(),Progress: %ld/%ld, current term: %ld,i1,goal-order,current_value);// 保存到结果序列result-sequence.push_back(current_value);loop_rate.sleep();}// ── 执行成功 ──goal_handle-succeed(result);RCLCPP_INFO(this-get_logger(),Goal completed! Fibonacci sequence has %ld terms,result-sequence.size());}rclcpp_action::ServerFibonacci::SharedPtr action_server_;};intmain(intargc,char**argv){rclcpp::init(argc,argv);autonodestd::make_sharedFibonacciServer();rclcpp::spin(node);rclcpp::shutdown();return0;}其中line:34 创建了一个action服务端并绑定回调函数handle_goal 处理客户端的 send_goal 请求handle_cancel 处理客户端的 async_cancel_goal 请求handle_accepted 在服务端通过 handle_goal 返回 ACCEPT 后同步被回调用于处理 goal 的任务action_server_rclcpp_action::create_serverFibonacci(this,fibonacci, std::bind(FibonacciServer::handle_goal, this, std::placeholders::_1, std::placeholders::_2), std::bind(FibonacciServer::handle_cancel, this, std::placeholders::_1), std::bind(FibonacciServer::handle_accepted, this, std::placeholders::_1));handle_goal 中提供的参数uuid是目标请求会话的唯一标识符可用于日志追踪。rclcpp_action::GoalResponsehandle_goal(constrclcpp_action::GoalUUIDuuid,std::shared_ptrconstFibonacci::Goalgoal)另外需要注意的是handle_accepted 应快速返回避免阻塞执行器。一般处理是在这里创建一个新线程处理goal的任务voidhandle_accepted(conststd::shared_ptrrclcpp_action::ServerGoalHandleFibonaccigoal_handle){std::thread{std::bind(FibonacciServer::execute,this,std::placeholders::_1),goal_handle}.detach();}三、Action客户端在action_learning_cpp/src目录下新增action_server_base.cpp文件文件内容如下/** * file action_client_basic.cpp * brief Action Client 基础 —— 斐波那契数列客户端 * * 知识点 * - rclcpp_action::create_client() 创建 Action Client * - wait_for_action_server() 等待服务端上线 * - async_send_goal() 发送目标 * - SendGoalOptions 的三个回调 * goal_response_callback —— 服务端接受/拒绝目标的回调 * feedback_callback —— 收到反馈的回调 * result_callback —— 收到最终结果的回调 * * 运行先启动 action_server_basic * ros2 run action_learning_cpp action_client_basic */#includerclcpp/rclcpp.hpp#includerclcpp_action/rclcpp_action.hpp#includeaction_learning_cpp/action/fibonacci.hpp#includechrono#includesstream#includememoryusingFibonacciaction_learning_cpp::action::Fibonacci;usingnamespacestd::chrono_literals;classFibonacciClient:publicrclcpp::Node{public:FibonacciClient():Node(fibonacci_client){client_rclcpp_action::create_clientFibonacci(this,fibonacci);RCLCPP_INFO(this-get_logger(), Fibonacci Action Client created );// 等待服务端上线if(!client_-wait_for_action_server(10s)){RCLCPP_ERROR(this-get_logger(),Action Server /fibonacci not available, exiting);return;}RCLCPP_INFO(this-get_logger(),Action Server is available, sending goal...);// 发送目标计算前 10 项斐波那契数列send_goal(10);}private:// 将数列格式转为字符串方便日志输出std::stringformat_sequence(conststd::vectorint64_tseq){std::ostringstream oss;oss[;for(size_t i0;iseq.size();i){if(i0)oss, ;ossseq[i];}oss];returnoss.str();}// ── 成员函数回调1: goal_response_callback ──// 服务端接受或拒绝目标时触发voidgoal_response_callback(constrclcpp_action::ClientGoalHandleFibonacci::SharedPtrgoal_handle){if(!goal_handle){RCLCPP_ERROR(this-get_logger(),Goal rejected!);}else{RCLCPP_INFO(this-get_logger(),Goal accepted, waiting for execution...);}}// ── 成员函数回调2: feedback_callback ──// 服务端发布反馈时触发voidfeedback_callback(rclcpp_action::ClientGoalHandleFibonacci::SharedPtr,conststd::shared_ptrconstFibonacci::Feedbackfeedback){RCLCPP_INFO(this-get_logger(),Feedback received: %s,format_sequence(feedback-current_sequence).c_str());}// ── 成员函数回调3: result_callback ──// 目标执行完成时触发成功/失败/取消voidresult_callback(constrclcpp_action::ClientGoalHandleFibonacci::WrappedResultresult){switch(result.code){caserclcpp_action::ResultCode::SUCCEEDED:RCLCPP_INFO(this-get_logger(),Goal succeeded! Fibonacci sequence: %s,format_sequence(result.result-sequence).c_str());break;caserclcpp_action::ResultCode::ABORTED:RCLCPP_ERROR(this-get_logger(),Goal aborted!);break;caserclcpp_action::ResultCode::CANCELED:RCLCPP_WARN(this-get_logger(),Goal canceled, partial sequence: %s,format_sequence(result.result-sequence).c_str());break;default:RCLCPP_ERROR(this-get_logger(),Unknown result code);break;}}voidsend_goal(int64_torder){// ── 创建目标消息 ──autogoal_msgFibonacci::Goal();goal_msg.orderorder;// 配置 SendGoalOptions —— 三个核心回调autosend_goal_optionsrclcpp_action::ClientFibonacci::SendGoalOptions();// ── 绑定成员函数作为回调 ──// 方法1: 使用 std::bindsend_goal_options.goal_response_callbackstd::bind(FibonacciClient::goal_response_callback,this,std::placeholders::_1);send_goal_options.feedback_callbackstd::bind(FibonacciClient::feedback_callback,this,std::placeholders::_1,std::placeholders::_2);// 方法2: 使用 lambda 包装成员函数更现代的风格send_goal_options.result_callback[this](constautoresult){this-result_callback(result);};// ── 发送目标 ──RCLCPP_INFO(this-get_logger(),Sending goal, order: %ld,order);client_-async_send_goal(goal_msg,send_goal_options);}rclcpp_action::ClientFibonacci::SharedPtr client_;};intmain(intargc,char**argv){rclcpp::init(argc,argv);autonodestd::make_sharedFibonacciClient();rclcpp::spin(node);rclcpp::shutdown();return0;}其中line:33 创建了一个action客户端入参为节点指针和Action名称返回Action客户端指针用于后续操作。client_rclcpp_action::create_clientFibonacci(this,fibonacci);line:54 将发送目标封装为一个成员函数函数中包括创建目标对象、配置目标操作、发送目标等基本流程// 1.创建目标消息autogoal_msgFibonacci::Goal();goal_msg.orderorder;// 2.配置 SendGoalOptions —— 三个核心回调autosend_goal_optionsrclcpp_action::ClientFibonacci::SendGoalOptions();send_goal_options.goal_response_callback...send_goal_options.feedback_callback...send_goal_options.result_callback...// 3.发送目标client_-async_send_goal(goal_msg,send_goal_options);其中注册回调函数展示了两种方法第一种使用 std::bind 传统的标准C风格第二种使用 lambda 包装成员函数更现代的C风格。// 方法1使用 std::bind 传统的标准C风格send_goal_options.goal_response_callbackstd::bind(FibonacciClient::goal_response_callback,this,std::placeholders::_1);send_goal_options.feedback_callbackstd::bind(FibonacciClient::feedback_callback,this,std::placeholders::_1,std::placeholders::_2);// 方法2: 使用 lambda 包装成员函数更现代的C风格send_goal_options.result_callback[this](constautoresult){this-result_callback(result);};四、编辑编译配置文件CMakeList.txt在action_learning_cpp/src目录下的CMakeList.txt文件中新增如下内容# 添加action依赖 find_package(rclcpp REQUIRED) find_package(rclcpp_action REQUIRED) find_package(std_msgs REQUIRED) # 生成自定义 Action 消息 find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} action/Fibonacci.action ) # 获取生成的消息类型支持目标 rosidl_get_typesupport_target(cpp_typesupport_target ${PROJECT_NAME} rosidl_typesupport_cpp) # 辅助宏创建可执行目标 macro(add_action_executable name) add_executable(${name} src/${name}.cpp) ament_target_dependencies(${name} rclcpp rclcpp_action rclcpp_lifecycle lifecycle_msgs std_msgs geometry_msgs) target_link_libraries(${name} ${cpp_typesupport_target}) install(TARGETS ${name} DESTINATION lib/${PROJECT_NAME}) endmacro() # 添加节点 add_action_executable(action_server_basic) add_action_executable(action_client_basic)五、编译运行进入到工作空间目录执行如下指令编译该工程colcon build编译成功后先设置环境再依次启动服务端与客户端节点。sourceinstall/setup.bash ros2 run action_learning_cpp action_server_basic ros2 run action_learning_cpp action_client_basic结果如下