文章结构
任务要求
使用 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客户端,最终运行结果与案例类似。
结果如下: