SLAM+语音机器人DIY系列:(二)ROS入门——6.编写简单的service和client

温馨提示 

本篇文章已经收录在我最新出版的书籍《机器人SLAM导航核心技术与实战》,感兴趣的读者可以购买纸质书籍来进行更加深入和系统性的学习,购买链接如下:

摘要                                          

ROS机器人操作系统在机器人应用领域很流行,依托代码开源和模块间协作等特性,给机器人开发者带来了很大的方便。我们的机器人“miiboo”中的大部分程序也采用ROS进行开发,所以本文就重点对ROS基础知识进行详细的讲解,给不熟悉ROS的朋友起到一个抛砖引玉的作用。本章节主要内容:

1.ROS是什么

2.ROS系统整体架构

3.在ubuntu16.04中安装ROS kinetic

4.如何编写ROS的第一个程序hello_world

5.编写简单的消息发布器和订阅器

6.编写简单的service和client

7.理解tf的原理

8.理解roslaunch在大型项目中的作用

9.熟练使用rviz

10.在实际机器人上运行ROS高级功能预览



6.编写简单的service和client  

上一节介绍了两个ros节点通过发布与订阅消息的通信方式,现在就介绍ros节点间通信的另外一种方式服务。我们将学到:如何自定义服务类型、server端节点编写、client端节点编写等。我就以实现两个整数求和为例,client端节点向server端节点发送ab的请求,server端节点返回响应sum=a+bclient端节点,通信网络结构如图20

20服务请求与响应ROS通信网络结构图

1)功能包的创建

catkin_ws/src/目录下新建功能包service_example,并在创建时显式的指明依赖roscppstd_msgs,依赖std_msgs将作为基本数据类型用于定义我们的服务类型。打开命令行终端,输入命令:

cd ~/catkin_ws/src/

#创建功能包service_example时,显式的指明依赖roscpp和std_msgs,
#依赖会被默认写到功能包的CMakeLists.txt和package.xml中
catkin_create_pkg service_example roscpp std_msgs

2)在功能包中创建自定义服务类型

通过前面的学习,我们知道服务通信过程中服务的数据类型需要用户自己定义,与消息不同,节点并不提供标准服务类型。服务类型的定义文件都是以*.srv为扩展名,并且被放在功能包的srv/文件夹下。

服务类型定义:

首先,在功能包service_example目录下新建srv目录,然后在service_example/srv/目录中创建AddTwoInts.srv文件,文件内容如下:

int64 a
int64 b
---
int64 sum

服务类型编译配置:

定义好我们的服务类型后,要想让该服务类型能在c++python等代码中被使用,必须要做相应的编译与运行配置。编译依赖message_generation,运行依赖message_runtime

打开功能包中的CMakeLists.txt文件,找到下面这段代码:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
)

message_generation添加进去,添加后的代码如下:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
  message_generation
)

继续,找到这段代码:

# add_service_files(
#   FILES
#   Service1.srv
#   Service2.srv
# )

去掉这段代码的#注释,将自己的服务类型定义文件AddTwoInts.srv填入,修改好后的代码如下:

add_service_files(
  FILES
  AddTwoInts.srv
)

继续,找到这段代码:

# generate_messages(
#   DEPENDENCIES
#   std_msgs
# )

去掉这段代码的#注释,generate_messages的作用是自动创建我们自定义的消息类型*.msg与服务类型*.srv相对应的*.h,由于我们定义的服务类型使用了std_msgs中的int64基本类型,所以必须向generate_messages指明该依赖,修改好后的代码如下:

generate_messages(
  DEPENDENCIES
  std_msgs
)

然后打开功能包中的package.xml文件,填入下面三句依赖:

<build_depend>message_generation</build_depend>
<build_export_depend>message_generation</build_export_depend>
<exec_depend>message_runtime</exec_depend>

检查ROS是否识别新建的服务类型:

我们通过<功能包名/服务类型名>找到该服务,打开命令行终端,输入命令:

source ~/catkin_ws/devel/setup.bash

rossrv show service_example/AddTwoInts

看到下面的输出,如图21,就说明新建服务类型能被ROS识别,新建服务类型成功了。

21新建服务类型能被ROS识别

3)功能包的源代码编写

