- 服务
上一个教程建立的ROS通讯是一种称为消息(message)的单向交互,该消息通过称为主题(topic)的渠道发送。 现在,我们将探索一种不同的通信类型,即通过一个节点到另一个节点的请求(request)以及该节点到第一个节点的响应(response)进行的双向交互。 在这个模块中,我们将创建一个服务服务器service server(等待请求并提供响应)和客户端client(请求信息,然后等待响应)。
创建Service定义
1. 在myworkcell_core程序包中创建一个名为srv的文件夹(与程序包的src文件夹处于同一级别)
cd ~/catkin_ws/src/myworkcell_core
mkdir srv
2. 在srv文件夹下创建文件 LocalizePart.srv
3.编辑LocalizePart.srv文件,添加:
#request
string base_frame
---
#response
geometry_msgs/Pose pose
4. 编辑程序包的CMakeLists.txt 和 package.xml:
message_generation 从上一步中创建的.srv文件构建C ++代码是必需的
message_runtime 提供新消息的运行时依赖性
geometry_msgs 提供我们的服务定义中使用的Pose消息类型
4.1 编辑程序包的CMakeLists.txt file 以增加构建依赖:
find_package(catkin REQUIRED COMPONENTS
roscpp
fake_ar_publisher
geometry_msgs
message_generation
)
4.2编辑程序包的CMakeLists.txt file 以增加运行依赖:
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES myworkcell_node
CATKIN_DEPENDS
roscpp
fake_ar_publisher
message_runtime
geometry_msgs
# DEPENDS system_lib
)
4.3 编辑package.xml file以增加构建/运行依赖:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<depend>geometry_msgs</depend>
5. 编辑程序包的CMakeLists.txt,添加规则以生成新的服务文件:
5.1 取消注释/编辑以下CMakeLists.txt规则以引用我们之前定义的LocalizePart服务:
## Generate services in the 'srv' folder
add_service_files(
FILES
LocalizePart.srv
)
5.2 取消注释/编辑以下CMakeLists.txt规则以启用消息和服务的生成:
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
geometry_msgs
)
服务服务器Server
编辑vision_node.cpp文件:
添加头文件:
#include <myworkcell_core/LocalizePart.h>
在Localizer类里添加:
添加成员变量
ros::ServiceServer server_;
在Localizer类的构造函数中初始化此变量,将服务发布给ROS主服务器master:
server_ = nh.advertiseService("localize_part", &Localizer::localizePart, this);
上面的advertiseService命令引用了名为localizePart的服务回调。 在Localizer类中使用此名称创建一个布尔函数。 请记住,您的请求和响应类型是在LocalizePart.srv文件中定义的。 布尔函数的参数是请求和响应类型,其一般结构为Package :: ServiceName :: Request或Package :: ServiceName :: Response。localizePart回调函数中的服务响应。 最终,此回调会将从fake_ar_publisher(在visionCallback中)收到的姿势转换为服务请求中指定的帧。 现在,我们将跳过帧转换,仅传递从fake_ar_publisher接收的数据。 将从fake_ar_publisher接收到的姿势测量值(保存到last_msg_)直接复制到服务响应中。
bool localizePart(myworkcell_core::LocalizePart::Request& req,
myworkcell_core::LocalizePart::Response& res)
{
// Read last message
fake_ar_publisher::ARMarkerConstPtr p = last_msg_;
if (!p) return false;
res.pose = p->pose.pose;
return true;
}
在visionCallback函数中注释掉ROS_INFO_STREAM调用,以避免不必要的信息使屏幕混乱。
服务客户端Client
在同一个myworkcell_core包中创建一个新节点,名为myworkcell_node.cpp。 这最终将是我们的主要“应用程序节点”,它控制“扫描与计划”任务中的操作顺序。 我们将执行的第一个操作是从我们在上面创建的视觉节点的LocalizePart服务中请求AR目标的位置。
添加头文件
#include <ros/ros.h>
#include <myworkcell_core/LocalizePart.h>
Main函数
int main(int argc, char **argv)
{
ros::init(argc, argv, "myworkcell_node");
ros::NodeHandle nh;
ROS_INFO("ScanNPlan node has been initialized");
ScanNPlan app(nh);
ros::Duration(.5).sleep(); // wait for the class to initialize
app.start();
ros::spin();
}
添加类ScanNPlan,在新的ScanNPlan类中,将ROS ServiceClient定义为该类的私有成员变量。 使用与先前定义的相同的服务名称(“ localize_part”)在ScanNPlan构造函数中初始化ServiceClient。 在名为start的ScanNPlan类内创建一个无参数的void函数。 这将包含我们大部分的应用程序工作流程。 现在,此函数将调用LocalizePart服务并打印响应。
class ScanNPlan
{
public:
ScanNPlan(ros::NodeHandle& nh)
{
vision_client_ = nh.serviceClient<myworkcell_core::LocalizePart>("localize_part");
}
void start()
{
ROS_INFO("Attempting to localize part");
// Localize the part
myworkcell_core::LocalizePart srv;
if (!vision_client_.call(srv))
{
ROS_ERROR("Could not localize part");
return;
}
ROS_INFO_STREAM("part localized: " << srv.response);
}
private:
// Planning components
ros::ServiceClient vision_client_;
};
编辑程序包的CMakeLists.txt,以构建新节点(可执行文件)及其相关的依存关系。 将以下规则直接添加到vision_node的匹配规则下的相应部分中:
add_executable(myworkcell_node src/myworkcell_node.cpp)
add_dependencies(myworkcell_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(myworkcell_node ${catkin_LIBRARIES})
重新构建工程
使用服务
roscore
rosrun fake_ar_publisher fake_ar_publisher_node
rosrun myworkcell_core vision_node
rosrun myworkcell_core myworkcell_node