目录
一、准备工作——配置vscode
二、服务端创建
1.创建文件demo01_server.cpp
2.服务端的CMakeList.txt配置
3.测试服务端
三、客户端实现
1.创建domo01_client.cpp
2.客户端的CMakeList.txt配置
3.测试客户端
4.优化客户端
学习参考:赵虚左的ROS课程与古月的ROS机器人开发实践书籍。
为了vscode在调用自定义服务数据时不报假错同时能够补全代码,需要对vscode进行配置。
c_cpp_properties.json(注意逗号)
"includePath": [
--snip--
"/home/rosmelodic/catkin_ws/devel/include/**"
],
也就是刚刚创建自定义服务数据,catkin_make后中间文件的目录。
在src中创建demo01_server.cpp文件。
#include "ros/ros.h"
#include "server_client/AddInts.h"
bool huidiao(server_client::AddInts::Request &request,
server_client::AddInts::Response &response){
int num11 = request.num1;
int num22 = request.num2;
ROS_INFO("收到的请求数据为:num11 =%d,num22=%d",num11,num22);
int sum12 = num11 + num22;
response.sum = sum12;
ROS_INFO("求和结果:sum = %d",sum12);
//ros::spin();//这是一次尝试
return true;
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"jiafa_server");
ros::NodeHandle n;
ros::ServiceServer service = n.advertiseService("add_ints",huidiao);
ROS_INFO("准备好加入两个整数");
ros::spin();
return 0;
}
#include "ros/ros.h"
#include "server_client/AddInts.h"
头文件:调用roscpp和刚刚创建的自定义服务数据类型。调用形式为功能包名/通过编译生成的自定义头文件名
bool huidiao(server_client::AddInts::Request &request,
server_client::AddInts::Response &response){
int num11 = request.num1;
int num22 = request.num2;
ROS_INFO("收到的请求数据为:num11 =%d,num22=%d",num11,num22);
int sum12 = num11 + num22;
response.sum = sum12;
ROS_INFO("求和结果:sum = %d",sum12);
ros::spin();
return true;
}
首先先声明了函数输入参数的数据类型,其中数据类型为刚刚创建的自定义服务数据类型,需要注意的是,自定义服务数据类型创建时通过“---”的符号将上下两部分分为了请求和应答数据类型,如下图。其中调用方法为
server_client::AddInts::Request(Response)——功能包名::创建的自定义数据类型文件名::Request(Response)——请求(应答)
int num11 = request.num1;
int num22 = request.num2;
声明变量同时接收数据。
ROS_INFO("收到的请求数据为:num11 =%d,num22=%d",num11,num22);
在控制台输出字符串,同时注意中文乱码问题,为了解决此问题,主函数中加入:
setlocale(LC_ALL,"");
计算完,再赋值给response.sum应答。
int sum12 = num11 + num22;
response.sum = sum12;
控制台输出结果
ROS_INFO("求和结果:sum = %d",sum12);
因为服务的回调函数需要返回的时布尔类型,故运行成功应该返回true,不再是之前话题通信中返回的void(空)。这是一次尝试,在回调函数中加入回头函数,发现这是不行的,主函数运行一次,直接跳出去了,不再循环。
//ros::spin();//这是一次尝试
return true;
进入主函数部分:
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"jiafa_server");
ros::NodeHandle n;
ros::ServiceServer service = n.advertiseService("add_ints",huidiao);
ROS_INFO("准备好加入两个整数");
ros::spin();
return 0;
}
防止在控制台出现中文乱码。
setlocale(LC_ALL,"");
常规的初始化节点和创建节点句柄
ros::init(argc,argv,"jiafa_server");
ros::NodeHandle n;
服务端创建,其中发现区别:
泛型函数的没声明,不意味着不能声明,不加就跟随着回调函数数据类型了。(函数帮助通过ctrl+shift+空格,调出)
加上的效果。
ros::ServiceServer service = n.advertiseService("add_ints",huidiao);
没有缓存区了,之前的话题通信都是有缓存区的。
ros::ServiceServer service = n.advertiseService("add_ints",huidiao);
add_executable(demo_server src/demo01_server.cpp)
## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(demo_server ${PROJECT_NAME}_gencpp)
target_link_libraries(demo_server
${catkin_LIBRARIES}
)
加上注释掉的部分是因为CMakeList.txt中出现了两处 add_dependencies,配置不对会报错:
(2条消息) ROS报错:CMakeList.txt配置错误——add_dependencies位置错误(难发现)_机械专业的计算机小白的博客-CSDN博客https://blog.csdn.net/wzfafabga/article/details/127385406其中这是为了在编译cpp文件之前,先编译自定义服务数据类型,生成中间文件。
add_dependencies(demo_server ${PROJECT_NAME}_gencpp)
启动ROS Master
roscore
source一下,打开刚刚的cpp文件,其中指向的名字是CMakeList.txt文件中的add_executable设置的。
source ./devel/setup.bash
rosrun server_client demo_server
此时服务端是挂起的。
rosmelodic@rosmelodic-virtual-machine:~/catkin_ws$ source ./devel/setup.bash
rosmelodic@rosmelodic-virtual-machine:~/catkin_ws$ rosrun server_client demo_server
[ INFO] [1666080588.394932943]: 准备好加入两个整数
为了查看服务端是否功能正常:
source一下,
source ./devel/setup.bash
rosservice call add_ints "num1: 0
num2: 0"
其中 add_ints是cpp主函数中创建的服务名
ros::ServiceServer service = n.advertiseService("add_ints",huidiao);
后面的都是Tab代码补齐的。
rosmelodic@rosmelodic-virtual-machine:~/catkin_ws$ rosservice call add_ints "num1: 1
num2: 5"
sum: 6
修改并运行,发现应答的sum得到了结果。这些参数都是对应着创建的自定义服务数据类型的srv文件:
int32 num1
int32 num2
---
int32 sum
同时观察启动服务端的控制台:
rosmelodic@rosmelodic-virtual-machine:~/catkin_ws$ rosrun server_client demo_server
[ INFO] [1666081116.775811954]: 准备好加入两个整数
[ INFO] [1666081146.878643685]: 收到的请求数据为:num11 =1,num22=5
[ INFO] [1666081146.878690322]: 求和结果:sum = 6
发现程序中的ROS_INFO输出的字符串全部输出了出来。到此服务端运行成功。
domo01_client.cpp
#include "ros/ros.h"
#include "server_client/AddInts.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"jiafa_client");
ros::NodeHandle n;
ros::ServiceClient client = n.serviceClient("add_ints");
server_client::AddInts xiaoxi ;
xiaoxi.request.num1 = 20;
xiaoxi.request.num2 = 30;
bool tiaojian = client.call(xiaoxi);
if(tiaojian)
{
ROS_INFO("应答结果接收成功");
ROS_INFO("结果为:%d",xiaoxi.response.sum);
}
else
{
ROS_INFO("接收结果失败");
}
return 0;
}
只对与服务端不同地方进行讨论:
ros::ServiceClient client = n.serviceClient("add_ints");
客户端的创建:
话题名必须和服务端话题名一致。
其中回调函数不在有,泛型数据类型为刚刚创建的自定义服务数据类型,注意这是不同于服务端的泛型数据类型的声明,其中服务端的泛型声明是请求和应答通过逗号隔开:
ros::ServiceServer service = n.advertiseService("add_ints",huidiao);
具体函数提示可以通过ctrl+shift+空格显示。
声明变量并赋值。
server_client::AddInts xiaoxi ;
xiaoxi.request.num1 = 20;
xiaoxi.request.num2 = 30;
通过client.call函数发布请求,返回一个布尔值来确定应答是否成功。
bool tiaojian = client.call(xiaoxi);
if(tiaojian)
{
ROS_INFO("应答结果接收成功");
ROS_INFO("结果为:%d",xiaoxi.response.sum);
}
else
{
ROS_INFO("接收结果失败");
}
add_executable(demo_client src/demo01_client.cpp)
add_dependencies(demo_client ${PROJECT_NAME}_gencpp)
target_link_libraries(demo_client
${catkin_LIBRARIES}
)
再次提醒add_dependencies出现了两次,注意是后面add_executable的add_dependencies!
服务通信与话题通信不同。话题通信中的发布者和订阅者可以以不同的先后顺序启动,当然,为了发布者发布的信息订阅者全能接收到,故先运行订阅者。服务通信必须先运行服务端再运行客户端。
启动ROS Master
roscore
启动服务端
source ./devel/setup.bash
rosrun server_client demo_server
启动客户端
source ./devel/setup.bash
rosrun server_client demo_client
运行结果如图
主函数中加入,其中argc对应的是主函数的参数,argc代表着传入参数的个数。
if (argc != 3)
// if (argc != 5)//launch 传参(0-文件路径 1传入的参数 2传入的参数 3节点名称 4日志路径)
{
ROS_ERROR("请提交两个整数");
return 1;
}
主函数中调整,其中atoi()函数作用是字符串转化为整形,可以按住ctrl点击函数名查看。其中古月的是atoll()是字符串转化为long long int型。其中argv[]对应主函数的参数。argv[]列表存放着传入参数的值,argv[1]和argv[2]是传入的第二个和第三个参数的值,由于传入的是字符串,故需要转化为int类型才能做数值计算。
/* Convert a string to an integer. */
// xiaoxi.request.num1 = 20;
// xiaoxi.request.num2 = 30;
xiaoxi.request.num1 = atoi(argv[1]);
xiaoxi.request.num2 = atoi(argv[2]);
再次运行服务端和客户端
rosrun server_client demo_server
rosrun server_client demo_client 10 20
结果: