赞
踩
关于action通信,先从之前导航中的应用场景开始介绍,描述如下:
机器人导航到某个目标点,此过程需要一个节点A发布目标信息,然后一个节点B接收到请求并控制移动,最终响应目标达成状态信息。
这好像是服务通信实现,因为需求中要A发送目标,B执行并返回结果,这是一个典型的基于请求响应的应答模式,不过,如果只是使用基本的服务通信实现,存在一个问题:导航是一个过程,是耗时操作,如果使用服务通信,那么只有在导航结束时,才会产生响应结果,而在导航过程中,节点A是不会获取到任何反馈的,从而可能出现程序"假死"的现象,过程的不可控意味着不良的用户体验,以及逻辑处理的缺陷(比如:导航中止的需求无法实现)。更合理的方案应该是:导航过程中,可以连续反馈当前机器人状态信息,当导航终止时,再返回最终的执行结果。在ROS中,该实现策略称之为:action 通信。
在ROS中提供了actionlib功能包集,用于实现 action 通信。action 是一种类似于服务通信的实现,其实现模型也包含请求和响应,但是不同的是,在请求和响应的过程中,服务端还可以连续的反馈当前任务进度,客户端可以接收连续反馈并且还可以取消任务。
action结构图解:
action通信接口图解:
goal:目标任务;
cacel:取消任务;
status:服务端状态;
result:最终执行结果(只会发布一次);
feedback:连续反馈(可以发布多次)。
一般适用于耗时的请求响应场景,用以获取连续的状态反馈。
创建两个ROS 节点,服务器和客户端,客户端可以向服务器发送目标数据N(一个整型数据)服务器会计算 1 到 N 之间所有整数的和,这是一个循环累加的过程,返回给客户端,这是基于请求响应模式的,又已知服务器从接收到请求到产生响应是一个耗时操作,每累加一次耗时0.1s,为了良好的用户体验,需要服务器在计算过程中,每累加一次,就给客户端响应一次百分比格式的执行进度,使用 action实现。
action、srv、msg 文件内的可用数据类型一致,且三者实现流程类似:
按照固定格式创建action文件;
编辑配置文件;
编译生成中间文件。
首先新建功能包,并导入依赖: roscpp rospy std_msgs actionlib actionlib_msgs;
然后功能包下新建 action 目录,新增 Xxx.action(比如:AddInts.action)。
action 文件内容组成分为三部分:请求目标值、最终响应结果、连续反馈,三者之间使用—分割示例内容如下:
#目标值
int32 num
---
#最终结果
int32 result
---
#连续反馈
float64 progress_bar
CMakeLists.txt
find_package (catkin REQUIRED COMPONENTS roscpp rospy std_msgs actionlib actionlib_msgs ) Copy add_action_files( FILES AddInts.action ) Copy generate_messages( DEPENDENCIES std_msgs actionlib_msgs ) Copy 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):
1.编写action服务端实现;
2.编写action客户端实现;
3.编辑CMakeLists.txt;
4.编译并执行。
需要像之前自定义 msg 实现一样配置c_cpp_properies.json 文件,如果以前已经配置且没有变更工作空间,可以忽略,如果需要配置,配置方式与之前相同:
{ "configurations": [ { "browse": { "databaseFilename": "", "limitSymbolsToIncludedHeaders": true }, "includePath": [ "/opt/ros/noetic/include/**", "/usr/include/**", "/xxx/yyy工作空间/devel/include/**" //配置 head 文件的路径 ], "name": "ROS", "intelliSenseMode": "gcc-x64", "compilerPath": "/usr/bin/gcc", "cStandard": "c11", "cppStandard": "c++17" } ], "version": 4 }
#include "ros/ros.h" #include "actionlib/server/simple_action_server.h" #include "demo01_action/AddIntsAction.h" /* 需求: 创建两个ROS节点,服务器和客户端, 客户端可以向服务器发送目标数据N(一个整型数据) 服务器会计算1到N之间所有整数的和,这是一个循环累加的过程,返回给客户端, 这是基于请求响应模式的, 又已知服务器从接收到请求到产生响应是一个耗时操作,每累加一次耗时0.1s, 为了良好的用户体验,需要服务器在计算过程中, 每累加一次,就给客户端响应一次百分比格式的执行进度,使用action实现。 流程: 1.包含头文件; 2.初始化ROS节点; 3.创建NodeHandle; 4.创建action服务对象; 5.处理请求,产生反馈与响应; 6.spin(). */ typedef actionlib::SimpleActionServer<demo01_action::AddIntsAction> Server; void cb(const demo01_action::AddIntsGoalConstPtr &goal,Server* server){ //获取目标值 int num = goal->num; ROS_INFO("目标值:%d",num); //累加并响应连续反馈 int result = 0; demo01_action::AddIntsFeedback feedback;//连续反馈 ros::Rate rate(10);//通过频率设置休眠时间 for (int i = 1; i <= num; i++) { result += i; //组织连续数据并发布 feedback.progress_bar = i / (double)num; server->publishFeedback(feedback); rate.sleep(); } //设置最终结果 demo01_action::AddIntsResult r; r.result = result; server->setSucceeded(r); ROS_INFO("最终结果:%d",r.result); } int main(int argc, char *argv[]) { setlocale(LC_ALL,""); ROS_INFO("action服务端实现"); // 2.初始化ROS节点; ros::init(argc,argv,"AddInts_server"); // 3.创建NodeHandle; ros::NodeHandle nh; // 4.创建action服务对象; /*SimpleActionServer(ros::NodeHandle n, std::string name, boost::function<void (const demo01_action::AddIntsGoalConstPtr &)> execute_callback, bool auto_start) */ // actionlib::SimpleActionServer<demo01_action::AddIntsAction> server(....); Server server(nh,"addInts",boost::bind(&cb,_1,&server),false); server.start(); // 5.处理请求,产生反馈与响应; // 6.spin(). ros::spin(); return 0; }
注:可以先配置CMakeLists.tx文件并启动上述action服务端,然后通过 rostopic 查看话题,向action相关话题发送消息,或订阅action相关话题的消息。
#include "ros/ros.h" #include "actionlib/client/simple_action_client.h" #include "demo01_action/AddIntsAction.h" /* 需求: 创建两个ROS节点,服务器和客户端, 客户端可以向服务器发送目标数据N(一个整型数据) 服务器会计算1到N之间所有整数的和,这是一个循环累加的过程,返回给客户端, 这是基于请求响应模式的, 又已知服务器从接收到请求到产生响应是一个耗时操作,每累加一次耗时0.1s, 为了良好的用户体验,需要服务器在计算过程中, 每累加一次,就给客户端响应一次百分比格式的执行进度,使用action实现。 流程: 1.包含头文件; 2.初始化ROS节点; 3.创建NodeHandle; 4.创建action客户端对象; 5.发送目标,处理反馈以及最终结果; 6.spin(). */ typedef actionlib::SimpleActionClient<demo01_action::AddIntsAction> Client; //处理最终结果 void done_cb(const actionlib::SimpleClientGoalState &state, const demo01_action::AddIntsResultConstPtr &result){ if (state.state_ == state.SUCCEEDED) { ROS_INFO("最终结果:%d",result->result); } else { ROS_INFO("任务失败!"); } } //服务已经激活 void active_cb(){ ROS_INFO("服务已经被激活...."); } //处理连续反馈 void feedback_cb(const demo01_action::AddIntsFeedbackConstPtr &feedback){ ROS_INFO("当前进度:%.2f",feedback->progress_bar); } int main(int argc, char *argv[]) { setlocale(LC_ALL,""); // 2.初始化ROS节点; ros::init(argc,argv,"AddInts_client"); // 3.创建NodeHandle; ros::NodeHandle nh; // 4.创建action客户端对象; // SimpleActionClient(ros::NodeHandle & n, const std::string & name, bool spin_thread = true) // actionlib::SimpleActionClient<demo01_action::AddIntsAction> client(nh,"addInts"); Client client(nh,"addInts",true); //等待服务启动 client.waitForServer(); // 5.发送目标,处理反馈以及最终结果; /* 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) */ demo01_action::AddIntsGoal goal; goal.num = 10; client.sendGoal(goal,&done_cb,&active_cb,&feedback_cb); // 6.spin(). ros::spin(); return 0; }
注:等待服务启动,只可以使用client.waitForServer();
,之前服务中等待启动的另一种方式ros::service::waitForService("addInts");
不适用
add_executable(action01_server src/action01_server.cpp)
add_executable(action02_client src/action02_client.cpp)
...
add_dependencies(action01_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(action02_client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
...
target_link_libraries(action01_server
${catkin_LIBRARIES}
)
target_link_libraries(action02_client
${catkin_LIBRARIES}
)
首先启动 roscore,然后分别启动action服务端与action客户端。
1.编写action服务端实现;
2.编写action客户端实现;
3.编辑CMakeLists.txt;
4.编译并执行。
{
"python.autoComplete.extraPaths": [
"/opt/ros/noetic/lib/python3/dist-packages",
"/xxx/yyy工作空间/devel/lib/python3/dist-packages"
]
}
#! /usr/bin/env python import rospy import actionlib from demo01_action.msg import * """ 需求: 创建两个ROS 节点,服务器和客户端, 客户端可以向服务器发送目标数据N(一个整型数据)服务器会计算 1 到 N 之间所有整数的和, 这是一个循环累加的过程,返回给客户端,这是基于请求响应模式的, 又已知服务器从接收到请求到产生响应是一个耗时操作,每累加一次耗时0.1s, 为了良好的用户体验,需要服务器在计算过程中, 每累加一次,就给客户端响应一次百分比格式的执行进度,使用 action实现。 流程: 1.导包 2.初始化 ROS 节点 3.使用类封装,然后创建对象 4.创建服务器对象 5.处理请求数据产生响应结果,中间还要连续反馈 6.spin """ class MyActionServer: def __init__(self): #SimpleActionServer(name, ActionSpec, execute_cb=None, auto_start=True) self.server = actionlib.SimpleActionServer("addInts",AddIntsAction,self.cb,False) self.server.start() rospy.loginfo("服务端启动") def cb(self,goal): rospy.loginfo("服务端处理请求:") #1.解析目标值 num = goal.num #2.循环累加,连续反馈 rate = rospy.Rate(10) sum = 0 for i in range(1,num + 1): # 累加 sum = sum + i # 计算进度并连续反馈 feedBack = i / num rospy.loginfo("当前进度:%.2f",feedBack) feedBack_obj = AddIntsFeedback() feedBack_obj.progress_bar = feedBack self.server.publish_feedback(feedBack_obj) rate.sleep() #3.响应最终结果 result = AddIntsResult() result.result = sum self.server.set_succeeded(result) rospy.loginfo("响应结果:%d",sum) if __name__ == "__main__": rospy.init_node("action_server_p") server = MyActionServer() rospy.spin()
注:可以先配置CMakeLists.tx文件并启动上述action服务端,然后通过 rostopic 查看话题,向action相关话题发送消息,或订阅action相关话题的消息。
#! /usr/bin/env python import rospy import actionlib from demo01_action.msg import * """ 需求: 创建两个ROS 节点,服务器和客户端, 客户端可以向服务器发送目标数据N(一个整型数据)服务器会计算 1 到 N 之间所有整数的和, 这是一个循环累加的过程,返回给客户端,这是基于请求响应模式的, 又已知服务器从接收到请求到产生响应是一个耗时操作,每累加一次耗时0.1s, 为了良好的用户体验,需要服务器在计算过程中, 每累加一次,就给客户端响应一次百分比格式的执行进度,使用 action实现。 流程: 1.导包 2.初始化 ROS 节点 3.创建 action Client 对象 4.等待服务 5.组织目标对象并发送 6.编写回调, 激活、连续反馈、最终响应 7.spin """ def done_cb(state,result): if state == actionlib.GoalStatus.SUCCEEDED: rospy.loginfo("响应结果:%d",result.result) def active_cb(): rospy.loginfo("服务被激活....") def fb_cb(fb): rospy.loginfo("当前进度:%.2f",fb.progress_bar) if __name__ == "__main__": # 2.初始化 ROS 节点 rospy.init_node("action_client_p") # 3.创建 action Client 对象 client = actionlib.SimpleActionClient("addInts",AddIntsAction) # 4.等待服务 client.wait_for_server() # 5.组织目标对象并发送 goal_obj = AddIntsGoal() goal_obj.num = 10 client.send_goal(goal_obj,done_cb,active_cb,fb_cb) # 6.编写回调, 激活、连续反馈、最终响应 # 7.spin rospy.spin()
先为 Python 文件添加可执行权限:chmod +x *.py
catkin_install_python(PROGRAMS
scripts/action01_server_p.py
scripts/action02_client_p.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
首先启动 roscore,然后分别启动action服务端与action客户端。
参考视屏:赵虚左ros入门
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。