功能包中需要编写两个独立可执行的节点,一个节点用来作为client端发起请求,另一个节点用来作为server端响应请求,所以需要在新建的功能包service_example/src/目录下新建两个文件server_node.cppclient_node.cpp,并将下面的代码分别填入。

首先,介绍server节点server_node.cpp,代码内容如下:

 

 1 #include "ros/ros.h"
 2 #include "service_example/AddTwoInts.h"
 3 
 4 bool add_execute(service_example::AddTwoInts::Request &req,
 5 service_example::AddTwoInts::Response &res)
 6 {
 7   res.sum = req.a + req.b;
 8   ROS_INFO("recieve request: a=%ld,b=%ld",(long int)req.a,(long int)req.b);
 9   ROS_INFO("send response: sum=%ld",(long int)res.sum);
10   return true;
11 } 
12 
13 int main(int argc,char **argv)
14 {
15   ros::init(argc,argv,"server_node");
16   ros::NodeHandle nh;
17 
18   ros::ServiceServer service = nh.advertiseService("add_two_ints",add_execute);
19   ROS_INFO("service is ready!!!");
20   ros::spin();
21 
22   return 0;
23 }

server节点代码进行解析。

#include “ros/ros.h”

#include “service_example/AddTwoInts.h”

包含头文件ros/ros.h,这是ROS提供的C++客户端库,是必须包含的头文件,就不多说了。service_example/AddTwoInts.h是由编译系统自动根据我们的功能包和在功能包下创建的*.srv文件生成的对应的头文件,包含这个头文件,程序中就可以使用我们自定义服务的数据类型了。

 

bool add_execute(...)

这个函数实现两个int64整数求和的服务,两个int64值从request获取,返回求和结果装入response里,requestresponse的具体数据类型都在前面创建的*.srv文件中被定义,这个函数返回值为bool型。

 

ros::init(argc,argv,”server_node”);

ros::NodeHandle nh;

初始化ros节点并指明节点的名称,声明一个ros节点的句柄,,就不多说了。

 

ros::ServiceServer service = nh.advertiseService(“add_two_ints”,add_execute);

这一句是创建服务,并将服务加入到ROS网络中,并且这个服务在ROS网络中以名称add_two_ints唯一标识,以便于其他节点通过服务名称进行请求。

 

ros::spin();

这一句话让程序进入自循环的挂起状态,从而让程序以最好的效率接收客户端的请求并调用回调函数,就不多说了。

 

接着,介绍client节点client_node.cpp,代码内容如下:

 

 1 #include "ros/ros.h"
 2 #include "service_example/AddTwoInts.h"
 3 
 4 #include <iostream>
 5 
 6 int main(int argc,char **argv)
 7 {
 8   ros::init(argc,argv,"client_node");
 9   ros::NodeHandle nh;
10 
11   ros::ServiceClient client =
12   nh.serviceClient<service_example::AddTwoInts>("add_two_ints");
13   service_example::AddTwoInts srv;
14   
15   while(ros::ok())
16   {
17     long int a_in,b_in;
18     std::cout<<"please input a and b:";
19     std::cin>>a_in>>b_in;
20 
21     srv.request.a = a_in;
22     srv.request.b = b_in;
23     if(client.call(srv))
24     {
25       ROS_INFO("sum=%ld",(long int)srv.response.sum);
26     }
27     else
28     {
29       ROS_INFO("failed to call service add_two_ints");
30     }
31   }
32   return 0;
33 }

client节点代码进行解析。

之前解释过的类似的代码就不做过多的解释了,这里重点解释一下前面没遇到过的代码。

ros::ServiceClient client =

 nh.serviceClient<service_example::AddTwoInts>("add_two_ints");

这一句创建client对象,用来向ROS网络中名称叫add_two_intsservice发起请求。

 

service_example::AddTwoInts srv;

定义了一个service_example::AddTwoInts服务类型的对象,该对象中的成员正是我们在*.srv文件中定义的absum,我们将待请求的数据填充到数据成员ab,请求成功后返回结果会被自动填充到数据成员sum中。

 

if(client.call(srv)){...}

这一句便是通过client的方法call来向service发起请求,请求传入的参数srv在上面已经介绍过了。

4)功能包的编译配置及编译

创建功能包service_example时,显式的指明依赖roscppstd_msgs,依赖会被默认写到功能包的CMakeLists.txtpackage.xml中,并且在功能包中创建*.srv服务类型时已经对服务的编译与运行做了相关配置,所以只需要在CMakeLists.txt文件的末尾行加入以下几句用于声明可执行文件就可以了:

