《ROS1学习笔记8——自定义服务素材》
文章目录
1、前言
在这一节我们要自定义服务通信接口,来实现服务通信。
这里以鱼香ROS老师给的例子解释一下。可能会更好理解:
总体的逻辑就是客服端发布指令让服务端给乌龟个速度,这部分是service ,服务端收到命令后给乌龟发布topic让乌龟动起来,这节就是service和topic都有。
我们后面编写的程序只涉及到客户端与服务端之间的通信,并没有加入话题。
2、自定义消息接口
2.1、新建功能包
cd src
catkin_create_pkg demo_service roscpp rospy std_msgs geometry_msgs turtlesim
2.2、自定义消息接口
2.2.1、新建srv
在功能包目录下,新建srv文件夹,并在srv目录下新建Person.srv文件(使用驼峰命名法,文件首字母大写)
string name
uint8 age
uint8 sex
uint8 unkown = 0
uint8 male = 1
uint8 female =2
---
string result
—上面是请求消息,横线下面是响应消息
2.2.1、在package.xml添加依赖
添加下面两行配置代码:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
message_genaation是编译依赖
message_runtime是运行依赖
2.2.3、修改CMakeLists
需要更改find_package和catkin_package
将message_genaration添加进去。
将message_runtime添加进去

1、find_package(catkin REQUIRED COMPONENTS …):
核心作用:声明编译当前包时需要的依赖包
2、catkin_package(…)
核心作用:声明当前包被其他包依赖时,对方需要的信息(包括运行时依赖、头文件路径、库文件等)。其中 CATKIN_DEPENDS 明确指定了运行时依赖(当前包运行时需要的包,以及依赖当前包的其他包在编译 / 运行时也需要的包)。
通俗总结:
- find_package 是 “我编译时需要什么”(给当前包的编译过程用)。
- catkin_package 是 “别人用我时需要什么”(给依赖当前包的其他包用,同时也隐含了当前包运行时的需求)。
3、添加一个配置项,用于将自己新建的.msg文件编译成其他的文件
add_service_files(FILES Person.srv)
generate_messages(DEPENDENCIES std_msgs)
如下图位置所示,并且在上面也有相应的注释说明

generate_messages(DEPENDENCIES std_msgs)
触发消息生成过程:ROS 会根据 add_message_files 声明的 .msg 文件,自动生成对应语言(C++、Python 等)的代码(如头文件、类定义),供你的程序调用。
DEPENDENCIES std_msgs
表示当前自定义消息依赖于 std_msgs 包中的基础消息类型,如刚用到的uint8等
2.3、编译
回到工作空间目录下,进行编译
cd..
catkin_make
编译成功后,devel/include/[pkg_name]目录下会出现下列该头文件
后续在编写代码中直接引用Person.h即可。
3、编写程序
在src目录下新建客户端和服务端代码
3.1、客户端(client)
实现思路:
- 初始化ROS节点
- 创建一个Client实例
- 发布服务请求数据
- 等待server处理之后的应答结果
#include <ros/ros.h>
#include "demo_service/Person.h"
int main(int argc, char** argv)
{
//初始化节点
ros::init(argc, argv, "person_client");
//创建节点句柄
ros::NodeHandle node;
//发现/spawn服务后,创建一个服务客户端,连接名为/spawn的service
//<demo_service::Person>数据类型
ros::service::waitForService("/show_person");
ros::ServiceClient person_client = node.serviceClient<demo_service::Person>("/show_person");
//初始化demo_service::Person的请求报告
demo_service::Person srv;
srv.request.name = "Tom";
srv.request.age = 20;
srv.request.sex = demo_service::Person::Request::male;
//请求服务调用
ROS_INFO("Call service to show[name:%s , age:%d, sex:%d]",
srv.request.name.c_str(), srv.request.age, srv.request.sex);
person_client.call(srv);
//显示服务调用结果
ROS_INFO("show person result : %s", srv.response.result.c_str());
return 0;
}
3.2、服务端(server)
实现思路
- 初始化ROS节点
- 创建一个Server实例
- 循环等待服务请求,进入回调函数
- 在回调函数中完成服务功能的处理,并反馈应答数据
#include <ros/ros.h>
#include "demo_service/Person.h"
//service回调函数,输入参数req, 输出参数res
bool personCallback(demo_service::Person::Request &req,
demo_service::Person::Response &res)
{
//显示请求数据
ROS_INFO("Person: name:%s age:%d sex:%d", req.name.c_str(), req.age, req.sex);
//设置反馈数据
res.result = "OK";
return true;
}
int main(int argc, char** argv)
{
//ROS节点初始化
ros::init(argc, argv, "person_server");
//创建节点句柄
ros::NodeHandle node;
//创建一个名为/show_person的server,注册回调函数personCallback
ros::ServiceServer person_service = node.advertiseService("/show_person", personCallback);
//循环等待回调函数
ROS_INFO("Ready to show person informtion.");
ros::spin();
return 0;
}
3.3、配置CMakeLists
在CMakeLists中添加如下代码
add_executable(person_server src/person_server.cpp)
target_link_libraries(person_server ${catkin_LIBRARIES})
add_dependencies(person_server ${PROJECT_NAME}_generate_messages_cpp)
add_executable(person_client src/person_client.cpp)
target_link_libraries(person_client ${catkin_LIBRARIES})
add_dependencies(person_client ${PROJECT_NAME}_generate_messages_cpp)

4、编译运行
4.1、编译
catkin_make
编译成功后,devel/lib目录下出现可执行文件
4.2、运行
这里我们打开三个终端:
终端1:运行roscore
终端2:
1、改变环境变量
source devel/setup.bash
2、运行服务端
rosrun demo_service person_server
如图:此时,服务端已经成功运行起来了,并且在等待客户端发布命令
终端3:
1、改变环境变量
source devel/setup.bash
2、运行客服端
rosrun demo_service person_client

运行客户端之后,
客户端成功向服务端发送了消息
并且服务端接收到了消息,并向客户端有所反馈。
并且还有一点就是,客户端这边不会一直发送数据,只有运行一次相应程序才会发送一次数据,然后服务端相应接收到一次数据。
浙公网安备 33010602011771号