ROS2 Jazzy 自定义 Action 实现小车导航(实时进度反馈)

发布时间:2026/7/2 2:35:01

ROS2 Jazzy 自定义 Action 实现小车导航(实时进度反馈) 一、项目简介ROS2 中Action专门用于长时间运行的任务导航、机械臂运动、拍照巡检等相比 Service 一问一答、Topic 单向发布Action 拥有三段完整交互Goal客户端下发执行目标坐标Feedback服务端循环推送实时进度剩余距离Result任务结束后返回最终执行结果本案例基于华为云开发者空间 ROS2 Jazzy自定义Move.action导航动作客户端下发目标 (7.0,8.0)服务端模拟小车渐进行驶、每秒推送剩余距离抵达终点后返回实际坐标。二、工程目录结构action_nav_ws ├── build install log # colcon编译自动生成缓存文件夹 └── src ├── nav_interfaces # 规范分包单独存放自定义Action接口 │ └── action │ └── Move.action # 导航动作定义文件 └── nav_action_demo # 业务包Action服务端客户端源码 ├── src │ ├── nav_server.cpp # 导航动作服务端 │ └── nav_client.cpp# 导航动作客户端 ├── launch # 预留启动文件目录 ├── include ├── CMakeLists.txt └── package.xml分层优势接口与业务代码完全解耦其他工程可直接复用nav_interfaces动作包。三、自定义 Action 接口 Move.action语法说明三段内容使用---强制分隔顺序固定为 Goal → Result → Feedback。四、Action 服务端 nav_server.cpp#include rclcpp/rclcpp.hpp #include rclcpp_action/rclcpp_action.hpp #include nav_interfaces/action/move.hpp using Move nav_interfaces::action::Move; class NavServer : public rclcpp::Node { public: NavServer() : Node(nav_server) { // 创建Action服务绑定3个核心回调函数 server_ rclcpp_action::create_serverMove( this, move_task, std::bind(NavServer::on_goal, this, std::placeholders::_1), std::bind(NavServer::on_cancel, this, std::placeholders::_1), std::bind(NavServer::on_accept, this, std::placeholders::_1) ); } private: rclcpp_action::ServerMove::SharedPtr server_; // 1. 收到客户端目标请求回调 rclcpp_action::GoalResponse on_goal(const rclcpp_action::GoalUUID , std::shared_ptrconst Move::Goal goal) { RCLCPP_INFO(get_logger(), 收到目标点%.2f , %.2f, goal-target_x, goal-target_y); // 接受本次导航任务 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; } // 2. 客户端取消任务回调 rclcpp_action::CancelResponse on_cancel(std::shared_ptrrclcpp_action::ServerGoalHandleMove) { RCLCPP_INFO(get_logger(), 任务被取消); return rclcpp_action::CancelResponse::ACCEPT; } // 3. 接受任务新开子线程执行导航防止阻塞节点spin void on_accept(std::shared_ptrrclcpp_action::ServerGoalHandleMove handle) { std::thread{std::bind(NavServer::execute, this, handle)}.detach(); } // 导航核心执行逻辑 void execute(std::shared_ptrrclcpp_action::ServerGoalHandleMove handle) { auto goal handle-get_goal(); auto feedback std::make_sharedMove::Feedback(); auto result std::make_sharedMove::Result(); float now_x 0.0f; float now_y 0.0f; rclcpp::Rate loop_rate(1); // 1Hz每秒更新一次进度 while (rclcpp::ok()) { // 判断任务是否被客户端取消 if (handle-is_canceling()) { handle-canceled(result); return; } // 模拟小车缓慢向目标靠近 now_x (goal-target_x - now_x) * 0.2; now_y (goal-target_y - now_y) * 0.2; // 计算当前距离目标的剩余距离 float dist sqrt(pow(goal-target_x - now_x, 2) pow(goal-target_y - now_y, 2)); feedback-remaining_dist dist; // 向客户端推送实时进度反馈 handle-publish_feedback(feedback); RCLCPP_INFO(get_logger(), 剩余距离%.2f, dist); // 距离小于0.1判定小车抵达终点 if (dist 0.1) { result-finish_state true; result-final_x now_x; result-final_y now_y; handle-succeed(result); RCLCPP_INFO(get_logger(), 抵达目标坐标%.2f , %.2f, now_x, now_y); break; } loop_rate.sleep(); } } }; int main(int argc, char ** argv) { rclcpp::init(argc, argv); rclcpp::spin(std::make_sharedNavServer()); rclcpp::shutdown(); return 0; }五、Action 客户端 nav_client.cpp#include rclcpp/rclcpp.hpp #include rclcpp_action/rclcpp_action.hpp #include nav_interfaces/action/move.hpp using Move nav_interfaces::action::Move; using namespace std::chrono_literals; class NavClient : public rclcpp::Node { public: NavClient() : Node(nav_client) { // 客户端绑定服务端动作名称 move_task client_ rclcpp_action::create_clientMove(this, move_task); } // 下发导航目标接口 void send_task(float x, float y) { // 等待服务端上线5秒未连接直接报错返回 if (!client_-wait_for_action_server(5s)) { RCLCPP_ERROR(get_logger(), 无法连接服务端); return; } // 构造目标坐标栈对象规避Jazzy版本类型报错 Move::Goal goal; goal.target_x x; goal.target_y y; // 异步发送导航目标 auto future client_-async_send_goal(goal); rclcpp::spin_until_future_complete(this-get_node_base_interface(), future); auto goal_handle future.get(); if (!goal_handle) { RCLCPP_ERROR(get_logger(), 目标被服务端拒绝); return; } // 异步获取导航最终结果 auto res_future client_-async_get_result(goal_handle); rclcpp::spin_until_future_complete(this-get_node_base_interface(), res_future); auto result_wrap res_future.get(); // 导航成功打印小车实际抵达坐标 if (result_wrap.code rclcpp_action::ResultCode::SUCCEEDED) { RCLCPP_INFO(get_logger(), 任务完成最终坐标 x%.2f y%.2f, result_wrap.result-final_x, result_wrap.result-final_y); } } private: rclcpp_action::ClientMove::SharedPtr client_; }; int main(int argc, char ** argv) { rclcpp::init(argc, argv); auto node std::make_sharedNavClient(); // 下发导航目标x7.0 y8.0 node-send_task(7.0, 8.0); rclcpp::spin(node); rclcpp::shutdown(); return 0; }六、完整编译命令与日志日志说明编译输出中黄色警告CATKIN_INSTALL_INTO_PREFIX_ROOT是华为云开发环境自带冗余变量不属于代码错误完全不影响编译、运行可直接忽略 日志末尾出现两行Finished 包名代表编译全部成功可执行文件生成在install文件夹内。七、分终端运行测试终端 1启动 Action 导航服务端运行效果接收客户端下发目标7.00 , 8.00每秒打印实时剩余距离模拟小车持续向目标靠近距离小于 0.1 时打印「抵达目标坐标6.94 , 7.93」导航任务结束终端 2启动 Action 导航客户端运行效果 客户端等待导航任务执行完成最终输出关键日志[INFO] [nav_client]: 任务完成最终坐标 x6.94 y7.93代表完整 Action 通信闭环客户端成功接收服务端返回的导航结果。八、华为云开发环境踩坑总结ros2 run提示 Package not found云开发空间环境索引存在 bugros2 pkg检索失效直接执行install目录下二进制文件即可绕过该问题直接运行二进制提示缺少.so动态库必须前置执行source install/setup.bash加载自定义动作库路径否则程序找不到nav_interfaces接口动态文件编译出现黄色 CATKIN 警告环境内置旧 catkin 兼容变量无任何功能影响无需修改 CMakeLists 屏蔽旧版本客户端红色unknown response日志已修复 本次更新后的客户端代码规避了 Jazzy 版本 API 兼容报错运行日志仅保留正常 INFO 信息无多余红色警告。九、整体流程梳理客户端构造导航目标 (7.0,8.0)异步发送给 Action 服务端服务端接收目标新开独立子线程模拟小车行驶循环计算小车与目标的距离每秒推送Feedback实时进度小车距离目标小于 0.1判定抵达终点封装Result结果客户端接收任务完成结果打印小车实际抵达坐标 完整实现 ROS2 Action「下发目标 - 实时进度反馈 - 任务结果返回」全套标准通信流程。十、拓展方向在代码中增加取消任务逻辑测试on_cancel回调编写 launch 文件一键同时启动服务端 客户端新增多组导航目标实现连续多点导航结合 TF2 / 激光雷达替换纯模拟行驶逻辑对接真实机器人底盘。

相关新闻