文章结构
- 任务要求
- 话题模型
- 实现步骤
- 自定义srv
- 定义srv文件
- 编辑配置文件
- 编译
- 自定义srv调用
- vscode配置
- 编写服务端实现
- 编写客户端实现
- 执行
- 启动roscore
- 编译
- 启动客户端和服务端
- 编译
- 启动roscore
- 启动节点
任务要求
编写代码实现 ROS 中的服务请求与答复:
- 创建服务端,注册 Service
- 当服务端收到客户端 Service 请求(携带整型参数 a,b)后服务端返回 a,b 的和给客户端
- 客户端输出结果。
话题模型
ROS Master 负责保管 Server 和 Client 注册的信息,并匹配话题相同的 Server 与 Client ,帮助 Server 与 Client 建立连接,连接建立后,Client 发送请求信息,Server 返回响应信息。
实现步骤
自定义srv
在服务通信中,客户端提交两个整数至服务端,服务端求和并响应结果到客户端。因此,需要创建服务器与客户端通信的数据载体。
-
按照固定格式创建srv文件
-
编辑配置文件
-
编译生成中间文件
定义srv文件
进入工作空间,在src文件夹下创建新的功能包:
在新的功能包中创建名为srv的文件夹,并在该文件夹中创建srv文件,这里将其命名为addInts.srv:
服务通信中,数据分成两部分,请求与响应,在 srv 文件中请求和响应使用—分割,具体实现如下:
# 客户端请求时发送的两个数字
int32 a
int32 b
---
# 服务器响应发送的数据
int32 sum
注意,行末不要打分号
编辑配置文件
package.xml中添加编译依赖与执行依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
CMakeLists.txt编辑 srv 相关配置:
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# 需要加入 message_generation,必须有 std_msgs
add_service_files(
FILES
addInt.srv
)
由于在srv文件内使用的数据类型都是基于标准消息的,即依赖于standard_messages,故有了下面的修改:
generate_messages(
DEPENDENCIES
std_msgs
)
前面的find_package是指创建的功能包所依赖的包,而catkin_package是指find_package所依赖的包:
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES service_demo
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
编译
命令行中:
$ catkin_make
或者在vscode中:ctrl+shift+B:
自定义srv调用
编写服务通信,客户端提交两个整数至服务端,服务端求和并响应结果到客户端。
vscode配置
找到devel/include的路径
在.vscode中的c_cpp_properties.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
}
编写服务端实现
编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器。服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,客户端再解析。
//1.头文件
#include "ros/ros.h"
#include "service_demo/addInt.h" //该头文件名字和srv文件一致
//5.处理请求并产生响应
bool doNums(service_demo::addInt::Request &request,
service_demo::addInt::Response &response) //请求对象和响应对象以参数的方式传入
{
//1.处理请求
int a = request.a;
int b = request.b;
ROS_INFO("收到的请求数据:a = %d, b = %d",a,b);
//2.组织响应
int sum = a+b;
response.sum = sum;
ROS_INFO("求和结果: sum = %d",sum);
return true;
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//2.初始化ROS节点
ros::init(argc,argv,"addInt_Server"); //节点名称需要保持唯一
//3.创建ROS句柄
ros::NodeHandle n;
//4.创建服务对象
//参数1为话题名称/主题名称,用于将服务端和客户端关联在一起
//参数2是一个回调函数,用于处理请求,返回值为布尔类型
ros::ServiceServer server = n.advertiseService("addInt",doNums);
//6.多请求,需要调用ros::spin()
ros::spin();
return 0;
}
随后在CMakeLists进行编译设置修改:
编写客户端实现
实现参数的动态提交:
- 格式:rosrun xxxx xxxx 12 34
- 节点执行时,需要获取命令中的参数,并组织进request
// 1.包含头文件
// 1.包含头文件
#include "ros/ros.h"
#include "service_demo/addInt.h"
int main(int argc, char *argv[])
{
//如果输入的参数个数不对,会进行提示。argc为传入参数的个数。argv[]为参数的数组
if(argc != 3)
{
ROS_INFO("提交的参数个数不对");
return 1;
}
setlocale(LC_ALL,"");
// 2.初始化 ROS 节点
ros::init(argc,argv,"addInt_Client");
// 3.创建 ROS 句柄
ros::NodeHandle n;
// 4.创建 客户端 对象
ros::ServiceClient client = n.serviceClient<service_demo::addInt>("addInt");
// 5.组织请求数据
service_demo::addInt ai;
//5-1.组织请求
//从argv数组中提取参数,将字符串类型转为整形
ai.request.a = atoi(argv[1]);
ai.request.b = atoi(argv[2]);
//5-2.处理响应
//调用判断服务器状态的函数,用于等待服务
//函数1
client.waitForExistence();
//函数2
// ros::service::waitForService("addInt");
//客户端请求服务器
bool flag = client.call(ai);
if (flag)
{
ROS_INFO("响应成功");
//获取结果
ROS_INFO("响应结果 = %d",ai.response.sum);
}
else
{
ROS_ERROR("请求处理失败....");
return 1;
}
return 0;
}
随后在CMakeLists文件中修改配置:
执行
启动roscore
$ roscore
编译
命令行中:
$ catkin_make
或者在vscode中:ctrl+shift+B:
启动客户端和服务端
编译
$ catkin_make
启动roscore
$ roscore
启动节点
$ source ./devel/setup.bash
$ service_demo service_demo_server_c
再开一个终端
$ source ./devel/setup.bash
$ rosrun rosrun service_demo service_demo_client_c 10 10
效果如下: