【ROS入门】使用 ROS 动作(Action)机制实现目标请求、进度与完成结果的反馈

文章结构

任务要求

使用 ROS 动作(Action)机制实现目标请求、进度与完成结果的反馈:

  • 创建服务端,注册 Action
  • 客户端发送action 请求检测 40个零件
  • 服务端接收后,每隔 1s 检测一个零件 (每检测一个打印1次),并实时给客户端返回检测进度(客户端打印进度百分比),并在检测完毕时告知客户端目标完成。

话题模型

action 是一种类似于服务通信的实现,其实现模型也包含请求和响应,但是不同的是,在请求和响应的过程中,服务端还可以连续的反馈当前任务进度,客户端可以接收连续反馈并且还可以取消任务。

action结构图解:

action结构图解:

实现步骤

定义action文件

action、srv、msg 文件内的可用数据类型一致,且三者实现流程类似

按照固定格式创建action文件

首先新建功能包,并导入依赖: roscpp rospy std_msgs actionlib actionlib_msgs

然后功能包下新建 action 目录,新增 check.action文件。

action 文件内容组成分为三部分:请求目标值、最终响应结果、连续反馈,三者之间使用---分割

内容如下:

编辑配置文件

CMakeLists.txt

xml 复制代码
find_package
(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  actionlib
  actionlib_msgs
)
xml 复制代码
add_action_files(
  FILES
  check.action
)
xml 复制代码
generate_messages(
  DEPENDENCIES
  std_msgs
  actionlib_msgs
)
xml 复制代码
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES demo04_action
 CATKIN_DEPENDS roscpp rospy std_msgs actionlib actionlib_msgs
#  DEPENDS system_lib
)

编译生成中间文件

编译后会生成一些中间文件。

msg文件(.../工作空间/devel/share/包名/msg/xxx.msg):

C++ 调用的文件(.../工作空间/devel/include/包名/xxx.h):

Python 调用的文件(.../工作空间/devel/lib/python3/dist-packages/包名/msg/xxx.py):

编写服务端和客户端

vscode配置

cpp 复制代码
{
  "configurations": [
    {
      "browse": {
        "databaseFilename": "${default}",
        "limitSymbolsToIncludedHeaders": false
      },
      "includePath": [
        "/opt/ros/noetic/include/**",
        "/home/chenyikeng/demo01_ws/src/helloworld/include/**",
        "/home/chenyikeng/ROS_Topic_Demo/src/topic_demo/include/**",
        "/usr/include/**",
        "/home/chenyikeng/ROS_Topic_Demo/devel/include"
      ],
      "name": "ROS",
      "intelliSenseMode": "gcc-x64",
      "compilerPath": "/usr/bin/gcc",
      "cStandard": "gnu11",
      "cppStandard": "c++14"
    }
  ],
  "version": 4
}

服务端

cpp 复制代码
/*
流程:
    1.包含头文件;
    2.初始化ROS节点;
    3.创建NodeHandle;
    4.创建action服务对象;
    5.处理请求,产生反馈与响应;
      a.获取并解析提交的目标值
      b.产生连续反馈
      c.最终结果响应
    6.spin().
*/


#include "ros/ros.h"
#include "actionlib/server/simple_action_server.h"  //actionlib里头服务端的库
#include "action_demo/checkAction.h"    //自定义action文件时编译生成的库

// 使用模板创建action服务对象并定义
typedef actionlib::SimpleActionServer<action_demo::checkAction> Server;


//请求处理(a.解析提交的目标值;b.产生连续反馈;c.最终结果响应) --- 回调函数
void callBack(const action_demo::checkGoalConstPtr &goalPtr, Server*  server)
{
    // a.获取并解析提交的目标值
    int goal_num = goalPtr -> num;
    ROS_INFO("客户端提交的目标值是: %d",goal_num);

    // b.产生连续反馈
    ros::Rate rate(1); //1Hz
    int result = 0;
    for(int i = 1; i<= goal_num; i++)
    {
        // 累加
        result ++;
        // 休眠
        rate.sleep();
        //产生(封装)连续反馈
        //创建feedback对象
        action_demo::checkFeedback fb;
        fb.progress_bar = i / (double)goal_num;
        //发送
        server->publishFeedback(fb);
        //打印
        ROS_INFO("检测%d个零件", result);
    }

    // c.最终结果响应
    // 创建result对象
    ROS_INFO("检测完成");
    action_demo::checkResult r;
    r.result = result;
    server -> setSucceeded(r);
}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");

    ros::init(argc,argv,"check_server");

    ros::NodeHandle n;

    // 创建action服务对象
    /*SimpleActionServer(ros::NodeHandle n,     #句柄
                        std::string name,       #话题名称
                        boost::function<void (const action_demo::checkGoalConstPtr &)> execute_callback,    
                        #回调函数,可以解析传入的目标值,产生连续反馈,以及响应最终结果
                        bool auto_start)        #布尔值,是否自动启动
    */
    Server server(n,"check",boost::bind(&callBack,_1,&server),false);

    //如果auto_start为false,那么需要手动调用该函数启动服务
    server.start();     
    ROS_INFO("服务启动......");

    //spin()回旋
    ros::spin();
    return 0;
}

