ROS1-Noetic学习指南
ROS基础配置
准备工作
创建工作空间并初始化
1 | mkdir -p 自定义空间名称/src |
首先会创建一个工作空间以及一个 src 子目录,然后再进入工作空间调用 catkin_make 命令编译
进入src创建ros包并添加依赖
1 | cd src |
在工作空间下生成一个功能包,该功能包依赖于 roscpp、rospy 与 std_msgs,其中roscpp是使用C++实现的库,而rospy则是使用python实现的库,std_msgs是标准消息库,创建ROS功能包时,一般都会依赖这三个库实现
HelloWorld(C++版)
进入 ros 包的 src 目录编辑源文件
1 | cd 自定义的包 |
C++源码实现(文件名自定义)
1 |
|
编辑 ros 包下的 Cmakelist.txt文件
1 | add_executable(源文件名 |
进入工作空间目录并编译
1 | cd 自定义工作空间名称 |
生成 build devel ….
执行
先启动命令行1:
roscore
再启动命令行2:
cd 工作空间
source ./devel/setup.bash
rosrun 包名 C++节点
命令行输出: Hello World!
PS:source ~/工作空间/devel/setup.bash可以添加进.bashrc文件,使用上更方便
添加方式1: 直接使用 gedit 或 vi 编辑 .bashrc 文件,最后添加该内容
添加方式2:echo “source ~/工作空间/devel/setup.bash” >> ~/.bashrc
HelloWorld(Python版)
进入 ros 包添加 scripts 目录并编辑 python 文件
1 | cd ros包 |
新建 python 文件: (文件名自定义)
1 | #! /usr/bin/env python |
为 python 文件添加可执行权限
1 | chmod +x 自定义文件名.py |
编辑 ros 包下的 CamkeList.txt 文件
1 | catkin_install_python(PROGRAMS scripts/自定义文件名.py |
进入工作空间目录并编译
1 | cd 自定义空间名称 |
进入工作空间目录并执行
先启动命令行1:
roscore
再启动命令行2:
cd 工作空间
source ./devel/setup.bash
rosrun 包名 自定义文件名.py
输出结果:HelloWorld!
ROS集成开发环境搭建
在 ROS 中,需要频繁的使用到终端,且可能需要同时开启多个窗口,推荐一款较为好用的终端Terminator
1 | sudo apt install terminator |
vscode 中编译 ros
快捷键 ctrl + shift + B 调用编译,选择:catkin_make:build
可以点击配置设置为默认,修改.vscode/tasks.json 文件
1 | { |
创建 ROS 功能包
选定 src 右击 —> create catkin package
- 设置包名xxx
- 添加依赖roscpp rospy std_msgs
PS1: 如果没有代码提示
修改 .vscode/c_cpp_properties.json
设置 “cppStandard”: “c++17”
PS2: main 函数的参数不可以被 const 修饰
PS3: 当ROS__INFO 终端输出有中文时,会出现乱码
INFO: ????????????????????????
解决办法:在函数开头加入下面代码的任意一句
1 | setlocale(LC_ALL, ""); |
Python的scripts代码需要可执行权限:*chmod +x .p
PS:
如果不编译直接执行 python 文件,会抛出异常。
第一行解释器声明,可以使用绝对路径定位到 python3 的安装路径 #! /usr/bin/python3,但是不建议
建议使用 #!/usr/bin/env python 但是会抛出异常 : /usr/bin/env: “python”: 没有那个文件或目录
解决1: #!/usr/bin/env python3 直接使用 python3 但存在问题: 不兼容之前的 ROS 相关 python 实现
解决2: 创建一个链接符号到 python 命令:sudo ln -s /usr/bin/python3 /usr/bin/python
launch文件演示
- 需求
一个程序中可能需要启动多个节点,比如:ROS内置的小乌龟案例,如果要控制乌龟运动,要启动多个窗口,分别启动roscore、乌龟界面节点、键盘控制节点。如果每次都调用 rosrun 逐一启动,显然效率低下,如何优化?
官方给出的优化策略是使用 launch 文件,可以一次性启动多个 ROS 节点。
- 实现
选定功能包右击 —> 添加 launch 文件夹
选定 launch 文件夹右击 —> 添加 launch 文件
编辑 launch 文件内容
1 | <launch> |
node —> 包含的某个节点
pkg —–> 功能包
type —-> 被运行的节点文件
name –> 为节点命名
output-> 设置日志的输出目标
运行 launch 文件
roslaunch 包名 launch文件名
运行结果: 一次性启动了多个节点
ROS架构
立足不同的角度,对ROS架构的描述也是不同的,一般我们可以从设计者、维护者、系统结构与自身结构4个角度来描述ROS结构:
- 设计者
ROS设计者将ROS表述为“ROS = Plumbing + Tools + Capabilities + Ecosystem”
Plumbing: 通讯机制(实现ROS不同节点之间的交互)
Tools:工具软件包(ROS中的开发和调试工具)
Capabilities:机器人高层技能(ROS中某些功能的集合,比如:导航)
Ecosystem:机器人生态系统(跨地域、跨软件与硬件的ROS联盟)
- 维护者
立足维护者的角度: ROS 架构可划分为两大部分
main:核心部分,主要由 Willow Garage 和一些开发者设计、提供以及维护。它提供了一些分布式计算的基本工具,以及整个ROS的核心部分的程序编写。
universe:全球范围的代码,有不同国家的ROS社区组织开发和维护。一种是库的代码,如OpenCV、PCL等;库的上一层是从功能角度提供的代码,如人脸识别,他们调用下层的库;最上层的代码是应用级的代码,让机器人完成某一确定的功能。
- 系统架构
立足系统架构: ROS 可以划分为三层
OS 层,也即经典意义的操作系统
ROS 只是元操作系统,需要依托真正意义的操作系统,目前兼容性最好的是 Linux 的 Ubuntu,Mac、Windows 也支持 ROS 的较新版本
中间层
是 ROS 封装的关于机器人开发的中间件,比如: 基于 TCP/UDP 继续封装的 TCPROS/UDPROS 通信系统, 用于进程间通信 Nodelet,为数据的实时性传输提供支持, 另外,还提供了大量的机器人开发实现库,如:数据类型定义、坐标变换、运动控制….
应用层
功能包,以及功能包内的节点,比如: master、turtlesim的控制与运动节点…
- 自身结构
就 ROS 自身实现而言: 也可以划分为三层
文件系统
ROS文件系统级指的是在硬盘上面查看的ROS源代码的组织形式
计算图
ROS 分布式系统中不同进程需要进行数据交互,计算图可以以点对点的网络形式表现数据交互过程,计算图中的重要概念: 节点(Node)、消息(message)、通信机制_主题(topic)、通信机制_服务(service)
开源社区
ROS的社区级概念是ROS网络上进行代码发布的一种表现形式
ROS文件系统
1 | WorkSpace --- 自定义的工作空间 |
- package.xml
该文件定义有关软件包的属性,例如软件包名称,版本号,作者,维护者以及对其他catkin软件包的依赖性。请注意,该概念类似于旧版 rosbuild 构建系统中使用的manifest.xml文件。
1 | |
- CMakelists.txt
文件CMakeLists.txt是CMake构建系统的输入,用于构建软件包。任何兼容CMake的软件包都包含一个或多个CMakeLists.txt文件,这些文件描述了如何构建代码以及将代码安装到何处。
1 | cmake_minimum_required(VERSION 3.0.2) #所需 cmake 版本 |
ROS文件系统相关命令
- 增
catkin_create_pkg 自定义包名 依赖包 === 创建新的ROS功能包
sudo apt install xxx === 安装 ROS功能包
- 删
sudo apt purge xxx ==== 删除某个功能包
- 查
rospack list === 列出所有功能包
rospack find 包名 === 查找某个功能包是否存在,如果存在返回安装路径
roscd 包名 === 进入某个功能包
rosls 包名 === 列出某个包下的文件
apt search xxx === 搜索某个功能包
- 改
rosed 包名 文件名 === 修改功能包文件
需要安装 vim
比如:rosed turtlesim Color.msg
- 执行
roscore
roscore === 是 ROS 的系统先决条件节点和程序的集合, 必须运行 roscore 才能使 ROS 节点进行通信。
roscore 将启动:
ros master
ros 参数服务器
rosout 日志节点
用法:
1 | roscore |
或(指定端口号)
1 | roscore -p xxxx |
rosrun
rosrun 包名 可执行文件名 === 运行指定的ROS节点
比如:rosrun turtlesim turtlesim_node
roslaunch
roslaunch 包名 launch文件名 === 执行某个包下的 launch 文件
ROS计算图
- 计算图简介
ROS文件结构是磁盘上 ROS 程序的存储结构,是静态的,而 ros 程序运行之后,不同的节点之间是错综复杂的,ROS 中提供了一个实用的工具: rqt_graph。
rqt_graph能够创建一个显示当前系统运行情况的动态图形。ROS 分布式系统中不同进程需要进行数据交互,计算图可以以点对点的网络形式表现数据交互过程。rqt_graph是rqt程序包中的一部分。
- 计算图安装
如果前期把所有的功能包(package)都已经安装完成,则直接在终端窗口中输入
1 | rosrun rqt_graph rqt_graph |
如果未安装则在终端(terminal)中输入
1 | $ sudo apt install ros-<distro>-rqt |
请使用你的ROS版本名称(比如:kinetic、melodic、Noetic等)来替换掉<distro>
。
例如当前版本是 Noetic,就在终端窗口中输入
1 | $ sudo apt install ros-noetic-rqt |
ROS通信机制
机器人是一种高度复杂的系统性实现,在机器人上可能集成各种传感器(雷达、摄像头、GPS…)以及运动控制实现,为了解耦合,在ROS中每一个功能点都是一个单独的进程,每一个进程都是独立运行的。更确切的讲,ROS是进程(也称为Nodes)的分布式框架。 因为这些进程甚至还可分布于不同主机,不同主机协同工作,从而分散计算压力。
ROS 中的基本通信机制主要有如下三种实现策略:
话题通信(发布订阅模式)
服务通信(请求响应模式)
参数服务器(参数共享模式)
话题通信
话题通信是ROS中使用频率最高的一种通信模式,话题通信是基于发布订阅模式的,也即:一个节点发布消息,另一个节点订阅该消息。话题通信的应用场景也极其广泛,比如下面一个常见场景:
机器人在执行导航功能,使用的传感器是激光雷达,机器人会采集激光雷达感知到的信息并计算,然后生成运动控制信息驱动机器人底盘运动。
概念:以发布订阅的方式实现不同节点之间数据交互的通信模式。
作用:用于不断更新的、少逻辑处理的数据传输场景。
理论模型
话题通信实现模型是比较复杂的,该模型如下图所示,该模型中涉及到三个角色:
- 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 照常通信。
话题通信基本操作A(C++)
需求:编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布文本消息,订阅方订阅消息并将消息内容打印输出。
- 发布方
1 | // 1.包含头文件 |
- 订阅方
1 | // 1.包含头文件 |
- 配置 CMakeLists.txt
1 | add_executable(Hello_pub |
- 执行
1.启动 roscore;
2.启动发布节点;
3.启动订阅节点。
运行结果与引言部分的演示案例1类似。
- 注意
补充0:
- vscode 中的 main 函数 声明 int main(int argc, char const *argv[]){},默认生成 argv 被 const 修饰,需要去除该修饰符
补充1:
ros/ros.h No such file or directory …..
检查 CMakeList.txt find_package 出现重复,删除内容少的即可
参考资料:https://answers.ros.org/question/237494/fatal-error-rosrosh-no-such-file-or-directory/
补充2:
订阅时,第一条数据丢失
原因: 发送第一条数据时, publisher 还未在 roscore 注册完毕
解决: 注册后,加入休眠 ros::Duration(3.0).sleep(); 延迟第一条数据的发送
PS:可以使用 rqt_graph 查看节点关系。
话题通信基本操作B(Python)
- 发布方
1 | #! /usr/bin/env python |
订阅方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35#! /usr/bin/env python
"""
需求: 实现基本的话题通信,一方发布数据,一方接收数据,
实现的关键点:
1.发送方
2.接收方
3.数据(此处为普通文本)
消息订阅方:
订阅话题并打印接收到的消息
实现流程:
1.导包
2.初始化 ROS 节点:命名(唯一)
3.实例化 订阅者 对象
4.处理订阅的消息(回调函数)
5.设置循环调用回调函数
"""
#1.导包
import rospy
from std_msgs.msg import String
def doMsg(msg):
rospy.loginfo("I heard:%s",msg.data)
if __name__ == "__main__":
#2.初始化 ROS 节点:命名(唯一)
rospy.init_node("listener_p")
#3.实例化 订阅者 对象
sub = rospy.Subscriber("chatter",String,doMsg,queue_size=10)
#4.处理订阅的消息(回调函数)
#5.设置循环调用回调函数
rospy.spin()添加可执行权限
终端下进入 scripts 执行:chmod +x *.py配置 CMakeLists.txt
1
2
3
4
5catkin_install_python(PROGRAMS
scripts/talker_p.py
scripts/listener_p.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)执行
1.启动 roscore;
2.启动发布节点;
3.启动订阅节点。
运行结果与引言部分的演示案例1类似。
PS:可以使用 rqt_graph 查看节点关系。
话题通信自定义msg
在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty….
ROS中还有一种特殊类型:Header,标头包含时间戳和ROS中常用的坐标帧信息。会经常看到msg文件的第一行具有Header标头。
- 定义msg文件
功能包下新建 msg 目录,添加文件 Person.msg
1 | string name |
- 编辑配置文件
package.xml中添加编译依赖与执行依赖
1 | <build_depend>message_generation</build_depend> |
CMakeLists.txt编辑 msg 相关配置
1 | find_package(catkin REQUIRED COMPONENTS |
1 | ## 配置 msg 源文件 |
1 | # 生成消息时依赖于 std_msgs |
1 | #执行时依赖 |
- 编译
后续说说
因为之前学过ros2,这里为了兼容ros1才学习的,具体在博客中就不详解了,详细内容查看官网文档