ROS基础篇
ROS基础篇
ROS:robot operating system,是一款旨在为机器人研究和开发提供代码复用的支持。
- ROS是一款分布式进程(也就是“节点”)框架。
- 支持多种语言,包括Java,C++,Python等。
- 大量的工具,库,协议。
- 优秀的进程通讯机制。
1. ROS的安装
ROS系统虽然叫做系统,但是其寄生于Linux系统之上,在学习期间,可以安装虚拟机进行学习。
- 虚拟机安装:VMware
- Linux系统安装:Ubuntu18.04
- VMtools:VMtools
- ROS安装:Ubuntu18.04安装ROS Melodic
2. ROS的使用
2.1 简单使用
下面将演示基于终端Terminator的简单流程,也可以用vscode。
2.1.1 安装Terminator
由于使用ros时需要打开多个ros终端,则可以安装终端分频工具,
sudo apt install terminator |
对此,该工具常用的快捷键:
Ctrl+Shift+W //关闭当前终端 |
2.1.2 创建工程及依赖项
mkdir -p 自定义空间名称/src # 生成工作空间,其下有一个src目录,用于存放源代码 |
打开终端,依次键入上述代码,将会完成:生成工作空间、进入工作空间、工作空间初始化,创建包及其依赖项。
- roscpp:C++实现库
- rospy:python实现库
- std_msgs:标准消息库
图例:
安装tree工具
,就可以在终端输出文件目录结构,然后我们输入tree
,观察到对应文件目录下,
sudo apt install tree |
2.1.3 输出“Hello World!"
下面演示使用C++实现“Hello World!"。
- 新建
hello.cpp
文件
cd pkg_hello/src # 进入自己创建的包下的src目录下 |
源码:
|
- 对CMakeLists.txt编辑
对src目录下的CMakeLists.txt文件进行编辑:
# 位于137行,取消注释,作用:声明可执行文件,同时将hello.cpp映射到hello上 |
- 编译
cd lancit_ws # 进入工作区 |
- 执行
打开Terminator,输入:
source ./devel/setup.bash # 刷新环境变量 |
右击Terminator空白处,选择水平分割,即新开一终端:
source ./devel/setup.bash # 设置环境变量 |
结果:
2.2 ROS文件架构
2.1.2 ROS 文件系统
ros文件系统,即ROS源代码组织形式,如:
- catkin_workspace:工作区。
- build:编译空间,用于存放CMake和catkin的缓存信息、配置信息和其他中间文件。
- devel:开发空间,用于存放编译后生成的目标文件,包括头文件、动态&静态链接库、可执行文件等。
- src:源码
- package:功能包,包含多个节点、库与配置文件,包名所有字母小写,只能由字母、数字与下划线组成。
- CMakeLists.txt:配置编译规则,比如源文件、依赖项、目标文件。
- package.xml:包信息,比如:包名、版本、作者、依赖项… (以前版本是 manifest.xml)。
- scripts:存储python文件。
- msg:消息通信格式文件。
- srv:服务通信格式文件。
- include:头文件。
- launch:可一次性运行多个节点。
- src:存储C++文件。
- package:功能包,包含多个节点、库与配置文件,包名所有字母小写,只能由字母、数字与下划线组成。
2.1.3 package.xml文件详解
|
2.1.4 CMakeLists.txt文件详解
作用:决定功能包,要依赖哪些package,要编译生成哪些目标,如何编译等。
cmake_minimum_required(VERSION 3.0.2) # 所需cmake最低版本 |
注意:cmakelist文件中函数的顺序不能随意交换。
2.1.5 .launch文件说明
当我们运行多个程序(节点),需要开启多个终端,这样操作显然效率低下,故使用launch文件实现一次启动多个节点。
按照2.1.2内容,在包下新建launch文件夹,新建.launch文件,内容如下:
<launch> |
- node:包含某个节点。
- pkg:功能包。
- type:被运行的节点文件。
- name:给节点命名。
- output:输出信息。
运行launch文件:
roslaunch 包名 launch文件名 |
例如,使用wheeltec科技的16线雷达,使用雷达驱动包,
roslaunch lslidar_driver lslidar_c16.launch |
2.1.6 文件系统指令
”一切基于文件“,对于ROS文件系统的操作都可以像对普通文件那样操作:增、删、查、改、执行。
- 增:
catkin_create_pkg 自定义包名 依赖包 # 创建新的ROS功能包 |
- 删:
sudo apt purge xxx # 删除某个功能包 |
- 查:
rospack list # 列出所有功能包 |
- 改:
rosed 包名 文件名 # 修改功能包文件 |
-
执行:
- roscore:启动主节点,同时输出的还有:rosout 和 parameter server,
- rosout:日志输出。
- parameter server:参数服务器,它并不是一个node,而是存储参数配置的一个服务器。
- 运行ros程序前都需要键入roscore,启动master后,node才能注册和通讯。
roscore # 是ROS的系统先决条件节点和程序的集合,必须运行roscore才能使ROS节点进行通信。
roscore 将启动:
ros master
ros 参数服务器
rosout 日志节点
# 用法:
roscore 或 roscore -p xxxx # 指定端口- rosrun:运行某一节点。
rosrun 包名 可执行文件名 # 运行指定的ROS节点
# 例如
rosrun turtlesim turtlesim_node- roslaunch:
roslaunch 包名 launch文件名 # 执行某个包下的 launch 文件
- roscore:启动主节点,同时输出的还有:rosout 和 parameter server,
2.1.7 计算图
前面介绍的是ROS文件结构,是磁盘上 ROS 程序的存储结构,是静态的,而 ros 程序运行之后,不同的节点之间是错综复杂的,ROS 中提供了一个实用的工具:rqt_graph。
rqt_graph能够创建一个显示当前系统运行情况的动态图形。ROS 分布式系统中**不同进程需要进行数据交互,计算图可以以点对点的网络形式表现数据交互过程。**rqt_graph是rqt程序包中的一部分。
- 安装:
sudo apt install ros-<distro>-rqt |
- 使用:
rosrun rqt_graph rqt_graph |
例如,在下面使用话题通讯时,发送者节点msgs_pub
通过话题simple_chat
向订阅者节点msgs_sub
发送数据:
2.3 ROS节点及函数
我参考了这位大佬的帖子,感谢!:ROS节点
2.3.1 ROS节点
在ROS系统里,最小的进程单元就是节点(node)。
- 一个软件包里可以有多个可执行文件,可执行文件在运行之后就成了一个进程(process),这个进程在ROS中就叫做节点。
- 从程序角度来说,node就是一个可执行文件(通常为C++编译生成的可执行文件、Python脚本)被执行,加载到了内存之中。
- 从功能角度来说,通常一个node负责者机器人的某一个单独的功能。由于机器人的功能模块非常复杂,我们往往不会把所有功能都集中到一个node上,而会采用分布式的方式,把鸡蛋放到不同的篮子里。
- 例如有一个node来控制底盘轮子的运动,有一个node驱动摄像头获取图像,有一个node驱动激光雷达…
如何管理这些繁杂的节点呢,ROS提供给我们的节点管master,master管理中心,主节点。
- node首先master处进行注册,之后master会将该node纳入整个ROS程序中。
- node之间的通信也是先由master进行“牵线”,才能两两的进行点对点通信。
- 当ROS程序启动时,第一步首先启动master,由节点管理器处理依次启动node。
查看节点,rosnode:
# 列出当前运行的node信息 |
2.3.2 node函数
-
ros::init():初始化
- 通过调用
ros::init()
函数来初始化node - 此函数向ROS系统传递命令行参数、定义node名字及其它参数
- 在调用roscpp其它函数前必须先调用
ros::init()
,其函数原型为:
void ros::init(int &argc, char **argv, const std::string &name, uint32_t options=0)
- 通过调用
-
ros::Publisher:发布消息,将节点设置成发布者,并将所发布主题的类型和名称告知节点管理器。
-
ros::Subscriber:订阅消息,将节点设置成接收者,并将所接受主题的类型和名称告知节点管理器。
-
ros::Rate loop_rate:循环速率
-
ros::spin():事件循环
-
ros::spinOnce():单次事件
-
ros::start():启动
-
ros::shutdown():关闭
-
ros::ok():当接受到ctrl+c 信号或者
ros::shutdown()
调用时,为false,终止运行节点。 -
add_executable(pub_node src/pub_node.cpp):最后执行文件节点名、源文件
3. ROS通讯
ROS系统的每个节点都是一个进程,即分布式框架,为实现各节点之间的通讯,数据传输,需要进行节点通讯:
- 话题通讯:发布订阅
- 服务通讯:请求响应
- 参数服务器:参数共享
3.1 话题通讯
3.1.1 理论模型
该模型中涉及到三个角色:
- ROS Master (管理者)
- Talker (发布者)
- Listener (订阅者)
ROS Master 负责保管 Talker 和 Listener 注册的信息,并匹配话题相同的 Talker 与 Listener,帮助 Talker 与 Listener 建立连接,连接建立后,Talker 可以发布消息,且发布的消息会被 Listener 订阅。
整个流程由以下步骤实现:
- Talker注册
Talker启动后,会通过RPC在 ROS Master 中注册自身信息,其中包含所发布消息的话题名称。ROS Master 会将节点的注册信息加入到注册表中。
- Listener注册
Listener启动后,也会通过RPC在 ROS Master 中注册自身信息,包含需要订阅消息的话题名。ROS Master 会将节点的注册信息加入到注册表中。
- ROS Master实现信息匹配
ROS Master 会根据注册表中的信息匹配Talker 和 Listener,并通过RPC 向 Listener 发送 Talker 的 RPC 地址信息。
- Listener向Talker发送请求
Listener 根据接收到的 RPC 地址,通过 RPC 向 Talker 发送连接请求,传输订阅的话题名称、消息类型以及通信协议(TCP/UDP)。
- Talker确认请求
Talker 接收到 Listener 的请求后,也是通过 RPC 向 Listener 确认连接信息,并发送自身的 TCP 地址信息。
- Listener与Talker件里连接
Listener 根据步骤4 返回的消息使用 TCP 与 Talker 建立网络连接。
- Talker向Listener发送消息
连接建立后,Talker 开始向 Listener 发布消息。
注意1:上述实现流程中,前五步使用的 RPC协议,最后两步使用的是 TCP 协议
注意2: Talker 与 Listener 的启动无先后顺序要求
注意3: Talker 与 Listener 都可以有多个
注意4: Talker 与 Listener 连接建立后,不再需要 ROS Master。也即,即便关闭ROS Master,Talker 与 Listern 照常通信。
3.1.2 话题通讯简单操作 (C++)
需求:
编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布文本消息,订阅方订阅消息并将消息内容打印输出。
- 新建包,
mkdir -p test/src |
- 发布方,保存在
test/src/pubsub/src/pub_node.cpp
。
|
- 订阅方,保存在
test/src/pubsub/src/sub_node.cpp
。
|
- 配置 CMakeLists.txt,记住一定是包下src中的。
add_executable(pub_node |
- 执行
- 在工作空间根目录编译:catkin_make。
- 键入
echo "source ~/ros_workspace/devel/setup.bash" >> ~/.bashrc
,下次使用直接source ~/.bashrc
。 - 键入
roscore
,启动主节点。 - 分屏两个终端,依次输入
rosrun pubsub pub_node
、rosrun pubsub sub_node
。 - 结果:
此时工作目录:
- 注意
注意1:不要忘记新建包是在src目录下新建的。
3.1.3 自定义msg
在上面我们通过std_msgs实现了简单文本数据的发送,下面我们演示怎样自定义数据结构。
在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型。
- 例如:String、Int32、Int64、Char、Bool、Empty…
- 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,
- 比如: 激光雷达的信息… std_msgs 由于描述性较差而显得力不从心,这种场景下可以使用自定义的消息类型。
msgs只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:
- int8, int16, int32, int64 (或者无符号类型: uint)*
- float32, float64
- string
- time, duration
- other msg files
- variable-length array[] and fixed-length array[C]
- header
ROS中还有一种特殊类型:Header
,标头包含时间戳和ROS中常用的坐标帧信息,会经常看到msg文件的第一行具有Header标头
。
**自定义数据:**姓名、身高、年龄等。
流程:
- 新建功能包
mkdir -p demo/demo31/src |
- 定义msg文件
功能包下新建 msg 目录,
cd demo_msgs |
添加文件 Person.msg,
string name |
- 编辑配置文件
**package.xml **中添加编译依赖与执行依赖
<build_depend>message_generation</build_depend> |
**CMakeLists.txt **中编辑 msg 相关配置
find_package(catkin REQUIRED COMPONENTS |
## 配置 msg 源文件 |
# 生成消息时依赖于 std_msgs |
# 执行时依赖 |
- 编译
编译后的中间文件查看:
C++ 需要调用的中间文件(…/工作空间/devel/include/包名/xxx.h)
-
Python 需要调用的中间文件(…/工作空间/devel/lib/python3/dist-packages/包名/msg)。
-
后续调用相关 msg 时,是从这些中间文件调用的。
3.1.4 自定义话题通讯 (C++)
需求:
编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布自定义消息,订阅方订阅自定义消息并将消息内容打印输出。
流程:
- vscode 配置
cd demo/demo31 |
避免报错,配置.vscode
下的 c_cpp_properties.json 的 includepath属性:
{ |
- 发布方
|
- 订阅方
|
- 配置 CMakeLists.txt
需要添加 add_dependencies 用以设置所依赖的消息相关的中间文件,注意位置,在catkin_package后。
add_executable(talker src/talker.cpp) |
- 执行
- 在工作空间根目录编译:catkin_make。
- 键入
echo "source ~/demo/demo31/devel/setup.bash" >> ~/.bashrc
,下次使用直接source ~/.bashrc
。 - 键入
roscore
,启动主节点。 - 分屏两个终端,依次输入
rosrun pubsub pub_node
、rosrun pubsub sub_node
。 - 结果:
此时src目录:
3.2 服务通讯
服务通信是基于请求响应模式的,是一种应答机制。
3.2.1 理论模型
服务通信较之于话题通信更简单些,该模型中涉及到三个角色:
- ROS master(管理者), 负责保管 Server 和 Client 注册的信息,并匹配话题相同的 Server 与 Client ,。
- Server(服务端),Server 返回响应信息。
- Client(客户端),Client 发送请求信息。
整个流程由以下步骤实现:
- Server注册
Server 启动后,会通过RPC在 ROS Master 中注册自身信息,其中包含提供的服务的名称。ROS Master 会将节点的注册信息加入到注册表中。
- Client注册
Client 启动后,也会通过RPC在 ROS Master 中注册自身信息,包含需要请求的服务的名称。ROS Master 会将节点的注册信息加入到注册表中。
- ROS Master实现信息匹配
ROS Master 会根据注册表中的信息匹配Server和 Client,并通过 RPC 向 Client 发送 Server 的 TCP 地址信息。
- Client发送请求
Client 根据步骤2 响应的信息,使用 TCP 与 Server 建立网络连接,并发送请求数据。
- Server发送响应
Server 接收、解析请求的数据,并产生响应结果返回给 Client。
注意:
1.客户端请求被处理时,需要保证服务器已经启动;
2.服务端和客户端都可以存在多个。
3.2.2 自定义srv
需求:
服务通信中,客户端提交两个整数至服务端,服务端求和并响应结果到客户端,请创建服务器与客户端通信的数据载体。
- 新建功能包
mkdir -p demo/demo32/src |
- 定义srv文件
功能包下新建 srv 目录,添加 test_msgs.srv 文件,
cd demo_msgs |
服务通信中,数据分成两部分:请求与响应,用---
分割,
# 客户端请求时发送的两个数字 |
- 编辑配置文件
package.xml 中添加编译依赖与执行依赖
<build_depend>message_generation</build_depend> |
CMakeLists.txt 编辑 srv 相关配置
find_package(catkin REQUIRED COMPONENTS |
# 新定义宏,添加自定义service文件 |
注意: 官网没有在 catkin_package 中配置 message_runtime,经测试配置也可以。
- 编译
- 跟自定义msg一样的,下面展示目录结构,
3.2.3 自定义服务通讯 (c++)
- 服务端,保存为
src/AddInts_Server.cpp
。
/* |
- 客户端,保存为
src/AddInts_Client.cpp
。
/* |
- 配置 CMakeLists.txt
add_executable(AddInts_Server src/AddInts_Server.cpp) |
- 执行
- 需要先启动服务:
rosrun 包名 服务
- 然后再调用客户端 :
rosrun 包名 客户端 参数1 参数2
结果:
优化:
-
在客户端发送请求前添加:
client.waitForExistence();
-
或:
ros::service::waitForService("AddInts");
-
这是一个阻塞式函数,只有服务启动成功后才会继续执行。
-
此处可以使用 launch 文件优化,但是需要注意 args 传参特点。
3.3 参数通讯
3.3.1 理论模型
参数服务器实现是最为简单的,该模型如下图所示,该模型中涉及到三个角色:
- ROS Master (管理者)
- Talker (参数设置者)
- Listener (参数调用者)
ROS Master 作为一个公共容器保存参数,Talker 可以向容器中设置参数,Listener 可以获取参数。
整个流程由以下步骤实现:
- Talker 设置参数
Talker 通过 RPC 向参数服务器发送参数(包括参数名与参数值),ROS Master 将参数保存到参数列表中。
- Listener 获取参数
Listener 通过 RPC 向参数服务器发送参数查找请求,请求中包含要查找的参数名。
- ROS Master 向 Listener 发送参数值
ROS Master 根据步骤2请求提供的参数名查找参数值,并将查询结果通过 RPC 发送给 Listener。
参数可使用数据类型:
- 32-bit integers
- booleans
- strings
- doubles
- iso8601 dates
- lists
- base64-encoded binary data
- 字典
注意:参数服务器不是为高性能而设计的,因此最好用于存储静态的非二进制的简单数据
3.3.2 参数通讯 (c++)
**需求:**实现参数服务器参数的增删改查操作。
在 C++ 中实现参数服务器数据的增删改查,可以通过两套 API 实现:
- ros::NodeHandle
- ros::param
下面为具体操作演示,
- 参数服务器新增(修改)参数
/* |
- 参数服务器获取参数
/* |
- 参数服务器删除参数
/* |
3.4 常用命令
当需要自定义节点和其他某个已经存在的节点通信时,如何获取对方的话题、以及消息载体的格式呢?
如下:
- rosnode : 操作节点
- rostopic : 操作话题
- rosservice : 操作服务
- rosmsg : 操作msg消息
- rossrv : 操作srv消息
- rosparam : 操作参数
可以使用-h
选项获取更多帮助。
上述命令是动态的。