客户端

cpp 复制代码
/*
流程:
    1.包含头文件;
    2.初始化ROS节点;
    3.创建NodeHandle;
    4.创建action客户端对象;
    5.发送目标,处理反馈以及最终结果;
      a.连接建立 --- 回调函数
      b.处理连续反馈 --- 回调函数
      c.处理最终响应 --- 回调函数
    6.spin().
*/
#include "ros/ros.h"
#include "actionlib/client/simple_action_client.h"  //actionlib里头服务端的库
#include "action_demo/checkAction.h"    //自定义action文件时编译生成的库


// 创建action客户端对象
typedef actionlib::SimpleActionClient<action_demo::checkAction> Client;

// 服务端返回最终响应结果时候触发的回调
void done_cb(const actionlib::SimpleClientGoalState &state, const action_demo::checkResultConstPtr &result)
{
    // 判断响应状态是否成功
    if (state.state_ == state.SUCCEEDED)
        ROS_INFO("检测完成");
    else 
        ROS_INFO("任务失败!");
}

// 连接被激活的时候触发的回调
void active_cb()
{
    ROS_INFO("服务已经被激活....");
}

// 连续反馈时的回调函数
void feedback_cb(const action_demo::checkFeedbackConstPtr &feedback)
{   
    float progress_bar_percentage = feedback->progress_bar*100;
    if(progress_bar_percentage-(int)progress_bar_percentage==0)
        ROS_INFO("当前进度:%d%%",(int)progress_bar_percentage);
    else
        ROS_INFO("当前进度:%.1f%%",progress_bar_percentage);
}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");

    ros::init(argc,argv,"check_client");

    ros::NodeHandle n;

    // 创建action客户端对象;
    // SimpleActionClient(ros::NodeHandle & n, const std::string & name, bool spin_thread = true)
    // actionlib::SimpleActionClient<demo01_action::AddIntsAction> client(nh,"addInts");
    Client client(n,"check",true);

    //等待服务启动
    ROS_INFO("等待服务器启动......");
    client.waitForServer();

    // 发送目标,处理反馈以及最终结果;
    /*  
        void sendGoal(const demo01_action::AddIntsGoal &goal, 
            boost::function<void (const actionlib::SimpleClientGoalState &state, const demo01_action::AddIntsResultConstPtr &result)> done_cb, 
            用于处理最终响应
            boost::function<void ()> active_cb, 
            连接被激活的时候使用的回调
            boost::function<void (const demo01_action::AddIntsFeedbackConstPtr &feedback)> feedback_cb)
            处理连续反馈时相关的回调函数
    */

    // 设置目标值:40个零件
    // 声明对象
    action_demo::checkGoal goal;
    goal.num = 40;

    // 发送目标数据
    client.sendGoal(goal,&done_cb,&active_cb,&feedback_cb);


    // spin()回旋
    ros::spin();
    return 0;
}

编译配置文件

cpp 复制代码
add_executable(check_server_c src/check_server_c.cpp)
add_executable(check_client_c src/check_client_c.cpp)
...

add_dependencies(check_server_c ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(check_client_c ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
...

target_link_libraries(check_server_c
  ${catkin_LIBRARIES}
)
target_link_libraries(check_client_c
  ${catkin_LIBRARIES}
)

执行

首先启动 roscore,然后分别启动action服务端与action客户端,最终运行结果与案例类似。

结果如下:

相关推荐
Betty’s Sweet几秒前
[C++]:IO流
c++·文件·fstream·sstream·iostream
敲上瘾14 分钟前
操作系统的理解
linux·运维·服务器·c++·大模型·操作系统·aigc
不会写代码的ys20 分钟前
【类与对象】--对象之舞,类之华章,共绘C++之美
c++
宋1381027972023 分钟前
Manus Xsens Metagloves虚拟现实手套
人工智能·机器人·vr·动作捕捉
兵哥工控23 分钟前
MFC工控项目实例三十二模拟量校正值添加修改删除
c++·mfc
长弓聊编程33 分钟前
Linux系统使用valgrind分析C++程序内存资源使用情况
linux·c++
cherub.40 分钟前
深入解析信号量:定义与环形队列生产消费模型剖析
linux·c++
禁默1 小时前
第六届机器人、智能控制与人工智能国际学术会议(RICAI 2024)
人工智能·机器人·智能控制
暮色_年华1 小时前
Modern Effective C++item 9:优先考虑别名声明而非typedef
c++
重生之我是数学王子1 小时前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt