ROS Action 完整示例(AI辅助):客户端发目标 + 服务器接参数(lambda 替代 boost::bind)

当然可以!以下是完全适配 CSDN 文章排版 的整理版内容,已按照你提供的结构进行优化排版:保留原始逻辑、注释清晰、语法高亮标记明确、标题分级规范、重点突出,并在文末添加了你提到的进阶建议 ------ OMPL 路径规划极简代码片段,方便你后续扩展文章内容。


ROS Action 完整示例:客户端发目标 + 服务器接参数(lambda 替代 boost::bind)

本文提供一个极简可运行的 ROS Action 示例 ,包含「OMPLDWBNavigator 服务器类 + 测试客户端」,清晰演示参数传递全过程。使用 C++11 lambda 表达式替代 boost::bind 实现回调绑定,适配 ROS Noetic / Melodic 等主流版本。

✅ 特点:代码简洁、可直接编译运行、适合新手理解 Action 通信机制

🔁 场景:机器人导航中"客户端发送目标 → 服务器执行路径规划 + 控制"的典型流程


一、完整源代码(可直接编译运行)

保存为 ompl_dwb_navigator_demo.cpp

cpp 复制代码
#include <ros/ros.h>
#include <actionlib/server/simple_action_server.h>
#include <actionlib/client/simple_action_client.h>
#include <move_base_msgs/MoveBaseAction.h> // 标准导航 Action 类型(ROS 自带)

// 核心服务器类:OMPLDWBNavigator(简化版,贴合实际项目结构)
class OMPLDWBNavigator {
private:
    ros::NodeHandle nh_; // ROS 节点句柄
    // Action 服务器对象(类型:MoveBaseAction)
    actionlib::SimpleActionServer<move_base_msgs::MoveBaseAction> as_;
    std::string action_name_; // Action 名称(与客户端一致)

    /**
     * 回调函数:接收客户端发送的目标参数
     * @param goal 客户端发送的目标(由 ROS 框架自动传入,对应原 boost::bind 的 _1)
     */
    void executeCB(const move_base_msgs::MoveBaseGoalConstPtr& goal) {
        ROS_INFO("[服务器] 收到客户端目标!");
        ROS_INFO("[服务器] 目标位置:x = %.2f, y = %.2f",
                 goal->target_pose.pose.position.x,
                 goal->target_pose.pose.position.y);

        // 模拟执行导航任务(实际项目中替换为 OMPL 路径规划 + DWB 速度控制)
        ros::Duration(2).sleep(); // 休眠 2 秒,模拟任务执行

        // 向客户端反馈任务执行成功
        as_.setSucceeded();
        ROS_INFO("[服务器] 目标执行完成!");
    }

public:
    /**
     * 构造函数:用 lambda 替代 boost::bind 绑定回调
     * 核心:[this] 捕获对象指针,(goal) 接收 ROS 自动传入的参数
     */
    OMPLDWBNavigator(const std::string& name) 
        : as_(nh_, name, 
              // lambda 替代 boost::bind(&OMPLDWBNavigator::executeCB, this, _1)
              [this](const move_base_msgs::MoveBaseGoalConstPtr& goal) {
                  this->executeCB(goal); // 手动转发目标参数
              }, 
              false), // false:不自动启动服务器,手动调用 as_.start()
          action_name_(name) {
        as_.start(); // 启动 Action 服务器
        ROS_INFO("[服务器] 已启动,等待客户端发送目标...");
    }

    ~OMPLDWBNavigator() {} // 析构函数(空实现,实际项目可加资源释放)
};

