文章目录
简述
动作消息通信与服务消息通信非常相似,服务具有与请求和响应分别对应的目标(goal)和结果(result)。除此之外,动作中多了反馈(feedback)。在一些场景下,收到请求后需要很长时间才能响应,又需要中间值时,则可以使用这个反馈发送相关的数据。
特点
反馈在动作客户端(action client)和动作服务器(action server)之间执行异步双向消息通信,其中动作客户端设置动作目标(goal),而动作服务器根据目标执行指定的工作,并将动作反馈和动作结果发送给动作客户端。与服务不同,动作通常用于指导复杂的机器人任务,例如发送一个目标值之后,还可以在任意时刻发送取消目标的命令。
交互过程
action通信使用的是ROS提供的通信包集actionlib,参考http://wiki.ros.org/actionlib。
action客户端与服务器应用程序之间的交互:
action客户端与服务端之间的消息通信接口:
核心元素
-
ROS Master(管理者)
必须首先被运行,并使用XMLRPC服务器,负责管理节点之间的消息通信中的连接信息。 -
Server (服务端)
服务端以请求作为输入,以响应作为输出,请求和响应都是消息,服务端收到服务请求后,执行指定的服务,并将结果下发给客户端,服务端是用于执行指定命令的节点。 -
Client (客户端)
客户端以请求作为输出,以响应作为输入,请求和响应都是消息,并发送服务请求到服务端后接收其结果,客户端是用于传达给定命令并接收结果值的节点。 -
action specification(动作规范)
参考:http://wiki.ros.org/cn/actionlib/DetailedDescription。
客户端节点和服务器节点通过建立在ROS消息之上的ROS动作协议进行通信,这些消息包括:
①goal
:用于向服务器发送目标。goal 话题使用自动生成的ActionGoal消息(例actionlib/TestActionGoal),用于将新目标发送到动作服务器。 实质上,ActionGoal 消息包装的是一个goal消息,并将其与目标ID捆绑在一起;
②cancel
:用于向服务器发送取消请求。cancel topic使用actionlib_msgs/GoalID消息,并允许动作客户端将cancel请求发送到action server。 每个cancel消息都有一个timestamp(时间戳)和goal ID;
③status
:用于通知客户端系统中每个目标的当前状态。状态话题使用的话题名为actionlib_msgs/GoalStatusArray,它为动作客户端提供有关动作服务器当前正在跟踪的每个目标服务器状态信息;
④feedback
:用于周期反馈目标的辅助信息。feedback主题使用自动生成的ActionFeedback消息,并为服务实现(server implementers)提供在处理目标期间向动作客户端发送定期更新的方法;
⑤result
:用于向client发送任务的执行结果,这个topic只会发布一次。result话题使用自动生成的ActionResult消息(例如:actionlib/TestActionResult),并为服务实现(server implementers)提供完成目标后向客户端发送信息的方法。
自定义action文件
ROS的action数据类型可以在这里查看。
action 文件中的数据分成三部分,使用 “- - -” 隔开,上面是目标定义,中间是结果定义,下面是反馈定义。
下面来创建一个洗衣的action文件,首先,在action_test包中创建action文件夹,然后在文件夹中创建消息文件(Laundry.action)。
# goal,洗衣类型 1:开始快洗;2:开始高温洗;3:开始浸泡洗
uint8 wash_type
---
# result,洗涤结果
string wash_result
---
# feedback,洗涤的进度
uint8 wash_percent
代码示例(服务端和客户端)
需求描述:编写服务端和客户端文件,来模拟洗衣过程,客户端输入洗衣模式后开始洗衣,服务的不断反馈洗衣进度,并反馈洗衣完成的结果。
继续在service_test中分别创建server.cpp(服务端)和client.cpp(客户端)两个文件。
服务端(action_server.cpp)
#include "ros/ros.h"
#include "actionlib/server/simple_action_server.h"
#include "action_test/LaundryAction.h"
#include <iostream>
typedef actionlib::SimpleActionServer<action_test::LaundryAction> ActionServer;
// 4.收到action的goal后调用的回调函数
void executeCb(const action_test::LaundryGoalConstPtr &goal, ActionServer *server)
{
// 获取目标值
uint8_t wash_type = goal->wash_type;
std::string wash_mode;
switch (wash_type)
{
case 1:
wash_mode = "快洗";
break;
case 2:
wash_mode = "高温洗";
break;
case 3:
wash_mode = "浸泡洗";
break;
default:
break;
}
ROS_INFO("目标值为%d,开始%s!", wash_type, wash_mode.c_str());
// 响应连续反馈
action_test::LaundryFeedback feedback;
for (int i = 0; i <= 100; i++)
{
feedback.wash_percent = i;
server->publishFeedback(feedback);
ros::Duration(0.5).sleep();
}
// 反馈结果
action_test::LaundryResult result;
result.wash_result = wash_mode + "完成!";
server->setSucceeded(result);
}
int main(int argc, char **argv)
{
// 设置编码
setlocale(LC_ALL, "");
// 1.初始化ROS节点
ros::init(argc, argv, "action_server");
// 2.实例化ROS句柄
ros::NodeHandle nh;
// 3.实例化action服务端对象
// 参数2为动作服务器名称,参数3为当一个新目标被接收时在一个单独的线程中被调用,参数4为告诉ActionServer是否在它出现时立即开始发布
ActionServer server(nh, "laundry", boost::bind(&executeCb, _1, &server), false);
server.start();
ros::spin();
return 0;
}
客户端(action_client.cpp)
#include "ros/ros.h"
#include "actionlib/client/simple_action_client.h"
#include "action_test/LaundryAction.h"
typedef actionlib::SimpleActionClient<action_test::LaundryAction> ActionClient;
void doneCb(const actionlib::SimpleClientGoalState &state, const action_test::LaundryResultConstPtr &result)
{
if (state.state_ == state.SUCCEEDED)
{
ROS_INFO("反馈结果:%s", result->wash_result.c_str());
}
else
{
ROS_INFO("任务失败!");
}
}
void activeCb()
{
ROS_INFO("动作已经被激活....");
}
void feedbackCb(const action_test::LaundryFeedbackConstPtr &feedback)
{
ROS_INFO("洗涤进度为:%d%s", feedback->wash_percent, "%");
}
int main(int argc, char **argv)
{
// 设置编码
setlocale(LC_ALL, "");
// 1.初始化ROS节点
ros::init(argc, argv, "action_client");
// 2.实例化ROS句柄
ros::NodeHandle nh;
// 3.实例化action客户端对象
// 参数2为动作的名称,参数3默认为true,无需再调用ros::spin(),设置为false时需手动调用
ActionClient client(nh, "laundry", true);
// 等待服务端启动
client.waitForServer();
// 4.定义动作目标数据
action_test::LaundryGoal goal;
goal.wash_type = 1;
// 5.发送目标,同时注册回调,处理反馈以及最终结果
// 参数1是转换为Done时处理的回调函数,参数2为转换为Active时处理的回调函数,参数3为每当收到此目标的反馈时就调用的回调函数
client.sendGoal(goal, &doneCb, &activeCb, &feedbackCb);
ros::spin();
return 0;
}
配置 CMakeLists.txt
# catkin构建时依赖的组件包
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
actionlib
actionlib_msgs
)
# 配置action源文件,FILES将引用当前功能包目录的action目录中的*.action文件,自动生成一个头文件(*.h)
add_action_files(
FILES
Laundry.action
)
# 生成消息时依赖于std_msgs、actionlib_msgs
generate_messages(
DEPENDENCIES
std_msgs
actionlib_msgs
)
# 运行时依赖,描述了库、catkin构建依赖项和系统依赖的功能包
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES action_test
CATKIN_DEPENDS roscpp rospy std_msgs actionlib actionlib_msgs
# DEPENDS system_lib
)
# 节点构建选项,配置可执行文件
add_executable(action_client src/action_client.cpp)
add_executable(action_server src/action_server.cpp)
# 构建库和可执行文件之前,预先生成依赖消息
add_dependencies(action_client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(action_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
# 节点构建选项,配置目标链接库
target_link_libraries(action_client
${catkin_LIBRARIES}
)
target_link_libraries(action_server
${catkin_LIBRARIES}
)
编译和运行
使用Ctrl+Shift+B进行编译,然后先使用roscore命令启动主节点。在新终端source下环境变量,然后分别运行服务端和客户端,此时可以看到服务端接收到了客户端的请求并不断反馈中间状态,并在最终反馈结果。
☝ ★★★ — 返回 《ROS机器人开发笔记汇总》总目录 — ★★★ ☝