• 【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

    find_package
    (catkin REQUIRED COMPONENTS
      roscpp
      rospy
      std_msgs
      actionlib
      actionlib_msgs
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    add_action_files(
      FILES
      check.action
    )
    
    • 1
    • 2
    • 3
    • 4
    generate_messages(
      DEPENDENCIES
      std_msgs
      actionlib_msgs
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    catkin_package(
    #  INCLUDE_DIRS include
    #  LIBRARIES demo04_action
     CATKIN_DEPENDS roscpp rospy std_msgs actionlib actionlib_msgs
    #  DEPENDS system_lib
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    编译生成中间文件

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

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

    编写服务端和客户端

    vscode配置

    {
      "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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    服务端

    /*
    流程:
        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 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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    客户端

    /*
    流程:
        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 client(nh,"addInts");
        Client client(n,"check",true);
    
        //等待服务启动
        ROS_INFO("等待服务器启动……");
        client.waitForServer();
    
        // 发送目标,处理反馈以及最终结果;
        /*  
            void sendGoal(const demo01_action::AddIntsGoal &goal, 
                boost::function done_cb, 
                用于处理最终响应
                boost::function active_cb, 
                连接被激活的时候使用的回调
                boost::function feedback_cb)
                处理连续反馈时相关的回调函数
        */
    
        // 设置目标值:40个零件
        // 声明对象
        action_demo::checkGoal goal;
        goal.num = 40;
    
        // 发送目标数据
        client.sendGoal(goal,&done_cb,&active_cb,&feedback_cb);
    
    
        // spin()回旋
        ros::spin();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    编译配置文件

    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}
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行

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

    结果如下:

    在这里插入图片描述

  • 相关阅读:
    arm64架构 统信UOS搭建PXE无盘启动Linux系统(麒麟桌面为例)
    入门JavaWeb之 Response 验证码和重定向
    【Excel小技巧】如何为Sheet特定区域/单元格设置“密码“/“不可编辑“
    数据库管理-第109期 19c OCM考后感(20231015)
    汽车电子专业知识篇(六十四)-车载毫米波雷达基本概念
    推荐计划里何时使用经常性与一次性推荐奖励?
    Day05
    Java Websocket 02: 原生模式通过 Websocket 传输文件
    ResNet-RS架构复现--CVPR2021
    怎么样学习pmp知识?
  • 原文地址:https://blog.csdn.net/PuddleRubbish/article/details/133490582