add_executable(server_node src/server_node.cpp)
target_link_libraries(server_node ${catkin_LIBRARIES})
add_dependencies(server_node service_example_gencpp)

add_executable(client_node src/client_node.cpp)
target_link_libraries(client_node ${catkin_LIBRARIES})
add_dependencies(client_node service_example_gencpp)

这里面的add_executable用于声明可执行文件。target_link_libraries用于声明可执行文件创建时需要链接的库。add_dependencies用于声明可执行文件的依赖项,由于我们自定义了*.srvservice_example_gencpp的作用是让编译系统自动根据我们的功能包和在功能包下创建的*.srv文件生成的对应的头文件和库文件,service_example_gencpp这个名称是由功能包名称service_example加上_gencpp后缀而来的,后缀很好理解:生成c++文件就是_gencpp

生成python文件就是_genpy

接下来,就可以用下面的命令对功能包进行编译了:

cd ~/catkin_ws/
catkin_make -DCATKIN_WHITELIST_PACKAGES="service_example"

5)功能包的启动运行

首先,需要用roscore命令来启动ROS节点管理器,ROS节点管理器是所有节点运行的基础。

打开命令行终端,输入命令:

roscore

然后,用source devel/setup.bash激活catkin_ws工作空间,用rosrun <package_name> <node_name>启动功能包中的server节点。

再打开一个命令行终端,分别输入命令:

cd ~/catkin_ws/
source devel/setup.bash
rosrun service_example server_node 

看到有输出servive is ready!!!”,就说明server节点已经正常启动并开始等待client节点向自己发起请求了,如图22

