目录
4.3.1 rostopic list 以及用命令行控制小海龟移动
1. ROS是什么
ROS是一个机器人原操系统,也可以理解为是一个用于编写机器人软件的灵活框架,集通信机制、开发工具、应用功能以及生态系统于一体,目的就是提高机器人研发中的软件复用率。也就是说可以把别人开发好的一套代码或功能拿去复用,在这基础上还可以做进一步的扩展跟完善,加速机器人的开发,减少重复造轮的时间。
1.1 通信机制
在通讯机制方面,ROS提供了一个非常重要的松耦合分布式通信机制。
1.2 开发工具
命令行工具、可视化工具。
1.3 应用功能
1.4 生态系统
发行版、软件源、ROS wiki、邮件列表、博客。
2. linux系统基本操作
ros的开发基本都基于linux,先简单介绍一下linux的基本操作。
在linux中,使用的最多操作应该就是使用终端执行各种命令了。
打开终端快捷键:Ctri + Alt + T
在终端中输入:pwd (输出当前终端所在的目录)
robot@robot-Vostro-3471:~$ pwd
/home/robot
在终端中输入:cd /文件夹名/ (进入该文件夹下的目录)
robot@robot-Vostro-3471:~$ cd /home/
robot@robot-Vostro-3471:/home$ pwd
/home
在终端中输入:cd .. (进入上一级目录)
robot@robot-Vostro-3471:~$ cd ..
在终端中输入:mkdir 新建文件夹名 (在当前目录下新建文件夹)
robot@robot-Vostro-3471:~$ mkdir test_folder
在终端中输入:ls (列出吧当前文件夹下的所有文件和文件夹)
robot@robot-Vostro-3471:~$ ls
CLionProjects examples.desktop Pictures Templates
Desktop main.cpp proj test_folder
Documents Music Public Videos
Downloads picture SunloginRemote vscodeProjects
在终端中输入:touch 文件名 (在当前文件夹下新建文件,默认为文本格式)
robot@robot-Vostro-3471:~/test_folder$ touch test_file
在终端中输入:mv 文件名 要移动到的路径(把文件从原路径拷贝到另一个路径下,并自带剪切作用,也就是拷贝文件后,原路径不存在此文件)
robot@robot-Vostro-3471:~/test_folder$ mv test_file /home/robot/
在终端中输入:cp 文件名 要复制到的路径/复制后的文件夹名(把文件从原路径拷贝到另一个路径下,原路径依然存在此文件)
robot@robot-Vostro-3471:~$ cp test_file test_folder/test_folder2
在终端中输入:rm 文件名(删除此文件)
robot@robot-Vostro-3471:~$ rm test_file
在终端中输入:rm -r 文件夹名(删除此文件夹)
robot@robot-Vostro-3471:~$ rm -r test_folder
在终端中输入:sudo apt-get updata(根据系统设置的软件源更新软件列表)
robot@robot-Vostro-3471:~$ sudo apt-get updata
sudo是为了提升用户的使用权限。
在终端中输入:rm --help(输出rm的使用方法)
robot@robot-Vostro-3471:~$ rm --help
Usage: rm [OPTION]... [FILE]...
Remove (unlink) the FILE(s).
-f, --force ignore nonexistent files and arguments, never prompt
-i prompt before every removal
-I prompt once before removing more than three files, or
when removing recursively; less intrusive than -i,
while still giving protection against most mistakes
--interactive[=WHEN] prompt according to WHEN: never, once (-I), or
always (-i); without WHEN, prompt always
--one-file-system when removing a hierarchy recursively, skip any
directory that is on a file system different from
that of the corresponding command line argument
--no-preserve-root do not treat '/' specially
--preserve-root do not remove '/' (default)
-r, -R, --recursive remove directories and their contents recursively
-d, --dir remove empty directories
-v, --verbose explain what is being done
--help display this help and exit
--version output version information and exit
By default, rm does not remove directories. Use the --recursive (-r or -R)
option to remove each listed directory, too, along with all of its contents.
To remove a file whose name starts with a '-', for example '-foo',
use one of these commands:
rm -- -foo
rm ./-foo
Note that if you use rm to remove a file, it might be possible to recover
some of its contents, given sufficient expertise and/or time. For greater
assurance that the contents are truly unrecoverable, consider using shred.
GNU coreutils online help: <http://www.gnu.org/software/coreutils/>
Full documentation at: <http://www.gnu.org/software/coreutils/rm>
or available locally via: info '(coreutils) rm invocation'
很多指令都可以用--help来查看使用方法。
3. ROS的核心概念
3.1 节点与节点管理器
每个节点(Node)都是在机器人网络系统中完成一个具体功能的进程。节点之间可以通过一系列的数据传输方式来完成通讯。一个网络系统中会包含很多节点,每个节点的编程语言也不是固定的,可以分布式完成节点的开发,最终只要有统一的通讯方式标准就可以完成整个系统的搭建。其中涉及到很多节点,节点之间的通讯和数据,包括通讯方式,节点的管理等。
- 节点(Node)---- 执行单元
执行具体任务的进程、独立运行的可执行文件(类似于windows系统中的.exe文件);
不同节点可使用不同的编程语言,可分布式运行在不同的主机;
节点在系统中的名称必须是唯一的。
- 节点管理器(ROS Master)---- 控制中心(所有节点的管理者,主要负责节点的管理和参数服务器的维护)
为节点提供命名和注册服务(记录节点的各种信息);
跟踪和记录话题/服务通信,辅助节点相互查找、建立连接(节点之间不知道彼此存在,ROS Master帮助他们建立连接,传输信息);
提供参数服务器,节点使用此服务器存储和检索运行时的参数(全局对象字典,记录全局变量值和变量名,可以由各个节点访问,可以读取配置文件的信息,读取参数的配置等等,主要为节点提供参数服务)。
- 参数(Parameter)---- 全局共享字典
可通过网络访问的共享、多变量字典;
节点使用此服务器来存储和检索运行时的参数;
适合存储静态、非二进制的不会频繁改变的配置参数,不适合存储动态配置的数据。
(使用RPC通信机制。)
3.2 通信方式
节点之间的通讯必然会涉及到不同的通信方式。有两种核心通信方式,话题和服务。根据机器人系统在实际场景的应用,来区分使用 两种通信方式。
3.2.1 话题通信
模型简单,分为发布者和订阅者两部分。单向数据传输,从发布者流向订阅者,数据传输通道为话题。
- 话题(Topic)----异步通信机制
节点之间用来传输数据的重要总线;
使用发布/订阅模型,数据由发布者传输到订阅者,同一个话题的订阅者或发布者可以不唯一;
- 消息(Message)----话题数据
具有一定的类型和数据结构,包括ROS提供的标准类型和用户自定义类型;
使用编程语言无关的.msg文件定义,编译过程中生产对应的代码文件。
3.2.2 服务通信
由于话题通信是异步单向的方式,无法判断数据传输的时效性。在一些情况下,更希望对方在收到数据时可以给出一个回应,于是自带反馈机制的服务通信出现了。
- 服务(Service)----同步通信机制
使用客户端/服务器(C/S)模型,客户端发送请求数据,服务器完成处理后返回应答数据;
使用编程语言无关的.srv文件定义请求和应答数据结构,编译过程中生成对应的代码文件。
3.2.3 两种通信方式的区别
3.3 文件系统
- 功能包(Package)
ROS软件中的基本单元,包含节点源码、配置文件、数据定义。
- 功能包清单(Package manifest)
记录功能包的基本信息,包含作者信息、许可信息、依赖选项、编译标志等。
- 元功能包(Mete Packages)
组织多个用于同一目的功能包。
4. ROS命令行工具的使用
ROS提供了非常丰富的命令行工具,这些工具可以帮助人们实现代码的编写、调试、测试,包括整个系统框架的调试,数据的显示,命令的帮助信息。这些都可以通过ROS命令行工具来实现。(以小海龟为例)
4.1 启动小海龟
4.1.1 启动ROS Master
$ roscore
出现:
4.1.2 启动小海龟仿真器
重新打开一个终端,启动小海龟仿真器
$ rosrun turtlesim turtlesim_node
注意,输入rosrun turtlesim后,按tab键会出现:
出现:
4.1.3 启动小海龟控制节点
重新打开一个终端,启动海龟控制节点
$ rosrun turtlesim turtle_teleop_key
点击上下左右键,出现:
4.1.4 rqt_graph
rqt_graph是一个用来显示系统计算图的工具。
此时,重新打开一个终端,输入:
rqt_graph
出现:
通过这张图可以明显看出,小海龟是由两个节点,和节点之间的话题组成的系统。
4.2 rosnode
rosnode是用来显示系统当中所有节点相关的信息的指令。
在终端中输入rosnode,然后回车,下面会出现帮助信息。
4.2.1 rosnode list
rosnode list是将系统中所有的节点列出来的指令。
$ rosnode list
4.2.2 rosnode info
rosnode info用来查看某一个节点具体的信息。
$ rosnode info 节点
4.3 rostopic
是与话题相关的命令行工具。
4.3.1 rostopic list 以及用命令行控制小海龟移动
打印出当前系统中所有的话题列表。
$ rostopic list
里面包含了小海龟的指令话题名:/turtle1/cmd_vel,键盘控制节点和小海龟访问器的节点就是通过这样的一个话题来做通信的,并完成运动。我们可以通过指令给话题发布数据,令海龟运动。
$ rostopic pub /turtle1/cmd_vel
再双击两次tab键,会自动补全后面的信息。
liner是线速度,angular是角速度,每个速度会分为x,y,z三个方向上的分量。
将liner处的x改为1.0,按换行键。
会发现海龟移动一段距离后停下。想要它一直移动,需要加个循环。
-r 10
r 的意思就是rate,频率,后面跟的数字代表每次发布几次这样的命令,也就是10赫兹,一秒发布10次。
运行后,会发现海龟一直运动,直到在墙边卡住。(这个命令是一直在运行的,只是海龟在墙边移动不了了)
将角速度的z值也换成1.0,会发现海龟一直在画圈。
4.3 rosmsg
4.3.1 rosmsg show
如果想要知道我们发布的话题geometry_msgs/Twist消息的结构是什么,可以使用rosmsg show geometry_msgs/Twist
4.4 rosservice
4.4.1 rosservice list
返回的是在此系统中所有提供服务的内容。服务端都是海龟仿真器,终端是作为客服端请求某个服务的。
其中有个服务为spawn,这个服务是产生一个新的海龟,如果我们想要同时存在两只海龟,可以这样做:
x和y分别代表产生新海龟的x坐标和y坐标(仿真器的左下角是原点),theta是海龟诞生的角度,name是新海龟的名字。
出现了一只新海龟:
此时,再输入rostopic list,就会发现多了一个turtle2。
也可以通过命令行控制小海龟2的运动。
4.5 rosbag
话题记录工具,可以记录我们当前系统里面所有的话题数据,并可以保存下来,下次需要使用时直接复现出来。
4.5.1 rosbag record
使用时后面会接一个-a,也就是all,将所有的数据都保存下来。-o,就是要把要保存的数据保存成一个压缩包,压缩包的名字叫什么,就是-o后面接的内容。
比如:
rosbag record -a -o cmd_record
保存完毕后,按ctrl+c返回。
文件默认保存在当前终端的默认路径下面。
4.5.2 rosbag play
现在可以尝试复现。
先把之前的小海龟关掉,重新打开一个。
$ roscore
$ rosrun turtlesim turtlesim_node
开始复现:
$ rosbag play cmd_record_2020-10-08-15-28-12.bag
结果:
5. 创建工作空间与功能包
5.1 工作空间概念
工作空间是ros中非常重要的一个概念,它是一个存放工程开发相关文件的文件夹。可以分为四个主要的文件夹:
src:代码空间(Source Space)。用来放置功能包的,所有功能包的代码,配置文件等等。
build:编译空间(Build Space)。放置在编译过程中产生的一些中间文件。
devel:开发空间(Development Space)。放置编译生成的可执行文件,一些库,还包括一些脚本等等。
install:安装空间(Install Space)。用指令安装成功的一些命令,一些结果等等。
开发空间和安装空间里面的内容是有一定重复的,在ros2里面,针对这一点做了很多修正,只保留了install空间。但在ros1里面,devel开发空间还是存在的,大部分情况下在做开发时,install安装空间是用不到的。
5.2 创建工作空间并编译
如何在自己的系统中创建工作空间并完成编译呢?
5.2.1 创建工作空间
$ mkdir -p ~/catkin_ws/src
先使用mkdir来创建一个文件夹,这个文件夹的名字叫做catkin_ws,这个名字根据自己喜好和需求可自定义。但后面的src是不能自定义的,只能叫做src。
创建好之后出现:
$ cd ~/catkin_ws/src
然后使用cd进入到src里面。
$ catkin_init_workspace
使用catkin_init_workspace把当前文件夹变成ros工作空间属性。
如下所示:
5.2.2 编译工作空间
$ cd ~/catkin_ws/
回到工作空间的根目录下做编译。
$ catkin_make
如下:
但此时去看catkin_ws中的文件夹,会发现没有install文件夹。
再使用catkin_make install:
此时,四个文件夹就都出现了。
src中放置的是功能包的源码和配置文件(新建的工作空间只有框架,具体的功能还未搭建),现在只有初始配置文件:
install是安装空间,里面会放置最终编译生成的可执行文件:
devel是开发空间,放置在开发过程中的一些可执行文件,包括库。功能与install类似,区别在于一个是开发中,一个是开发完成后:
build是在编译过程中的一些中间文件(8进制文件),基本上使用不到的:
编译成功之后再去设置环境变量。
5.2.3 设置环境变量
因为很多的可执行文件都是在工作空间中放置的,为了让自己的系统找得到这些可执行文,都需要去设置环境变量。
$ source devel/setup.bash
设置完成后,再检查环境变量。
$ echo $ROS_PACKAGE_PATH
5.3 创建和编译功能包
5.3.1 创建功能包
在创建源码时,一定要创建功能包,功能包是ros里放置源码的最小单元。所有源码都必须放置到功能包中。要注意,同一个工作空间下,不允许存在同名功能包;不同工作空间下,允许存在同名功能包。
指令结构:
$ catkin_create_pkg<package_name>[depend1][depend2][depend3]
创建功能包的指令是catkin_create_pkg,后面要跟几个参数。第一个参数是创建的功能包的名字package_name,后面紧跟着很多依赖,比如这里的depend1,depend2,depend3等等。这些依赖就是此功能包在编译时需要依赖ros里哪些其他的功能包。比如下面:
$ cd ~/catkin_ws/src
$ catkin_create_pkg test_pkg std_msgs rospy roscpp
这个test_pkg需要依赖std_msgs(ros标准的消息结构),rospy(python接口),roscpp(c++接口)。也就是需要用到哪些库,就需要在后面跟着这样的依赖。
功能包一定要放置到src里面。
执行上述指令:
结果:
功能包test_pkg出现了。下面是test_pkg里的文件和文件夹。
src是用来放置功能包的代码,比如cpp文件以及其他的代码文件都可以放到里面。
include是用来放置头文件的,比如c++里面的.h文件等。
而CMakeLists.txt和package.xml是每个功能包都必须存在的两个文件,有这两个文件才标志着这个文件夹是个功能包,而不是一个普通文件夹。
- package.xml文件
文件内容是通过xml语言描述的一些跟功能包相关的信息,比如功能包的名字,版本号,描述信息,维护者的email, 开源许可证,以及功能包的依赖信息等等:
- CMakeLists.txt文件
这个文件是用来描述功能包的编译规则,使用cmake语法。
5.3.2 编译功能包
现在我们有了新的功能包就可以来做编译了,虽然这个功能包里没有实际的内容。
$ cd ~/catkin_ws
首先一定要先回到catkin_ws目录下,然后用catkin_make编译工作空间。
$ catkin_make
如果想要运行功能包里的程序的话,要先设置一下工作空间的环境变量:
$ source ~/catkin_ws/devel/setup.bash
这是针对catkin_ws工作空间的环境变量的设置。只有设置环境变量之后我们才能去让系统找到工作空间,并找到工作空间中对应的一些功能包。
可以打开环境变量检查一下:
$ echo $ROS_PACKAGE_PATH
把功能包放到src里面,就可以通过环境变量找到功能包了。环境变量是非常重要的,一定不要忘记设置。
6. 发布者Publisher的编程实现
6.1 什么是Publisher
什么是Publisher呢?
想要知道什么是发布者,首先要知道系统的节点关系。以海龟为例:
系统中存在两个节点:teleop_turtle 和 turtlesim,其中teleop_turtle节点创建了一个Publisher,用于发布键盘控制的速度指令,turtlesim节点创建了一个Subscriber,用于订阅速度指令,实现小乌龟在界面上的运动。这里的话题是/turtlr1/cmd_vel。也就是说,Publisher的主要作用是针对制定话题发布特定数据类型的消息。Publisher和Subscriber是ROS系统中最基本、最常用的通信方式。
之前主要是通过命令行的形式或者键盘控制的形式来发布指令,现在来看一下如何通过程序来实现发布者,并发布控制海龟的指令。要实现的模型如下:
ROS master是管理所有节点的,其中有两个主要节点,一个是Subscriber,就是海龟仿真器;另外一个就是Publisher,数值的发布者,要通过程序去实现,并发布一个Massage,这个Massage的数据结构是Twist消息,里面会分成线速度和角速度。通过Topic(/turtle1/cmd_yel)总线把数据传送给Subscriber,Subscriber再去订阅得到的数据,控制海龟运动。
6.2 创建功能包
首先第一步一定是要先创建功能包,里面放置要实现的代码。
要将功能包放到src中。
$ cd ~/catkin_ws/src
再创建功能包,并添加依赖:
$ catkin_create_pkg learning_topic roscpp rospy std_msgs geometry_msgs turtlesim
6.3 创建发布者代码(C++)
如何具体实现一个发布者:
- 初始化ROS节点;
- 向ROS Msater注册节点信息,包括发布的话题名和话题中的消息类型;
- 创建消息数据;
- 按照一定频率循环发布消息。
/**
* 该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist, 主要是为了控制海龟完成速度指令的运动。
*/
//ros相关API的头文件
#include <ros/ros.h>
//线速度角速度相关消息的头文件
#include <geometry_msgs/Twist.h>
int main(int argc, char **argv)
{
// ROS节点初始化
//通过ros::init完成节点的初始化,里面要设置节点的名字(velocity_publisher)。要注意节点名不可以重复。
ros::init(argc, argv, "velocity_publisher");
// 创建节点句柄
//节点句柄主要是来管理ros节点相关的API资源
ros::NodeHandle n;
// 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10
ros::Publisher turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);
// 设置循环的频率
ros::Rate loop_rate(10);
int count = 0;
//封装数据,发布数据,延时满足程序进入下次循环
while (ros::ok())
{
// 初始化geometry_msgs::Twist类型的消息
geometry_msgs::Twist vel_msg;
//线速度
vel_msg.linear.x = 0.5;
//角速度
vel_msg.angular.z = 0.2;
// 发布消息
turtle_vel_pub.publish(vel_msg);
ROS_INFO("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]",
vel_msg.linear.x, vel_msg.angular.z);
// 按照循环频率延时
loop_rate.sleep();
}
return 0;
}
6.4 配置发布者代码编译规则
如何具体配置CMakeList.txt中的编译规则呢?
- 设置需要编译的代码和生成的可执行文件;
- 设置链接库;
打开learning_topic中的CMakeList.txt,然后找到一个合适位置。(CMakeList.txt文件中默认存在很多注释,基本上描述了CMakeList.txt的编译规则和使用方式。)
将下面两句代码放在Build最后,Install之前。
add_executable(velocity_publisher src/velocity_publisher.cpp)
target_link_libraries(velocity_publisher ${catkin_LIBRARIES})
add_executable是用来描述把哪一个程序文件编译成可执行文件。
target_link_libraries是用来帮助生成的可执行文件和ros的一些库做链接。
6.5 编译并运行发布者
编译就要回到工作空间目录下:
$ cd ~/catkin_ws
做编译:
$ catkin_make
设置环境变量:
source devel/setup.bash
在系统根目录下,按ctrl+h,会出现隐藏文件,找到.bashrc文件并打开,在最后面添上自己工作空间的环境目录:
source /home/robot/catkin_ws/devel/setup.bash
保存即可,这样就不用每次都在终端里面输入设置环境变量的指令。但要重新启动终端才能生效。
打开ros master:
$ roscore
运行海龟仿真器:
$ rosrun turtlesim turtlesim_node
运行程序:
$ rosrun learning_topic velocity_publisher
运行结果:
可以在目录:/home/robot/catkin_ws/devel/lib/learning_topic下找到生成的可执行文件:
7. 订阅者Subscriber的编程实现
通过海龟仿真器来发布数据,实现一个订阅者订阅海龟的位姿信息。由ROS Master管理两个节点,一个是Subscriber,也就是这节要实现的订阅者程序,另一个是Publisher,即海龟仿真器。两者之间要传输Message,也就是海龟的Pose信息,话题是turtle1/Pose,整个数据传输的方向是从turtlesim到Pose Listener。
7.1 创建订阅者代码(C++)
如何具体实现一个订阅者?
- 初始化ROS节点;
- 订阅需要的话题;
- 循环等待话题消息,接收到消息后进入回调函数;
- 在回调函数中完成消息处理。
/**
* 该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose
*/
#include <ros/ros.h>
//消息类型头文件
#include "turtlesim/Pose.h"
// 接收到订阅的消息后,会进入消息回调函数
void poseCallback(const turtlesim::Pose::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("Turtle pose: x:%0.6f, y:%0.6f", msg->x, msg->y);
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "pose_subscriber");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback
ros::Subscriber pose_sub = n.subscribe("/turtle1/pose", 10, poseCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
7.2 配置订阅者代码编译规则
如何具体配置CMakeList.txt中的编译规则呢?
- 设置需要编译的代码和生成的可执行文件;
- 设置链接库;
打开learning_topic中的CMakeList.txt,然后找到一个合适位置。(CMakeList.txt文件中默认存在很多注释,基本上描述了CMakeList.txt的编译规则和使用方式。)
将下面两句代码放在Build最后,Install之前。
add_executable(pose_subscriber src/pose_subscriber.cpp)
target_link_libraries(pose_subscriber ${catkin_LIBRARIES})
7.3 编译并运行订阅者
编译就要回到工作空间目录下:
$ cd ~/catkin_ws
做编译:
$ catkin_make
打开ros master:
$ roscore
运行海龟仿真器:
$ rosrun turtlesim turtlesim_node
运行程序:
$ rosrun learning_topic pose_subscriber
$ rosrun learning_topic velocity_publisher
运行订阅者终端中显示的是发布者程序控制的海龟的位置信息。
运行结果:
8. 话题消息的定义与使用
无论是给海龟发布输入指令的Twist消息,还是去订阅海龟位姿的Pose消息。Twist和Pose都是在海龟系统中定义好的,直接去使用。如果在自己的开发当中,这些已经定义好的消息没有办法满足我们使用要求,应该怎么办?
可以自己来定义消息类型。
完成消息定义:
传输一个personal个人信息,包括姓名,年龄,性别等等。Publisher会来发布这个人的信息。Subscriber会接收这个人的信息。通过topic,也就是person_info把person的个人信息在learning_topic中定义。
8.1 自定义话题消息
8.1.1 定义msg文件
这里主要是针对person做描述:
/*
*Person.msg
*/
string name
uint8 sex
uint8 age
uint8 unknown = 0
uint8 male = 1
uint8 female = 2
(这个文件与语言无关。)
打开工作空间里面的learning_topic,新建文件夹msg:
与消息有关的定义,都放在msg文件夹里面。
打开msg文件夹,创建Person.msg文件:
把上面的内容复制进去。ros在编译时,就会根据Person.msg定义变成C++或Python等不同语言的程序。
完成数据接口的定义后,还要针对这个定义来设置一些编译的规则。
8.1.2 在package.xml中添加功能包依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
打开learning_topic下的package.xml,在依赖最后添上上面两段代码:
一个是编译依赖,一个是运行依赖。编译依赖会依赖动态产生message的功能包,运行依赖会依赖动态运行时的massgae的功能包。
8.1.3 在CMakeList.txt中添加编译依赖
find_package(...... message_generation)
add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)
catkin_package(...... message_runtime)
主要有三步。
第一步,先把learning_topic中的CMakeList.txt打开,在find_package中添加message功能包:
第二步,要在CMakeLists里面添加一个把.msg文件编译成对应不同语言程序文件的配置项:
第一句是将定义的Person.msg作为数据接口,会针对这个接口做编译。第二句是在编译Person.msg文件时会依赖哪些ros已有的库或包。
最后一步,要在catkin_package中创建动态运行时的massgae依赖,将CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs turtlesim的注释去掉,后面加上message_runtime:
现在就完成了.msg文件的基本的配置。
8.1.4 编译生成语言相关文件
`回到工作空间的根目录下进行编译:
$ catkin_make
编译结束。
.msg文件编译生成的代码文件在/home/robot/catkin_ws/devel/include/learning_topic下:
编译结束后就要调用.msg编译生产的头文件,如何通过程序来使用它呢?
这里面会涉及到发布者和订阅者的创建,下面是c++的实现。
8.2 创建和编译发布者和订阅者
8.2.1 创建发布者和订阅者
发布者:
/**
* 该例程将发布/person_info话题,自定义消息类型learning_topic::Person
*/
#include <ros/ros.h>
#include "learning_topic/Person.h"
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "person_publisher");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10
ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info", 10);
// 设置循环的频率
ros::Rate loop_rate(1);
int count = 0;
while (ros::ok())
{
// 初始化learning_topic::Person类型的消息
learning_topic::Person person_msg;
person_msg.name = "Tom";
person_msg.age = 18;
person_msg.sex = learning_topic::Person::male;
// 发布消息
person_info_pub.publish(person_msg);
ROS_INFO("Publish Person Info: name:%s age:%d sex:%d",
person_msg.name.c_str(), person_msg.age, person_msg.sex);
// 按照循环频率延时
loop_rate.sleep();
}
return 0;
}
订阅者:
/**
* 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person
*/
#include <ros/ros.h>
#include "learning_topic/Person.h"
// 接收到订阅的消息后,会进入消息回调函数
void personInfoCallback(const learning_topic::Person::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("Subcribe Person Info: name:%s age:%d sex:%d",
msg->name.c_str(), msg->age, msg->sex);
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "person_subscriber");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
配置代码编译规则:
add_executable(person_publisher src/person_publisher.cpp)
target_link_libraries(person_publisher${catkin_LIBRARIES})
add_dependencies(person_publisher${PROJECT_NAME}_generate_messages_cpp)
add_executable(person_subscriber src/person_subscriber.cpp)
target_link_libraries(person_subscriber${catkin_LIBRARIES})
add_dependencies(person_subscriber${PROJECT_NAME}_generate_messages_cpp)
将上面六行代码都拷贝到CMakeLists.txt中:
add_dependencies是与生成的头文件做连接的。(想要与自定义消息连接的话,必须要添加这样一句代码)
8.2.2 编译发布者和订阅者
$ cd ~/catkin_ws/
$ catkin_make
$ roscore
$ rosrun learning_topic person_subscriber
$ rosrun learning_topic person_publisher
依然是在工作空间根目录下编译:
然后运行程序:
(打开三个终端分别输入下面代码运行)
$ roscore
$ rosrun learning_topic person_subscriber
$ rosrun learning_topic person_publisher
如果此时将ros master关掉,是不会影响上面两者的信息传输的,因为ros master只是起到建立连接的作用,连接建立完成后,ros master作用就不在存在了。(不包括访问参数服务器,有第三个节点加入进来等特殊情况)
9. 客户端Client的编程实现
ros中的另一种通信方式,服务通信。
下面是service(服务)模型:
如何通过程序的方式发布请求,实现客户端让海龟仿生器来产生新的海龟。
首先,Server端依然是海龟仿生器,Client端是需要实现的程序。在Client会实现一个客服端请求的节点,会发布一个产生海龟的request请求,然后发送给Server端,Server端收到后会产生一个新的海龟,并回馈一个response给Client端。Client端就会知道有没有产生新的海龟。中间Service的名字是/spawn,里面用到Service的消息结构叫做turtlesim::Spawn,这是在turtlesim功能包里自定义的消息结构。整个节点依然是由ROS Master来做管理。
9.1 创建功能包
为了和话题模型区别开,将会单独建立一个新功能包。
先进入工作空间的src目录下:
$ cd ~/catkin_ws/src
创建功能包:
$ catkin_create_pkg learning_service roscpp rospy std_msgs geometry_msgs turtlesim
9.2 创建客户端代码
代码:
/**
* 该例程将请求/spawn服务,服务数据类型turtlesim::Spawn
*/
#include <ros/ros.h>
#include <turtlesim/Spawn.h>
int main(int argc, char** argv)
{
// 初始化ROS节点
ros::init(argc, argv, "turtle_spawn");
// 创建节点句柄
ros::NodeHandle node;
// 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的service
ros::service::waitForService("/spawn");//阻塞型服务,要确定spawn服务是存在的,并在提供服务中
ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");//创建客户端,是用来给spawn这个服务发送请求的,请求类型是turtlesim::Spawn
// 初始化turtlesim::Spawn的请求数据(封装请求数据)
turtlesim::Spawn srv;
srv.request.x = 2.0;
srv.request.y = 2.0;
srv.request.name = "turtle2";
// 请求服务调用
ROS_INFO("Call service to spwan turtle[x:%0.6f, y:%0.6f, name:%s]",
srv.request.x, srv.request.y, srv.request.name.c_str());
//call方法发送请求
add_turtle.call(srv);//阻塞型方法
// 显示服务调用结果
ROS_INFO("Spwan turtle successfully [name:%s]", srv.response.name.c_str());
return 0;
};
如何实现一个客户端:
- 初始化ROS节点;
- 创建一个Client实例;
- 发布服务请求数据;
- 等待Server处理之后的应答结果。
9.3 配置客户端代码编译规则
如何配置CMakeList.txt中的编译规则
- 设置需要编译的代码和生成的可执行文件;
- 设置链接库;
add_executable(turtle_spawn src/turtle_spawn.cpp)
target_link_libraries(turtle_spawn ${catkin_LIBRARIES})
将上面两行代码放入learning_service下的CMakeList.txt中:
9.4 编译并运行客户端
使用以下命令:
$ cd ~/catkin_ws
$ catkin_make
$ roscore
$ rosrun turtlesim turtlesim_node
$ rosrun learning_service turtle_spawn
进入工作空间根目录下,并编译功能包:
在devel的lib下面会出现一个learning_service,里面是turtle_spawn.cpp编译后生成的可执行文件:
运行客户端结果:
10. 服务端Server的编程实现
在ROS Master的管理下有两个节点,一个Client,一个Server,要实现的关键是Server一端。Server一端主要是通过Topic给海龟发送速度的指令,Service是控制Server是否要给海龟发指令,而Client相当于海龟运动停止的开关,发一个Request海龟运动,再发一个运动停止,Server端接收这样的指令并完成topic,也就是海龟运动指令的发送。
10.1 创建服务器代码(C++)
10.2 配置服务器代码编译规则
10.3 编译并运行服务器