文章结构
- 任务要求
- 话题模型
- 实现步骤
- 定义action文件
- 按照固定格式创建action文件
- 编辑配置文件
- 编译生成中间文件
- 编写服务端和客户端
- vscode配置
- 服务端
- 客户端
- 编译配置文件
- 执行
任务要求
使用 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 COMPONENTSroscpprospystd_msgsactionlibactionlib_msgs
)
add_action_files(FILEScheck.action
)
generate_messages(DEPENDENCIESstd_msgsactionlib_msgs
)
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES demo04_actionCATKIN_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配置
{"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.初始化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); //1Hzint 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;
}
客户端
/*
流程: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);elseROS_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;
}
编译配置文件
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客户端,最终运行结果与案例类似。
结果如下: