ROS action(动作)通信详细讲解

目录

前言

动作通信

概念

原理

实现效果

案例实现

案例

分析

实操

建立功能包

配置自定义action文件

建立action文件夹

建立action文件

配置action文件内容

修改“CMakeLists.txt”配置文件

直接添加

修改注释

查看配置是否成功

C++实现

修改配置文件

建立源文件

action服务器端实现

导包

建立服务器端对象

boost::bind(&cb, _1, &server)讲解

回调函数的实现

测试

action客户端实现

创建客户端对象

发送目标请求消息

结果响应消息的回调函数实现

测试

Python实现

修改配置文件

建立源文件

​编辑

 action服务器端实现

导包

建立MyAction类

1. 无参构造

2. 成员函数实现

测试

1. 修改配置文件

 2. 运行

action客户端实现

创建动作客户端对象

静态设置目标数据

发送目标数据请求消息

回调函数实现

测试


前言

本篇博客的案例实现建立在看完了上两篇博客的基础上,会跳过一些以及讲解过的内容。上两篇篇博客涉及到的许多概念,在本文不会详细展开,可以浏览上两篇博客(不想看完上篇博客,本文提及的相关概念都会提供具体位置,方便学习)

话题通信(这篇博客比较基础)

服务通信(没有基础看这篇博客需要参考《话题通信》的博客)

动作通信

概念

动作通信的通信模式类似于服务通信,有请求响应的操作,但是,除了请求响应的实时性外,动作通信的服务器端还能连续反馈当前的任务进度(相当于进度条),因此动作通信一般是用于耗时的请求响应通信模式的任务中的;客户端也可以在服务器端完成响应前取消任务。

原理

这个的实现依赖ROS内置的action包

下面两个图都是在网上找的,侵权联系删除

我们只需要实现发送的代码和响应时执行的代码就行了

第一步:相同话题的动作客户端与动作服务器端连接

第二步:动作客户端发布带有任务的请求消息给动作服务器端

(第二步起到结束任务前,动作客户端都可以发送取消的请求消息给动作服务器端以取消任务)

第三步:动作服务器端开始响应,向动作客户端连续发送状态和当前进度,直到第四步

第四步:动作服务器端完成任务,并反馈响应结果

第五步:动作客户端处理响应结果

实现效果

案例实现

案例

使用ROS动作(Action)机制实现目标请求、进度与完成结果的反馈。
要求:编写代码实现ROS中动作请求与进度反馈:创建服务端,注册Action客户端,发送action请求检测40个零件,服务端接收后,每隔1s测一个零件(每检测一个打印一次):实时给客户端返回检测进度(客户端打印进度百分比),并在检测完毕时告知客户端目标完成。如·服务端实时打印:检测1个零件检测2个零件...检测40个零件检测完成客户端实时打印:2.5%5%...100%检测完成

分析

要实现每个1s,在服务端发布一次当前进度并打印完成检查的零件个数,在客户端打印当前进度,也要打印响应消息的结果

实操

建立工作空间和修改配置文件“tasks.json”这里就省略了,由于小编将在同一个工作空间目录下,之前配置好了“tasks.json”,所以这里不需要再配置了。

建立工作空间和修改配置文件的导航链接

建立功能包

由于和之前的不太一样,这里再详细的讲一下

右键在工作空间目录下的"src"目录,选择“Create Catkin Package”

在编辑框中输入包名,如小编的"test3_action",按下回车键确认

不同在这里,要多添加依赖 actionlib 和 actionlib_msgs

roscpp rospy std_msgs actionlib actionlib_msgs

建立完毕

配置自定义action文件

建立action文件夹

建立action文件

在action目录下新建文件

建立一个以.action为后缀的文件

配置action文件内容

将下面的内容保存在我们建立的action文件中

# 目标数据变量
int32 goal
---
# 最终响应变量
int32 result
---
# 连续反馈变量
float64 progress_bar

修改“CMakeLists.txt”配置文件

可以直接添加在后面或者修改注释

直接添加

经过测试,可以同时存在两个catkin_package()

add_action_files(
  FILES
  check.action
)

generate_messages(
  DEPENDENCIES
  acionlib_msgs
  std_msgs
)
catkin_package(
 CATKIN_DEPENDS actionlib actionlib_msgs roscpp rospy std_msgs
)
修改注释

点击“CMakeLists.txt”文件,找到add_action_files()和generate_messages()

取消add_action_files的注释,将FILES下面的.action文件改成我们建好的check.action文件

取消generate_message注释,由于两个依赖(action_msgs和std_msgs)都需要,所以两个依赖要添加(位置无所谓)

取消括号中的第三行就可以了

查看配置是否成功

按“Shift+Strl+b”进行编译

点击 devel > share > test3_action > msg,可以看到生成了一堆的"check..."消息文件

C++方面编译成功

点击 devel > include > test3_action,可以看到很多"check..."头文件

Python方面编译成功

点击 devel > lib > test3_action > msg,可以看到一堆python文件

C++实现

修改配置文件

在同一个工作空间目录下,配置过了,这里省略

直接导航到配置文件

建立源文件

action服务器端实现
#include "ros/ros.h"
#include "actionlib/server/simple_action_server.h"
#include "test3_action/checkAction.h"

// 重定义:范型为test3_action::checkAction的动作服务器类
typedef actionlib::SimpleActionServer<test3_action::checkAction> Server;

// 回调函数的实现 参数1:目标数据变量指针常量 参数2:服务器对象指针
void cb (const test3_action::checkGoalConstPtr &goal_ptr, Server* server)
{
    // 获取目标数据
    int goal = goal_ptr->goal;
    // 设置频率
    ros::Rate rate(1);
    // 实时发布当前状态
    int result = 0;
    for(result = 1; result <= goal; result++)
    {
        rate.sleep();
        // 建立反馈数据的响应消息对象
        test3_action::checkFeedback fb;
        // 计算当前进度
        fb.progress_bar = (double)result/goal * 100;
        // 发送当前状态
        server->publishFeedback(fb);
    }
    // 多加了1,要减回来
    result--;
    //休眠,防止客户端处理最终响应消息的回调函数提前执行(对客户端实现时发现的问题进行优化)
    rate.sleep();
    // 建立结果数据的响应消息对象
    test3_action::checkResult r;
    // 这里要随着实现的目标变通
    r.result = (result == goal);
    // 发送结果数据
    server->setSucceeded(r);
    // 打印
    std::cout << "检测" << result << "个零件" << std::endl;
}

int main(int argc, char *argv[])
{
    // 初始化ROS节点
    ros::init(argc, argv, "server_action");
    // 创建节点句柄
    ros::NodeHandle nh;
    // 创建动作服务器对象
    Server server(nh, "Check", boost::bind(&cb, _1, &server), false);
    // 如果server实例化时设置的是不自动启动,想启动服务器需要执行start()函数
    server.start();
    // spin()回旋
    ros::spin();

    return 0;
}

没有详细展开讲述的内容在这里讲过了

导包
#include "actionlib/server/simple_action_server.h"

 因为是action通信,所以需要导入action相关的头文件使用action的功能

建立服务器端对象
actionlib::SimpleActionServer<test3_action::checkAction> server(nh, "Check", boost::bind(&cb, _1, &server), false);

由于范型为test3_action::checkAction的动作服务器类会被多次使用,这里将其整个重定义为Server类,方便使用。

因为使用到了checkAction(包含了整个action文件),且这个泛型是我们自己定义的,需要引头文件

#include "test3_action/checkAction.h"

实例化时,这个类有多个重载(小编只会使用这种重载,其他的都不知道是建立对象还是什么用),以小编的代码为例,一共四个参数

参数1:节点句柄

参数2:话题名称

参数3:可调用的对象(下面详细讲解)

参数4:布尔值(决定服务器是否自动启动),如果不(false),如果想启动服务器,就要在后面的代码中执行start()函数进行服务器端启动

server.start();
boost::bind(&cb, _1, &server)讲解

boost中封装了一个bind()函数,该函数可以将其他函数或类中的成员方法绑定到特定的参数中,并返回一个可调用对象(是用于ation通信中的发送状态、当前进度、结果数据的消息的对象),具体需要

主参数1:函数指针(这里需要的是:实现的用于处理消息的回调函数)

主参数2:多个参数,函数需要多少个参数,就要有多少个参数,不通过这个函数传入需要占位

在boost::bind(&cb, _1, &server)中,_1是占位符

回调函数的实现
void cb (const test3_action::checkGoalConstPtr &goal_ptr, Server* server)
{
}

回调函数中

参数1:指针常量,动作客户端会发送目标数据的请求消息,由这个参数接收

参数2:服务器端对象指针,通过绑定可以将建立好的服务器端对象指针传入

由于回调函数的实现内容在上面的代码注释中讲解的比较详细,没什么需要补充的

测试

修改配置文件

直接添加在后面或者修改

add_executable(server03 src/server03.cpp)
add_dependencies(server03 ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(server03
  ${catkin_LIBRARIES}
)

编译(按下“Shift+Ctrl+b”)

打开终端“Ctrl+Alt+t”或右键桌面选择“在终端中打开”,进入工作空间目录

cd demo02_ws

分屏或多打开几个终端窗口,分别在不同窗口

启动ROS核心

roscore

 刷新环境变量,这里需要用到四五个

source ./devel/setup.bash

启动action服务器端(test3_action是功能包名,server03是映射名称)

rosrun test3_action server03

先查看话题是否有了

rostopic list

 都有了

使用这些话题,先运行用来打印当前进度和结果的ROS内置命令

打印当前进度

rostopic echo /Check/feedback

 打印结果

rostopic echo /Check/result

再运行模拟客户端的命令

先输入

rostopic pub /C

这里的"C"是Check的首字母,根据你命名的话题改变,在没有重名的请况下可以让系统自动补齐话题

再输入"g",这个是action包定的,应该是一致的名称都是"xxx.../goal"

然后一直按tab键补齐,补到下图为止

修改goal后面的数据(这是可编辑的命令,还需要按回车键确认的),例如40

 按下回车键

等到进度变成100%时,打印的结果为True,成功了

action客户端实现
#include "ros/ros.h"
#include "actionlib/client/simple_action_client.h"
#include "test3_action/checkAction.h"

// 结果响应消息的回调函数实现
void done_cb(const actionlib::SimpleClientGoalState &state, const test3_action::checkResultConstPtr &result)
{
    // 判断响应状态
    if(state.state_ == state.SUCCEEDED)
        // 判断是否检测完成
        if(result->result)
            std::cout << "检测完成" << std::endl;
        else
            std::cout << "未检测完成" << std::endl;
    else
        std::cout << "响应失败" << std::endl;
    
    // ***************** 无效代码 ******************
    // ros::Rate rate(1); 
    // rate.sleep();
    // if(/*result->result*/state.state_ == state.SUCCEEDED)
    //     std::cout << "检测完成" << std::endl;
    // else
    //     std::cout << "响应失败" << std::endl;
    //*********************************************
}

// 连接激活的回调函数实现
void active_cb()
{
    std::cout << "开始检测" << std::endl;
}

// 连续反馈响应消息的回调函数实现
void feedback_cb(const test3_action::checkFeedbackConstPtr &feedback)
{
    std::cout << "当前进度" << feedback->progress_bar << "%" <<std::endl;
    // ROS_INFO("当前进度:%2.1f%%", feedback->progress_bar);
}

int main(int argc, char* argv[])
{
    // 使用ROS_INFO时需要使用以解决中文乱码问题
    setlocale(LC_ALL, "");
    // 初始化ROS节点
    ros::init(argc, argv, "client_action");
    // 建立节点句柄
    ros::NodeHandle nh;
    // 创建动作客户端对象
    actionlib::SimpleActionClient<test3_action::checkAction> client(nh, "Check");
    // 挂起等待服务器启动
    client.waitForServer();
    // 静态实现目标数据设置
    test3_action::checkGoal goal; // 创建目标数据对象
    goal.goal = 40;
    //发送目标的请求消息
    client.sendGoal(goal, &done_cb, &active_cb, &feedback_cb);
    // spin()回旋
    ros::spin();

    return 0;
} 

没有展开的看这里

创建客户端对象
actionlib::SimpleActionClient<test3_action::checkAction> client(nh, "Check");

和创建服务器端对象一样的方式,只是不需要可调用对象(由于只用到了一次,没有重定义)

参数1:节点句柄

参数2:话题名称

参数3:布尔值(用于决定服务器是否自动启动),由于具有默认值(True),所以可以不写参数

发送目标请求消息
client.sendGoal(goal, &done_cb, &active_cb, &feedback_cb);

参数1:目标数据对象

参数2:用于处理结果响应消息的回调函数,最后调用

参数3:用于连接的回调函数,最先调用

参数4:用于处理连续反馈响应消息的回调函数

结果响应消息的回调函数实现
// 判断响应状态
if(state.state_ == state.SUCCEEDED)
    // 判断是否检测完成
    if(result->result)
        std::cout << "检测完成" << std::endl;
    else
        std::cout << "未检测完成" << std::endl;
else
    std::cout << "响应失败" << std::endl;

state中有封装一个成员state_,用于存储真正的响应状态

state中封装了另一个成员SUCCEEDED,是记录了响应成功的状态

将两个成员对比,就能判断响应的状态

其实(result->result)和(state.state_ == state.SUCCEEDED)都是判断响应状态的,但是小编在ubuntu22.04中,无论怎么改代码,让“结果响应消息的回调函数”休眠1秒,都会出现“客户端最终响应执行结束早于连续反馈的回调函数”的情况,就是“检查完成”比“当前进度:100%”早打印出来。

解决:这是服务器端的问题,在循环执行后休眠,再发送最终响应消息

在ubunt18.04中,任意使用(result->result)和(state.state_ == state.SUCCEEDED)判断响应状态(就是小编框起来的“无效代码”),都没有这种情况。

测试

修改配置文件

add_executable(client03 src/client03.cpp)
add_dependencies(client03 ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(client03
  ${catkin_LIBRARIES}
)

编译("Shift+Ctrl+b"),打开ROS核心,刷新环境变量,运行服务器端

运行客户端

rosrun test3_action client03

ubuntu22.04下的运行结果(没优化)

ubuntu22.04进行优化的运行结果

ubuntu18.04下的运行结果

Python实现

修改配置文件

 在同一个工作空间目录下,配置过了,这里省略

直接导航到配置文件

建立源文件

不会点这里

添加可执行权限教程

 action服务器端实现
#! /usr/bin/env python
#-*-coding: UTF-8 -*-

import rospy
import actionlib
from test3_action.msg import *

# 类实现
class MyAction:

    # 无参构造函数
    def __init__(self):
        self.server = actionlib.SimpleActionServer("Check", checkAction, self.cb, False)
        self.server.start()
        # rospy.loginfo("服务器端启动......") # 调试使用
    
    # 成员函数实现(相当于调用此行为与 action server 交互)
    def cb(self, goal):
        # 储存目标数据
        goal_goal = goal.goal
        # rospy.loginfo(goal_goal) # 调试使用
        # 设置频率
        rate = rospy.Rate(1)
        # 实现实时发布当前进度
        result = 0
        for i in range(1, goal_goal+1):
            result = i
            rate.sleep() # 在打印前面是为了让客户端和服务器端打印进度同步
            rospy.loginfo("检测%d个零件", result)
            # 建立连续反馈响应消息对象
            fb = checkFeedback()
            # 设置进度
            fb.progress_bar = i / goal_goal * 100
            # rospy.loginfo(fb.progress_bar) # 调试使用
            # 发布连续反馈的响应消息
            self.server.publish_feedback(fb)
        
        rate.sleep() # 休眠防止客户端的处理连续反馈的回调函数提前结束
        # 建立最终结果的响应消息对象
        r = checkResult()
        # 设置最终结响应的数据
        r.result = result == goal_goal # 不能理解这样生成的布尔类型数据,没有括号
        # 发布最终响应数据
        self.server.set_succeeded(r)


# 创建入口
if __name__ == "__main__":
    # 初始化ROS节点
    rospy.init_node("server_p_action")
    # 使用无参构造函数实例化
    myAction = MyAction()
    # spin()回旋
    rospy.spin()

没有详细展开讲述的内容在这里讲过了

这个服务器端的实现和以前的不太一样,但实现的本质方法都是实例化对象,并使用无参构造的方式调用回调函数,进行请求消息的处理和响应

导包
import actionlib

 因为是action通信,所以需要导入action相关的包使用action的功能

from test3_action.msg import *

在导入这个包时, 会遇到报错,解决

靠近红色的波浪线,会跳出提示,点击“快速修复...”

 点击“将"./devel/lib/python3/dist-packages"添加到extraPaths”中

看到选中的这一个路径被添加到了analysis的额外路径中,下面的"files.associations"不知道怎么多出来的

建立MyAction类
1. 无参构造
self.server = actionlib.SimpleActionServer("Check", checkAction, self.cb, False)

这里需要使用SimpleActionServer类来实例化对象(由于只用到了一次)

参数1:话题名称

参数2:泛型类型,需要导包,其中,import* 是指导入test3_action.msg中的所有包,和添加路径时的 "/** " 类似

from test3_action.msg import *

参数3:类中的行为(成员函数)

参数4:服务器是否自动启动

如果传True,就没什么可提的,但是传False后,想在想要使用时打开需要在后面的代码添加

server.start()
2. 成员函数实现
def cb(self, goal):

由于action服务器对象是类中的成员,所以可以直接调用,只需要一个参数目标数据的请求消息变量。

其他的基本是固定的实现对应固定的函数,而且在上面的代码中注释得也比较详细,就省略了 

测试
1. 修改配置文件

还是可以选中直接添加到文件的后面,或者跟着小编改

catkin_install_python(PROGRAMS
  scripts/server04.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

 2. 运行

编译(按下“Shift+Ctrl+b”)

打开终端“Ctrl+Alt+t”或右键桌面选择“在终端中打开”,进入工作空间目录

cd demo02_ws

分屏或多打开几个终端窗口,分别在不同窗口

启动ROS核心

roscore

 刷新环境变量,这里需要用到四五个

source ./devel/setup.bash

 运行服务器端(test3_action是功能包,server04.py是源文件名)

rosrun test3_action server04.py

 查看话题

rostopic list

运行用来打印当前进度和结果的ROS内置命令

再次刷新环境变量,输入一半的命令行

rostopic pub /C

 " C "是自己话题的首字母

按一下"tab"键(补齐,也可以输)

输入"g",下一个要补齐的是goal

大概按三下"tab"键,补齐到如下图内容为止

将goal的数据改成40,按下回车键

下图为开始运行的截图,由于连续反馈是

成功了

action客户端实现
#! /usr/bin/env python
#-*-coding: UTF-8 -*-

import rospy
import actionlib
from test3_action.msg import *

# 处理最终响应数据的回调函数
def done_cb(states, result):
    # 判断响应是否成功 (states == actionlib.GoalStates.SUCCEEDED 和 result 的布尔值是一样的,只需要判断一个)
    if states == actionlib.GoalStatus.SUCCEEDED:
        if result:
            rospy.loginfo("检测完成")
        else :
            rospy.loginfo("检测未完成")
    else :
        rospy.loginfo("响应失败")

# 激活时启动的回调函数
def active_cb():
    rospy.loginfo("开始检测")

# 处理连续反馈消息的回调函数
def feedback_cb(feedback):
    rospy.loginfo("%2.1f%%", feedback.progress_bar)

# 建立入口
if __name__ == "__main__":
    # 初始化ROS节点
    rospy.init_node("client_p_action")
    # 创建动作客户端对象
    client = actionlib.SimpleActionClient("Check", checkAction)
    # 等待服务器启动
    client.wait_for_server()
    # 静态设置目标数据
    goal = checkGoal()
    goal.goal = 40
    # 发送目标数据请求消息并处理响应
    client.send_goal(goal, done_cb, active_cb, feedback_cb) 
    # spin()回旋
    rospy.spin()
创建动作客户端对象

使用SimpleActionClient()函数,

参数1:话题名称

参数2:泛型

静态设置目标数据

动态的设置可以看这个

创建目标数据请求消息对象,调用里面封装的目标数据变量,进行设置

发送目标数据请求消息
client.send_goal(goal, done_cb, active_cb, feedback_cb) 

参数1:目标数据请求消息对象

参数2:处理最终响应数据的回调函数,最后调用并结束

参数3:激活时启动的回调函数,最先调用并结束

参数4:处理连续反馈消息的回调函数

回调函数实现

由于大部分实现比较简短且注释详细,就不再讲解了

在实现处理最终响应数据的回调函数时,由于最终的响应消息会将响应的状态和响应的结果按这个顺序发送,所以用states和result的参数顺序接收

在实现处理连续反馈响应消息的回调函数时,因为只有连续反馈响应消息发送,所以用feedback命名接收的变量,容易看懂这个变量怎么用。

测试

先进行编译("Shift+Ctrl+b")

只需要打开ROS核心,并刷新环境变量,再运行服务器端和客户端,就能得到下图

# 如果不在工作空间目录下还要运行,以demo02_ws为例
cd demo02_ws
# 启动ROS核心
roscore
# 刷新环境变量
source ./devel/setup.bash
# 运行服务器端 test3_action是功能包,server04.py是python源文件
rosrun test3_action server04.py
# 运行客户端 test3_action是功能包,client04.py是python源文件
rosrun test3_action client04.py

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值