actionlib

actionlib

前言

  • Actionlib是ROS中一个很重要的库,类似service通信机制,actionlib也是一种请求响应机制的通信方式,actionlib主要弥补了service通信的一个不足,就是当机器人执行一个长时间的任务时,假如利用service通信方式,那么publisher会很长时间接受不到反馈的reply,致使通信受阻。当service通信不能很好的完成任务时候,actionlib则可以比较适合实现长时间的通信过程,actionlib通信过程可以随时被查看过程进度,也可以终止请求,这样的一个特性,使得它在一些特别的机制中拥有很高的效率

简介

  • 那么什么是 action?
    • 一种问答通信机制
    • 带有连续的反馈
    • 可以在任务过程中止运行
    • 基于ROS的消息机制实现
    • 多对多的action-server与action-client的交互机制

客户端-服务器交互

  • actionlib 使用 client-server工作模式,ActionClient 和 ActionServer 通过 "ROS Action Protocol" 进行通信,"ROS Action Protocol" 以 ROS 消息方式进行传输。此外 ActionClient 和 ActionServer 为用户提供了一些简单的接口,用户使用这些接口可以完成 goal 请求(client-side)和 goal 执行(server-side)

client_server_interaction.png

  • ActionClient 和 ActionServer 之间使用action protocol通信,action protocol 就是预定义的一组 ROS message,这些 message 被放到 ROS topic 上在 ActionClient 和 ActionServer 之间进行传输实现二者的沟通

action_interface.png

ROS Message:

  • goal:用于向服务器发送任务目标
  • cancel:用于向服务器发送取消请求
  • status:用于通知客户端系统中每个目标的当前状态
  • feedback:用于周期反馈任务运行的监控数据
  • result:用于向client发送任务的执行结果,这个topic只会发布一次

创建action功能包

  • 依然使用 catkin_simple 功能来创建 package
catkin_create_pkg dodishes roscpp actionlib actionlib_msgs
  • 然后在新建的package中创建一个action文件夹用来存储action messages的信息,方法与创建service messages一样
cd dodishes
mkdir action
  • 惯例,在package.xml文件中添加两行生成消息文件的语句
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

定义action文件

  • ROS系统在action文件(文件名后缀为.action)中定义了上述 goal、result、feedback 等消息,每部分用 “---” 分隔
  • 这些文件被放置在包的 ./action 目录

例如:./action/DoDishes.action

./action/DoDishes.action # 定义目标消息
# Define the goal
uint32 dishwasher_id # Specify which dishwasher we want to use
---
# Define the result # 定义结果消息
uint32 total_dishes_cleaned
---
# Define a feedback message # 定义周期反馈消息
float32 percent_complete

基于这个 .action 文件,需要生成 6 条消息,以便客户端和服务器进行通信

功能包配置

package.xml

  • 在package.xml文件中添加以下几行
<build_depend>actionlib</build_depend>
<build_depend>actionlib_msgs</build_depend>
<exec_depend>actionlib</exec_depend>
<exec_depend>actionlib_msgs</exec_depend>

CMakeLists.txt

  • 在CMakeLists.txt文件中添加以下几行
find_package(catkin REQUIRED COMPONENTS
actionlib_msgs
actionlib
)
add_action_files(DIRECTORY action
FILES
DoDishes.action
)
generate_messages(
DEPENDENCIES
actionlib_msgs
)
catkin_package(
CATKIN_DEPENDS actionlib_msgs
)

编写action_server

  • 在dodishes/src中创建DoDishes_server.cpp文件
#include <ros/ros.h>
#include <actionlib/server/simple_action_server.h> // 这是一个library里面一个做好的包(simple_action_server)里面的头文件
#include "dodishes/DoDishesAction.h" // 这个头文件是重点,在上一部分生成的action消息的头文件
// 为服务端数据类型定义别名
typedef actionlib::SimpleActionServer<dodishes::DoDishesAction> Server;
// 收到action的goal后调用该回调函数
void execute(const dodishes::DoDishesGoalConstPtr & goal, Server * as)
{
ros::Rate r(1);
dodishes::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;
// 发布feedback
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);
// 其中的参数n,就是NodeHandle。而“do_dishes”,是你给server起的名字
// boost::bind(&execute, _1, &server)是当收到新的goal时候需要的返回函数
// false的意思是暂时不启动这个server
server.start(); // 服务器开始运行
ros::spin();
return 0;
}

总结:

  • 初始化ROS节点;
  • 创建action_server实例;
  • 启动服务器,等待action请求;
  • 在回调函数中完成动作服务功能的处理,并反馈进度信息;
  • action完成,发送结束信息

编写action_client

  • 在dodishes/src中创建DoDishes_client.cpp文件
#include <ros/ros.h>
#include <actionlib/client/simple_action_client.h> // 这是一个library里面一个做好的包(simple_action_server)里面的头文件
#include "dodishes/DoDishesAction.h" // 这个头文件是重点,在上一部分生成的action消息的头文件
// 为客户端数据类型定义一个别名
typedef actionlib::SimpleActionClient<dodishes::DoDishesAction> Client;
// 当服务器完成后会调用该回调函数一次
void doneCb(const actionlib::SimpleClientGoalState & state,
const dodishes::DoDishesResultConstPtr & result)
{
ROS_INFO("Yay! The dishes are now clean");
ros::shutdown();
}
// 当服务器激活后会调用该回调函数一次
void activeCb()
{
ROS_INFO("Goal just went active");
}
// 收到feedback后调用该回调函数
void feedbackCb(const dodishes::DoDishesFeedbackConstPtr & feedback)
{
ROS_INFO(" percent_complete : %f ", feedback->percent_complete);
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "do_dishes_client");
// 定义一个客户端,连接名为do_dishes的服务器,开启自循环线程
Client client("do_dishes", true);
ROS_INFO("Waiting for action server to start.");
client.waitForServer(); // 客户端等待服务器函数
// 可以传递一个ros::Duration作为最大等待值,程序进入到这个函数开始挂起等待
// 服务器就位或者达到等待最大时间退出,前者返回true,后者返回false
ROS_INFO("Action server started, sending goal.");
dodishes::DoDishesGoal goal; // 创建一个action的goal
goal.dishwasher_id = 1; // 定义盘子的目标,也就是洗一个盘子
// 发送action的goal给服务器端,并且设置回调函数
client.sendGoal(goal, &doneCb, &activeCb, &feedbackCb);
ros::spin();
return 0;
}

总结:

  • 初始化ROS节点;
  • 创建action_client实例;
  • 连接action服务端;
  • 发送action goal;
  • 根据不同类型的服务端反馈处理回调函数

编译配置

CMakeLists.txt

add_executable(DoDishes_client src/DoDishes_client.cpp)
target_link_libraries(DoDishes_client ${catkin_LIBRARIES})
add_dependencies(DoDishes_client ${${PROJECT_NAME}_EXPORTED_TARGETS})
add_executable(DoDishes_server src/DoDishes_server.cpp)
target_link_libraries(DoDishes_server ${catkin_LIBRARIES})
add_dependencies(DoDishes_server ${${PROJECT_NAME}_EXPORTED_TARGETS})


如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
posted @   TNTksals  阅读(865)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示