// ---------------------- 测试客户端(发送目标给服务器)----------------------
void sendGoalToServer() {
    // 1. 创建 Action 客户端(参数1:Action 名称,需与服务器一致;参数2:true=自动自旋)
    actionlib::SimpleActionClient<move_base_msgs::MoveBaseAction> ac("ompl_dwb_navigator", true);

    ROS_INFO("[客户端] 等待服务器启动...");
    ac.waitForServer(); // 阻塞等待服务器上线
    ROS_INFO("[客户端] 服务器已连接,准备发送目标!");

    // 2. 构造目标数据(可修改 x、y 坐标测试)
    move_base_msgs::MoveBaseGoal goal;
    goal.target_pose.header.frame_id = "base_link"; // 坐标系(简化使用 base_link)
    goal.target_pose.header.stamp = ros::Time::now(); // 时间戳
    goal.target_pose.pose.position.x = 1.0; // 目标 x 坐标
    goal.target_pose.pose.position.y = 2.0; // 目标 y 坐标
    goal.target_pose.pose.orientation.w = 1.0; // 姿态(无旋转,w=1 表示姿态正常)

    // 3. 发送目标给服务器
    ac.sendGoal(goal);
    ROS_INFO("[客户端] 目标已发送:x = 1.0, y = 2.0");

    // 4. 等待服务器执行完成,并判断结果
    ac.waitForResult();
    if (ac.getState() == actionlib::SimpleClientGoalState::SUCCEEDED) {
        ROS_INFO("[客户端] 服务器执行目标成功!");
    } else {
        ROS_WARN("[客户端] 服务器执行目标失败!");
    }
}

// ---------------------- 主函数(启动节点)----------------------
int main(int argc, char** argv) {
    // 初始化 ROS 节点(节点名称:ompl_dwb_navigator_demo)
    ros::init(argc, argv, "ompl_dwb_navigator_demo");

    // 1. 创建服务器对象(启动 Action 服务器)
    OMPLDWBNavigator server("ompl_dwb_navigator"); // Action 名称统一

    // 2. 启动客户端并发送目标(测试用,实际项目可拆分到单独节点)
    sendGoalToServer();

    ros::spin(); // 保持节点运行(循环处理回调)
    return 0;
}

二、编译配置文件(CMakeLists.txt)

放在工作空间 src 目录下(与 .cpp 文件同目录)

cmake 复制代码
cmake_minimum_required(VERSION 3.0.2)
project(ompl_dwb_navigator_demo) # 项目名称,需与包名一致

# 查找依赖包(ROS 核心依赖)
find_package(catkin REQUIRED COMPONENTS
  roscpp
  actionlib
  move_base_msgs
)

# 声明包的依赖(供其他包调用)
catkin_package(
  CATKIN_DEPENDS roscpp actionlib move_base_msgs
)

# 包含头文件目录
include_directories(
  ${catkin_INCLUDE_DIRS}
)

# 编译节点(可执行文件名称:ompl_dwb_navigator_demo)
add_executable(ompl_dwb_navigator_demo src/ompl_dwb_navigator_demo.cpp)

# 链接依赖库
target_link_libraries(ompl_dwb_navigator_demo
  ${catkin_LIBRARIES}
)

三、运行步骤(详细可复制)

bash 复制代码
# 1. 启动 ROS 核心(新终端)
roscore

# 2. 编译代码(在工作空间根目录,新终端)
catkin_make
source devel/setup.bash

# 3. 运行节点(新终端)
rosrun ompl_dwb_navigator_demo ompl_dwb_navigator_demo

四、预期运行日志(参数传递全过程)

复制代码
[ INFO] [1712345678.901234]: [服务器] 已启动,等待客户端发送目标...
[ INFO] [1712345678.901567]: [客户端] 等待服务器启动...
[ INFO] [1712345678.901789]: [客户端] 服务器已连接,准备发送目标!
[ INFO] [1712345678.902012]: [客户端] 目标已发送:x = 1.0, y = 2.0
[ INFO] [1712345678.902234]: [服务器] 收到客户端目标!
[ INFO] [1712345678.902456]: [服务器] 目标位置:x = 1.00, y = 2.00
[ INFO] [1712345680.902678]: [服务器] 目标执行完成!
[ INFO] [1712345680.902901]: [客户端] 服务器执行目标成功!

五、核心知识点(适配文章讲解)

1. lambda 替代 boost::bind 的核心逻辑(对应原问题)

原写法(boost::bind) Lambda 等价写法 说明
boost::bind(&OMPLDWBNavigator::executeCB, this, _1) [this](const auto& goal) { this->executeCB(goal); } 完全等价,无功能差异
this 捕获列表 [this] 获取当前对象指针,用于调用成员函数
_1 lambda 参数 goal 接收 ROS 框架自动传入的目标参数