(图22server节点已经正常启动

最后,用source devel/setup.bash激活catkin_ws工作空间,用rosrun <package_name> <node_name>启动功能包中的client节点。

再打开一个命令行终端,分别输入命令:

cd ~/catkin_ws/
source devel/setup.bash
rosrun service_example client_node 

看到有输出提示信息please input a and b:”后,键盘键入两个整数,以空格分割,输入完毕后回车。如果看到输出信息“sum=xxx”,就说明client节点向server端发起的请求得到了响应,打印出来的sum就是响应结果,这样就完成了一次服务请求的通信过程,如图23

(图23client节点已经正常启动

到这里,我们编写的serverclient就大功告成了,为了加深对整个程序工作流程的理解,我再把serverclientROS通信网络结构图拿出来,加深一下理解。

24服务请求与响应ROS通信网络结构图

后记                

------SLAM+语音机器人DIY系列【目录】快速导览------

第1章:Linux基础

1.Linux简介

2.安装Linux发行版ubuntu系统

3.Linux命令行基础操作

第2章:ROS入门

1.ROS是什么

2.ROS系统整体架构

3.在ubuntu16.04中安装ROS kinetic

4.如何编写ROS的第一个程序hello_world

5.编写简单的消息发布器和订阅器

6.编写简单的service和client

7.理解tf的原理

8.理解roslaunch在大型项目中的作用

9.熟练使用rviz

10.在实际机器人上运行ROS高级功能预览

第3章:感知与大脑

1.ydlidar-x4激光雷达

2.带自校准九轴数据融合IMU惯性传感器

3.轮式里程计与运动控制

4.音响麦克风与摄像头

5.机器人大脑嵌入式主板性能对比

6.做一个能走路和对话的机器人

第4章:差分底盘设计

1.stm32主控硬件设计

2.stm32主控软件设计

3.底盘通信协议

4.底盘ROS驱动开发

5.底盘PID控制参数整定

6.底盘里程计标

第5章:树莓派3开发环境搭建

1.安装系统ubuntu_mate_16.04

2.安装ros-kinetic

3.装机后一些实用软件安装和系统设置

4.PC端与robot端ROS网络通信

5.Android手机端与robot端ROS网络通信

6.树莓派USB与tty串口号绑定

7.开机自启动ROS节点

第6章:SLAM建图与自主避障导航

1.在机器人上使用传感器

2.google-cartographer机器人SLAM建图

3.ros-navigation机器人自主避障导航

4.多目标点导航及任务调度

5.机器人巡航与现场监控

第7章:语音交互与自然语言处理

1.语音交互相关技术

2.机器人语音交互实现

3.自然语言处理云计算引擎

第8章:高阶拓展

1.miiboo机器人安卓手机APP开发

2.centos7下部署Django(nginx+uwsgi+django+python3)

 


 

参考文献

 

[1] 张虎,机器人SLAM导航核心技术与实战[M]. 机械工业出版社,2022.

 

 

 

 

前言
编程基础篇
第1章 ROS入门必备知识
1.1 ROS简介 2
1.1.1 ROS的性能特色 2
1.1.2 ROS的发行版本 3
1.1.3 ROS的学习方法 3
1.2 ROS开发环境的搭建 3
1.2.1 ROS的安装 4
1.2.2 ROS文件的组织方式 4
1.2.3 ROS网络通信配置 5
1.2.4 集成开发工具 5
1.3 ROS系统架构 5
1.3.1 从计算图视角理解ROS架构 6
1.3.2 从文件系统视角理解ROS架构 7
1.3.3 从开源社区视角理解ROS架构 8
1.4 ROS调试工具 8
1.4.1 命令行工具 9
1.4.2 可视化工具 9
1.5 ROS节点通信 10
1.5.1 话题通信方式 12
1.5.2 服务通信方式 15
1.5.3 动作通信方式 19
1.6 ROS的其他重要概念 25
1.7 ROS 2.0展望 28
1.8 本章小结 28
第2章 C++编程范式
2.1 C++工程的组织结构 29
2.1.1 C++工程的一般组织结构 29
2.1.2 C++工程在机器人中的组织结构 29
2.2 C++代码的编译方法 30
2.2.1 使用g++编译代码 31
2.2.2 使用make编译代码 32
2.2.3 使用CMake编译代码 32
2.3 C++编程风格指南 33
2.4 本章小结 34
第3章 OpenCV图像处理
3.1 认识图像数据 35
3.1.1 获取图像数据 35
3.1.2 访问图像数据 36
3.2 图像滤波 37
3.2.1 线性滤波 37
3.2.2 非线性滤波 38
3.2.3 形态学滤波 39
3.3 图像变换 40
3.3.1 射影变换 40
3.3.2 霍夫变换 42
3.3.3 边缘检测 42
3.3.4 直方图均衡 43
3.4 图像特征点提取 44
3.4.1 SIFT特征点 44
3.4.2 SURF特征点 50
3.4.3 ORB特征点 52
3.5 本章小结 54
硬件基础篇
第4章 机器人传感器
4.1 惯性测量单元 56
4.1.1 工作原理 56
4.1.2 原始数据采集 60
4.1.3 参数标定 65
4.1.4 数据滤波 73
4.1.5 姿态融合 75
4.2 激光雷达 91
4.2.1 工作原理 92
4.2.2 性能参数 94
4.2.3 数据处理 96
4.3 相机 100
4.3.1 单目相机 101
4.3.2 双目相机 107
4.3.3 RGB-D相机 109
4.4 带编码器的减速电机 111
4.4.1 电机 111
4.4.2 电机驱动电路 112
4.4.3 电机控制主板 113
4.4.4 轮式里程计 117
4.5 本章小结 118
第5章 机器人主机
5.1 X86与ARM主机对比 119
5.2 ARM主机树莓派3B+ 120
5.2.1 安装Ubuntu MATE 18.04 120
5.2.2 安装ROS melodic 122
5.2.3 装机软件与系统设置 122
5.3 ARM主机RK3399 127
5.4 ARM主机Jetson-tx2 128
5.5 分布式架构主机 129
5.5.1 ROS网络通信 130
5.5.2 机器人程序的远程开发 130
5.6 本章小结 131
第6章 机器人底盘
6.1 底盘运动学模型 132
6.1.1 两轮差速模型 132
6.1.2 四轮差速模型 136
6.1.3 阿克曼模型 140
6.1.4 全向模型 144
6.1.5 其他模型 148
6.2 底盘性能指标 148
6.2.1 载重能力 148
6.2.2 动力性能 148
6.2.3 控制精度 150
6.2.4 里程计精度 150
6.3 典型机器人底盘搭建 151
6.3.1 底盘运动学模型选择 152
6.3.2 传感器选择 152
6.3.3 主机选择 153
6.4 本章小结 155
SLAM篇
第7章 SLAM中的数学基础
7.1 SLAM发展简史 158
7.1.1 数据关联、收敛和一致性 160
7.1.2 SLAM的基本理论 161
7.2 SLAM中的概率理论 163
7.2.1 状态估计问题 164
7.2.2 概率运动模型 166
7.2.3 概率观测模型 171
7.2.4 概率图模型 173
7.3 估计理论 182
7.3.1 估计量的性质 182
7.3.2 估计量的构建 183
7.3.3 各估计量对比 190
7.4 基于贝叶斯网络的状态估计 193
7.4.1 贝叶斯估计 194
7.4.2 参数化实现 196
7.4.3 非参数化实现 202
7.5 基于因子图的状态估计 206
7.5.1 非线性最小二乘估计 206
7.5.2 直接求解方法 206
7.5.3 优化方法 208
7.5.4 各优化方法对比 218
7.5.5 常用优化工具 219
7.6 典型SLAM算法 221
7.7 本章小结 221
第8章 激光SLAM系统
8.1 Gmapping算法 223
8.1.1 原理分析 223
8.1.2 源码解读 228
8.1.3 安装与运行 233
8.2 Cartographer算法 240
8.2.1 原理分析 240
8.2.2 源码解读 247
8.2.3 安装与运行 258
8.3 LOAM算法 266
8.3.1 原理分析 266
8.3.2 源码解读 267
8.3.3 安装与运行 270
8.4 本章小结 270
第9章 视觉SLAM系统
9.1 ORB-SLAM2算法 274
9.1.1 原理分析 274
9.1.2 源码解读 310
9.1.3 安装与运行 319
9.1.4 拓展 327
9.2 LSD-SLAM算法 329
9.2.1 原理分析 329
9.2.2 源码解读 334
9.2.3 安装与运行 337
9.3 SVO算法 338
9.3.1 原理分析 338
9.3.2 源码解读 341
9.4 本章小结 341
第10章 其他SLAM系统
10.1 RTABMAP算法 344
10.1.1 原理分析 344
10.1.2 源码解读 351
10.1.3 安装与运行 357
10.2 VINS算法 362
10.2.1 原理分析 364
10.2.2 源码解读 373
10.2.3 安装与运行 376
10.3 机器学习与SLAM 379
10.3.1 机器学习 379
10.3.2 CNN-SLAM算法 411
10.3.3 DeepVO算法 413
10.4 本章小结 414
自主导航篇
第11章 自主导航中的数学基础
11.1 自主导航 418
11.2 环境感知 420
11.2.1 实时定位 420
11.2.2 环境建模 421
11.2.3 语义理解 422
11.3 路径规划 422
11.3.1 常见的路径规划算法 423
11.3.2 带约束的路径规划算法 430
11.3.3 覆盖的路径规划算法 434
11.4 运动控制 435
11.4.1 基于PID的运动控制 437
11.4.2 基于MPC的运动控制 438
11.4.3 基于强化学习的运动控制 441
11.5 强化学习与自主导航 442
11.5.1 强化学习 443
11.5.2 基于强化学习的自主导航 465
11.6 本章小结 467
第12章 典型自主导航系统
12.1 ros-navigation导航系统 470
12.1.1 原理分析 470
12.1.2 源码解读 475
12.1.3 安装与运行 479
12.1.4 路径规划改进 492
12.1.5 环境探索 496
12.2 riskrrt导航系统 498
12.3 autoware导航系统 499
12.4 导航系统面临的一些挑战 500
12.5 本章小结 500
第13章 机器人SLAM导航综合实战
13.1 运行机器人上的传感器 502
13.1.1 运行底盘的ROS驱动 503
13.1.2 运行激光雷达的ROS驱动 503
13.1.3 运行IMU的ROS驱动 504
13.1.4 运行相机的ROS驱动 504
13.1.5 运行底盘的urdf模型 505
13.1.6 传感器一键启动 506
13.2 运行SLAM建图功能 506
13.2.1 运行激光SLAM建图功能 507
13.2.2 运行视觉SLAM建图功能 508
13.2.3 运行激光与视觉联合建图功能 508
13.3 运行自主导航 509
13.4 基于自主导航的应用 510
13.5 本章小结 511
附录A Linux与SLAM性能优化的探讨
附录B 习题
posted @ 2019-02-17 16:54  小虎哥哥爱学习  阅读(7416)  评论(0编辑  收藏  举报