文章目录

前言

  ROS中常用的通信机制是话题和服务,但是很多场景下,这两种通信机制往往满足不了所有需求。
  action通信机制,是一种带有连续反馈的上层通信机制,底层基于ROS话题通信。

一、什么是action

ROS中的actionlib功能包,用于实现action的通信机制。action类似于服务通信机制,不同之处在于action带有连续反馈,可以不断的反馈任务进度,也可以在任务过程中中止运行。

二、action的工作机制

action采用客户端/服务器的工作模式,如下:

在这里插入图片描述
Action客户端与Action服务端之间通过actionlib定义的action protocol进行通信,这种协议基于ROS的消息机制实现。
客户端向服务端发布任务目标以及在必要的时候取消任务,服务端会向客户端发布当前状态、实时反馈和任务执行的结果。如图:
在这里插入图片描述

三、action的定义

action通过.action文件定义,放置在功能包中的action文件夹下,格式如下:

#定义目标信息
uint32 dishwasher_id
#定义结果信息
uint32 total_dishes_cleaned
#定义周期反馈的消息
float32 percent_complete

可见,一个action的定义需要三部分:goal、result、feedback。

四、代码实现

主要实现action的客户端和服务端节点,新建名为action_tutorials功能包。

1、创建客户端

在action的定义中,描述了一个洗盘子的任务。客户端节点负责发出action请求,DoDishes_client.cpp文件内容如下:

#include <actionlib/client/simple_action_client.h>
#include "action_tutorials/DoDishesAction.h"

typedef actionlib::SimpleActionClient<action_tutorials::DoDishesAction> Client;

// 当action完成后会调用该回调函数一次
void doneCb(const actionlib::SimpleClientGoalState& state,
        const action_tutorials::DoDishesResultConstPtr& result)
{
    ROS_INFO("Yay! The dishes are now clean");
    ros::shutdown();
}

// 当action激活后会调用该回调函数一次
void activeCb()
{
    ROS_INFO("Goal just went active");
}

// 收到feedback后调用该回调函数
void feedbackCb(const action_tutorials::DoDishesFeedbackConstPtr& feedback)
{
    ROS_INFO(" percent_complete : %f ", feedback->percent_complete);
}

int main(int argc, char** argv)
{
    ros::init(argc, argv, "do_dishes_client");

    // 定义一个客户端
    Client client("do_dishes", true);

    // 等待服务器端
    ROS_INFO("Waiting for action server to start.");
    client.waitForServer();
    ROS_INFO("Action server started, sending goal.");

    // 创建一个action的goal
    action_tutorials::DoDishesGoal goal;
    goal.dishwasher_id = 1;

    // 发送action的goal给服务器端,并且设置回调函数
    client.sendGoal(goal,  &doneCb, &activeCb, &feedbackCb);

    ros::spin();

    return 0;
}

2、创建服务端

服务器节点负责完成洗盘子的任务,并且反馈洗盘子的实时进度,DoDishes_server.cpp文件内容如下:

#include <ros/ros.h>
#include <actionlib/server/simple_action_server.h>
#include "action_tutorials/DoDishesAction.h"

typedef actionlib::SimpleActionServer<action_tutorials::DoDishesAction> Server;

// 收到action的goal后调用该回调函数
void execute(const action_tutorials::DoDishesGoalConstPtr& goal, Server* as)
{
    ros::Rate r(1);
    action_tutorials::DoDishesFeedback feedback;

    ROS_INFO("Dishwasher %d is working.", goal->dishwasher_id);

    // 假设洗盘子的进度,并且按照1hz的频率发布进度feedback
    for(int i=1; i<=10; i++)
    {
        feedback.percent_complete = i * 10;
        as->publishFeedback(feedback);
        r.sleep();
    }

    // 当action完成后,向客户端返回结果
    ROS_INFO("Dishwasher %d finish working.", goal->dishwasher_id);
    as->setSucceeded();
}

int main(int argc, char** argv)
{
    ros::init(argc, argv, "do_dishes_server");
    ros::NodeHandle n;

    // 定义一个服务器
    Server server(n, "do_dishes", boost::bind(&execute, _1, &server), false);
    
    // 服务器开始运行
    server.start();

    ros::spin();

    return 0;
}

3、配置

CMakeLists.txt中添加如下规则:

find_package(catkin REQUIRED genmsg actionlib_msgs actionlib)
add_action_files(DIRECTORY action FILES DoDishes.action)
generate_messages(DEPENDENCIES actionlib_msgs)

在功能包的package.xml文件中添加如下配置:

  <build_depend>actionlib</build_depend>
  <build_depend>actionlib_msgs</build_depend>
  <build_depend>roscpp</build_depend>
  <run_depend>actionlib</run_depend>
  <run_depend>actionlib_msgs</run_depend>
  <run_depend>roscpp</run_depend>

然后编译功能包,如下:
在这里插入图片描述
在这里插入图片描述

从编译后生成的这些文件看,action确实是一种基于消息的、更加高层的通信机制。

4、运行

先启动master节点,命令如下:

roscore

启动服务端节点,命令如下:

rosrun action_tutorials DoDishes_server

再启动客户端节点,命令如下:

rosrun action_tutorials DoDishes_client 

效果如下:
在这里插入图片描述