💡 使用 lambda 更现代、更直观,且避免了 boost::bind 的语法复杂性和编译警告。


2. 参数传递核心疑问解答(读者常见问题)

  • 「参数什么时候传?」

    当客户端调用 ac.sendGoal(goal) 后,ROS ActionLib 框架会自动触发服务器端的 executeCB 回调函数。

  • 「参数从哪来?」

    来自客户端构造的 move_base_msgs::MoveBaseGoal 对象,如 x=1.0, y=2.0

  • 「参数在哪接?」

    在服务器的 executeCB 函数中通过 const move_base_msgs::MoveBaseGoalConstPtr& goal 参数接收,可直接访问其字段。


3. 避坑提醒(文章重点标注)

依赖包无需额外安装move_base_msgs 是 ROS 自带导航包,直接编译即可。

⚠️ lambda 不可少 [this] :否则无法调用类成员函数 executeCB,编译报错。

Action 名称必须一致 :服务器与客户端都使用 "ompl_dwb_navigator",否则无法通信。


🔧 进阶建议:加入 OMPL 路径规划极简代码片段(可用于后续文章扩展)

如果你想让这个示例更贴近真实导航场景,可以在 executeCB 中加入一个极简 OMPL 路径规划模拟逻辑,如下所示:

cpp 复制代码
// 在 executeCB 中添加路径规划模拟(伪 OMPL)
ROS_INFO("[服务器] 开始路径规划(OMPL 模拟)...");

// 简化:假设我们有一个"地图"边界
double start_x = 0.0, start_y = 0.0;
double goal_x = goal->target_pose.pose.position.x;
double goal_y = goal->target_pose.pose.position.y;

std::vector<std::pair<double, double>> path;
// 模拟生成一条直线路径(每 0.5 米一个点)
for (double t = 0; t <= 1.0; t += 0.5) {
    double x = start_x + t * (goal_x - start_x);
    double y = start_y + t * (goal_y - start_y);
    path.push_back({x, y});
    ROS_INFO("[OMPL] 路径点: (%.2f, %.2f)", x, y);
}

ROS_INFO("[服务器] 路径规划完成,共 %lu 个路径点。", path.size());

📌 提示:真实 OMPL 集成需结合 ompl 库、状态空间定义、碰撞检测(如 costmap_2d),但此模拟可用于教学演示路径生成过程。

你可以在后续文章中逐步引入 nav_core 插件接口、global_planner 封装或 ompl_ros 集成,层层递进。


✅ CSDN 排版说明(作者自用参考)

  1. 所有代码块均标注语言类型(cpp / cmake / bash / plaintext),CSDN 可自动高亮;
  2. 标题使用 ### 分级,支持右侧文章目录生成;
  3. 关键知识点使用表格、加粗、引用块突出显示,提升可读性;
  4. 注释详尽,段落清晰,读者可一键复制运行;
  5. 最后添加"进阶建议",为系列文章留出扩展空间。

需要我帮你把这个示例拆成两个独立节点(服务端 + 客户端分离)、或者封装成 ROS Package 模板(含 package.xml)吗?我可以继续补充,让你的文章更完整~ 😊

相关推荐
碧波bibo39 分钟前
【算法突破】【C++】 第一篇 数组
c++·算法
free-elcmacom41 分钟前
用Python玩转GAN:让AI学会“造假”的艺术
人工智能·python·机器学习
计算机毕设匠心工作室1 小时前
【python大数据毕设实战】全国健康老龄化数据分析系统、Hadoop、计算机毕业设计、包括数据爬取、数据分析、数据可视化、机器学习
后端·python
45288655上山打老虎1 小时前
【智能指针】
开发语言·c++·算法
Dxy12393102161 小时前
Python的PIL对象crop函数详解
开发语言·python
水饺编程1 小时前
第3章,[标签 Win32] :WM_CREATE 消息的产生
c语言·c++·windows·visual studio
翔云 OCR API1 小时前
护照NFC识读鉴伪接口集成-让身份核验更加智能与高效
开发语言·人工智能·python·计算机视觉·ocr
三好kiii1 小时前
海康威视热成像摄像头温度矩阵提取实战:ISAPI + Python 实现无 SDK 读取
图像处理·python