ROS基础知识
代码模板
c++ 模板
1 |
|
python 模板
1 | #! /usr/bin/env python |
ROS环境搭建与初认识
ROS安装
具体细节看教程
采用虚拟机安装 ubuntu,再安装 ROS
虚拟机软件:virtualbox(免费) 官网下载软件安装包以及拓展包
ubuntu版本:ubuntu-20.04.6 镜像文件下载地址
ROS版本:Noetic
1 | sudo apt install ros-noetic-desktop-full |
配置环境变量,方便在任意终端中使用 ROS:
1 | echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc |
安装构建依赖:
1 | sudo apt install python3-rosdep python3-rosinstall python3-rosinstall-generator python3-wstool build-essential |
测试ROS安装环境:
- 启动三个命令行(ctrl + alt + T)
- 命令行1键入:
roscore
- 命令行2键入:
rosrun turtlesim turtlesim_node
(此时会弹出图形化界面) - 命令行3键入:
rosrun turtlesim turtle_teleop_key
(可以控制小乌龟运动)
以下命令行内容中[ ]包围的为替换内容
ROS简单程序
ROS中的程序即便使用不同的编程语言,实现流程也大致类似,以当前HelloWorld程序为例,实现流程大致如下:
- 先创建一个工作空间;
- 再创建一个功能包;
- 编辑源文件;
- 编辑配置文件;
- 编译并执行
上述流程中,C++和Python只是在步骤3和步骤4的实现细节上存在差异,其他流程基本一致
-
创建工作空间(demo01_ws)并初始化
1
2
3mkdir -p [demo01_ws]/src
cd [demo01_ws]
catkin_make初始化后 demo01_ws文件夹中有src build devel三个文件夹
-
进入 src 创建 ros 包(helloworld)并添加依赖(roscpp rospy std_msgs)
1
2cd src
catkin_create_pkg [helloworld] roscpp rospy std_msgs
c++
-
进入 ros 包的 src 目录编辑源文件
创建helloworld_c.cpp文件
1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, char *argv[])
{
//执行 ros 节点初始化
ros::init(argc,argv,"hello");
//创建 ros 节点句柄(非必须)
ros::NodeHandle n;
//控制台输出 hello world
ROS_INFO("hello world!");
return 0;
} -
编辑 ros 包下的 Cmakelist.txt文件
1
2
3
4
5
6add_executable([helloworld_c]
src/helloworld_c.cpp
)
target_link_libraries([自定义名称]
${catkin_LIBRARIES}
)分别在初始的136行与149行
通过自定义名称可以映射到
src/helloworld_c.cpp
文件,一般取名和文件名相同 -
进入工作空间目录(demo01_ws)打开终端并编译
1
catkin_make
如果正常会显示[100%]
-
执行
先启动命令行1:
1
roscore
再启动命令行2:
1
2source ./devel/setup.bash
rosrun [helloworld] [helloworld_c]helloworld是ros包名称,helloworld_c是cpp文件映射
命令行输出: hello world!
注意,这个
source ./devel/setup.bash
命令只针对当前的终端窗口可用将下面这条指令添加进
.bashrc
文件(根目录的隐藏文件),即可全局使用1
source ~/[demo01_ws]/devel/setup.bash
python
-
进入 ros 包添加 scripts 目录并编辑 python 文件
创建helloworld_p.py
1
2
3
4
5
6
7
8#! /usr/bin/env python
## 指定解释器
import rospy
if __name__ == "__main__":
rospy.init_node("hello_p");
rospy.loginfo("hello world! by python"); -
为 python 文件添加可执行权限
在scripts文件夹下打开终端
1
2chmod +x *.py
ll //查看已有权限的内容 -
编辑 ros 包下的 CamkeList.txt 文件
1
2
3catkin_install_python(PROGRAMS scripts/[helloworld_p].py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)python3需要这一步
在初始的162行
-
进入工作空间目录(demo01_ws)打开终端并编译
1
catkin_make
如果正常会显示[100%]并且输出Built target [自定义名称]
-
执行
先启动命令行1:
1
roscore
再启动命令行2:
1
2source ./devel/setup.bash
rosrun [helloworld] [helloworld_p.py]命令行输出: hello world! by python
注意,与c++不同,python文件没有自定义映射名称,在rosrun时需要写入完整文件名
ROS集成开发环境
终端
在 ROS 中,需要频繁的使用到终端,且可能需要同时开启多个窗口,推荐使用Terminator
安装:
1 | sudo apt install terminator |
常用快捷键:
1 | Alt+Up //移动到上面的终端 |
VScode
下载:vscode 下载 选择适用于linux系统的.deb文件
安装:
- 方式1:双击安装即可(或右击选择安装) [可能会出现没反应或闪退行为]
- 方式2:
sudo dpkg -i xxxx.deb
复制具体的下载文件名称
vscode 集成 ROS 插件:c++,CMake,python,ROS
vscode 使用_基本配置:
-
创建 ROS 工作空间
1
2
3mkdir -p [工作空间名称]/src(必须得有 src)
cd [工作空间名称]
catkin_make -
启动 vscode
1
code .
-
vscode 中编译 ros
快捷键 ctrl + shift + B 调用编译,选择
catkin_make:build
右边的小齿轮配置修改.vscode/tasks.json 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18{
// 有关 tasks.json 格式的文档,请参见
// https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"label": "catkin_make:debug", //代表提示的描述性信息
"type": "shell", //可以选择shell或者process,如果是shell代码是在shell里面运行一个命令,如果是process代表作为一个进程来运行
"command": "catkin_make",//这个是我们需要运行的命令
"args": [],//如果需要在命令后面加一些后缀,可以写在这里,比如-DCATKIN_WHITELIST_PACKAGES=“pac1;pac2”
"group": {"kind":"build","isDefault":true},
"presentation": {
"reveal": "always"//可选always或者silence,代表是否输出信息
},
"problemMatcher": "$msCompile"
}
]
} -
创建 ROS 功能包
选定 src 右击 —> create catkin package
设置包名(helloworld) 添加依赖(roscpp rospy std_msgs)
-
C++ 实现
在功能包的 src 下新建 cpp 文件
1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, char *argv[])
{
// 解决中文乱码
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_c");
ROS_INFO("hello vscode");
return 0;
}如果没有代码提示
修改 .vscode/c_cpp_properties.json,设置 “cppStandard”: “c++17”
光标指向括号内,ctrl+shift+空格提示函数格式
-
python 实现
在 功能包 下新建 scripts 文件夹,添加 python 文件
1
2
3
4
5
6
7#! /usr/bin/env python
import rospy
if __name__ == "__main__":
rospy.init_node("hello_p")
rospy.loginfo("hello vscode! it's python")在scripts文件夹下打开终端并添加可执行权限
1
chmod +x *.py
-
配置 CMakeLists.txt
-
编译执行
编译:ctrl + shift + B
执行:和之前一致,只是可以在 VScode 中添加终端
首先执行:
source ./devel/setup.bash
c++:
rosrun [包名] [自定义名称]
python:
rosrun [包名] [源文件名称]
如果不编译直接执行 python 文件,会抛出异常
解决方案:
- 第一行解释器声明,可以使用绝对路径定位到 python3 的安装路径 #! /usr/bin/python3(不建议)
- 创建一个链接符号到 python 命令:
sudo ln -s /usr/bin/python3 /usr/bin/python
(建议)
launch文件
解决需要启动多个节点的效率问题
实现:
-
选定功能包右击 —> 添加 launch 文件夹
-
选定 launch 文件夹右击 —> 添加 launch 文件
-
编辑 launch 文件内容
以小乌龟示例程序启动为例:
1
2
3
4
5
6
7<launch>
<!-- 添加被执行节点-->
<!-- 乌龟GUI -->
<node pkg="turtlesim" type="turtlesim_node" name="turtle_GUI" />
<node pkg="turtlesim" type="turtle_teleop_key" name="turtle_key" />
<node pkg="hello_vscode" type="hello_vscode_c" name="hello" output="screen"/>
</launch>- node —> 包含的某个节点
- pkg -----> 功能包
- type ----> 被运行的节点文件
- name --> 为节点命名
- output-> 设置日志的输出目标
-
运行 launch 文件
1
roslaunch [功能包名] [launch文件名]
-
运行结果:一次性启动了多个节点
ROS架构
文件系统

1 | WorkSpace --- 自定义的工作空间 |
文件系统相关命令
-
增
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 === 是 ROS 的系统先决条件节点和程序的集合, 必须运行 roscore 才能使 ROS 节点进行通信
roscore 将启动:
- ros master
- ros 参数服务器
- rosout 日志节点
rosrun 包名 可执行文件名 === 运行指定的ROS节点
比如:
rosrun turtlesim turtlesim_node
roslaunch 包名 launch文件名 === 执行某个包下的 launch 文件
计算图
rqt_graph能够创建一个显示当前系统运行情况的动态图形
计算图可以以点对点的网络形式表现数据交互过程
运行程序后,启动新终端,键入: rqt_graph 或 rosrun rqt_graph rqt_graph,可以看到网络拓扑图,该图可以显示不同节点之间的关系
ROS通信机制
话题通信
话题通信是ROS中使用频率最高的一种通信模式,基于发布订阅模式,即一个节点发布消息,另一个节点订阅该消息
以激光雷达信息的采集处理为例,在 ROS 中有一个节点需要时时的发布当前雷达采集到的数据,导航模块中也有节点会订阅并解析雷达数据
以此类推,像雷达、摄像头、GPS… 等等一些传感器数据的采集都使用了话题通信
话题通信适用于不断更新、少逻辑处理的数据传输场景
.webp)
- 在工作空间下功能包中,基本每一个可执行文件都会初始化一个节点(node),并唯一命名
- 节点之间通过话题(topic)进行通信
- 话题类型就是发布的消息(msg)
- 传输的内容需要关注消息类型
普通文本
需求: 实现基本的话题通信,一方发布数据,一方接收数据
c++实现
在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注:
- 发布方
- 接收方
- 数据
发布方:
1 |
|
接收方:
1 |
|
ConstPtr
是boost::shared_ptr
(智能指针),即msg
是一个指针必须用
->
访问其成员的data
字段
配置CmakeList.txt
1 | add_executable(demo01_pub src/demo01_pub.cpp) |
vscode 中的 main 函数 声明 int main(int argc, char const *argv[]){}
,默认生成 argv 被 const 修饰,需要去除该修饰符
python实现
发布方:
1 | #! /usr/bin/env python |
订阅方:
1 | #! /usr/bin/env python |
添加可执行权限
终端下进入 scripts 执行:chmod +x *.py
配置 CMakeLists.txt:
1 | catkin_install_python(PROGRAMS |
自定义msg
msgs只是简单的文本文件,每行具有字段类型和字段名称
如果需要自定义消息类型,则需要自定义msg
流程:
- 按照固定格式创建 msg 文件
- 编辑配置文件
- 编译生成可以被 Python 或 C++ 调用的中间文件
创建自定义消息
-
定义msg文件
功能包下新建 msg 目录,添加文件 Person.msg
1
2
3string name
uint16 age
float64 height -
编辑配置文件
package.xml中添加编译依赖与执行依赖
1
2<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>CMakeLists.txt编辑 msg 相关配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# 需要加入 message_generation,必须有 std_msgs
...
# 配置 msg 源文件
add_message_files(
FILES
Person.msg
)
...
# 执行时依赖
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES demo02_talker_listener
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)编译依赖find_package内的包
find_package依赖catkin_package内的包
如果catkin_package内的包不对,可能编译成功但运行失败
-
编译
编译后查看
C++ 需要调用的中间文件(…/工作空间/devel/include/包名/xxx.h)
Python 需要调用的中间文件(…/工作空间/devel/lib/python3/msg/xxx.py)
c++实现
需要先配置 vscode,将前面生成的 head 文件路径配置进 c_cpp_properties.json 的 includepath属性
1 | "includePath": [ |
发布方:
1 |
|
订阅方:
1 |
|
配置CMakeList:
1 | add_executable(person_talker src/person_talker.cpp) |
add_dependencies如果考虑简单,可以仅将注释的内容
1 # add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})中
${PROJECT_NAME}_node
处改为节点名称,其它保留原来格式也是可以的
python实现
将前面生成的 python 文件路径配置进 settings.json
1 | "python.autoComplete.extraPaths": [ |
发布方:
1 | #! /usr/bin/env python |
订阅方:
1 | #! /usr/bin/env python |
终端下进入 scripts 执行:chmod +x *.py
配置 CMakeLists.txt:
1 | catkin_install_python(PROGRAMS |
服务通信
服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制
即一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A
服务通信更适用于对时时性有要求、具有一定逻辑处理的应用场景

案例:
实现两个数字的求和,客户端节点,运行会向服务器发送两个数字,服务器端节点接收两个数字求和并将结果响应回客户端
自定义srv
srv 文件内的可用数据类型与 msg 文件一致,且定义 srv 实现流程与自定义 msg 实现流程类似:
- 按照固定格式创建srv文件
- 编辑配置文件
- 编译生成中间文件
流程:
-
定义srv文件
功能包下新建 srv 目录,添加 xxx.srv 文件,请求和响应数据类型之间用
---
分开1
2
3
4
5
6# 客户端请求时发送的两个数字
int32 num1
int32 num2
---
# 服务器响应发送的数据
int32 sum -
编译配置文件
package.xml中添加编译依赖与执行依赖
1
2<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>同话题通信自定义
CMakeLists.txt编辑 srv 相关配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# 需要加入 message_generation,必须有 std_msgs
add_service_files(
FILES
AddInts.srv
)
generate_messages(
DEPENDENCIES
std_msgs
)官网示例没有在 catkin_package 中配置 message_runtime,经测试配置也可以
-
编译,查看包方式同之前
需要先配置 vscode,将前面生成的 head 文件路径配置进 c_cpp_properties.json 的 includepath属性
"includePath": [
"/opt/ros/noetic/include/**",
"/usr/include/**",
"/home/ros/demo03_ws/devel/include/**" //增加的
],
c++
服务端:
1 | /* |
服务端的回调函数的参数是
demo03_server_client::AddInts::Request& req
,没有ConstPtr
,req
是请求对象的引用,不是指针引用 (
req
) 的行为和对象实例一致,直接用.
访问成员,因为服务是“一问一答”模式,不需要共享所有权,直接修改传入的请求和响应对象即可话题通信中将接收到的消息封装为
boost::shared_ptr
(智能指针)传递给回调函数,目的是为了高效管理内存(避免拷贝)
客户端:
1 | /* |
配置CMakeLists
1 | add_executable(AddInts_Server src/AddInts_Server.cpp) |
python
服务端:
1 | #! /usr/bin/env python |
客户端:
1 | #! /usr/bin/env python |
配置CMakeLists
1 | catkin_install_python(PROGRAMS |
参数服务器
以共享的方式实现不同节点之间数据交互的通信模式,存储一些多节点共享的数据,类似于全局变量。
案例:实现参数增删改查操作
ROS Master 作为一个公共容器保存参数,Talker 可以向容器中设置参数,Listener 可以获取参数

注意:参数服务器不是为高性能而设计的,因此最好用于存储静态的非二进制的简单数据
c++
- 参数服务器新增(修改)参数
实现参数服务器数据的增删改查,可以通过两套 API 实现:
1 | /* |
1 |
|
- 参数服务器获取参数
在 roscpp 中提供了两套 API 实现参数操作
1 | /* |
- 参数服务器删除参数
1 | /* |
python
- 参数服务器新增(修改)参数
1 | #! /usr/bin/env python |
- 参数服务器获取参数
1 | """ |
- 参数服务器删除参数
1 | """ |
常用命令
和之前介绍的文件系统操作命令比较,文件操作命令是静态的,操作的是磁盘上的文件,而上述命令是动态的,在ROS程序启动后,可以动态的获取运行中的节点或参数的相关信息
ROS/CommandLineTools - ROS Wiki
rosnode - ROS Wiki 用于获取节点信息的命令
- rosnode list 列出所有的活动节点
- rosnode ping [node_name] 测试到节点的连接状态
- rosnode info [node_name] 打印节点信息
- rosnode machine [machine_name] 列出指定设备上的节点
- rosnode kill [node_name] 杀死某个节点
- rosnode cleanup 清除无用节点,比如小乌龟示例
rostopic - ROS Wiki 用于显示有关ROS话题的调试信息,包括发布者,订阅者,发布频率和ROS消息
-
rostopic list 显示所有活动状态下的话题
-
rostpic echo [topic-name] 获取指定话题当前发布的消息
-
rostopic pub 直接调用命令向订阅者发布消息 [这类指令都需要先source 一下]
1
2rostopic pub -r [频率] /话题名称 消息类型 消息内容
rostopic pub -r 10 chatter_person plumbing_pub_sub/Person [按两次Tab补齐] -
rostopic info [topic-name] 获取当前话题的相关信息
-
rostopic type [topic-name] 打印话题类型
-
rostopic hz [topic-name] 显示话题的发布频率
rosmsg - ROS Wiki 用于显示有关 ROS消息类型的工具
-
rosmsg list 列出所有消息
-
rosmsg list | grep -i [消息种类] 显示消息的具体路径
-
rosmsg show [消息种类] 显示消息描述
-
rosmsg info [消息种类] 作用与 rosmsg show 一样
rosservice - ROS Wiki 用于列出和查询ROS Services的工具
- rosservice list 列出所有活动的服务
- rosservice call [service-name] 使用提供的参数调用服务
- rosservice info [service-name] 打印有关服务的信息
- rosservice type [service-name] 打印服务类型
rossrv是用于显示有关ROS服务类型的信息的命令行工具,与 rosmsg 使用语法高度一致
rosparam - ROS Wiki 用于使用YAML编码文件在参数服务器上获取和设置ROS参数
-
rosparam list 列出所有参数
-
rosparam set 设置参数 [参数名] [参数值]
-
rosparam get 获取参数
-
rosparam delete 删除参数
-
rosparam dump 将参数写出到外部文件
1
rosparam dump ×××.yaml
-
rosparam load [yaml文件] 从外部文件加载参数
1
rosparam load xxx.yaml
通信机制比较
三种通信机制中
参数服务器是一种数据共享机制,可以在不同的节点之间共享数据
话题通信与服务通信是在不同的节点之间传递数据的
话题通信:Publisher→Subscriber [Topic] [msg]
服务通信:Client→Server [Service] [srv]
二者的实现流程相似,都是两个节点通过话题关联到一起,并使用某种类型的数据载体实现数据传输
Topic(话题) | Service(服务) | |
---|---|---|
通信模式 | 发布/订阅 | 请求/响应 |
同步性 | 异步(无序) | 同步(有序) |
底层协议 | ROSTCP/ROSUDP | ROSTCP/ROSUDP |
缓冲区 | 有(queue_size 队列) | 无 |
时时性 | 弱 | 强 |
节点关系 | 多对多 | 一对多(一个 Server) |
通信数据 | msg | srv |
使用场景 | 连续高频的数据发布与接收:雷达、里程计 | 偶尔调用或执行某一项特定功能:拍照、语音识别 |
ROS通信机制进阶
常用API
建议参考官方API文档或参考源码
在运行程序时rosrun 后接着写 _[参数名] = [参数值] 可以给节点
初始化函数
ros::init(argc, argv, “name”, [options])
-
argc — 封装实参的个数(n+1)
-
argv — 封装参数的数组
-
name — 为节点命名(唯一性)
-
options — 节点启动选项 ros::init_options::AnonymousName
给前面的节点命名后增加一个随机数,使得同一个节点能够重复启动
否则重名情况下,第二次启动时第一次会自动停止
返回值: void
rospy.init_node(name, argv=None, anonymous=False)
- name — 节点名称
- argv — 封装节点调用时传递的参数
- anonymous — 取值为 true 时,为节点名称后缀随机编号
话题与服务
在 roscpp 中,话题和服务的相关对象一般由 ros::NodeHandle nh 创建,python中不用专门创建NodeHandle
发布对象
ros::Publisher pub = nh.advertise< type >(“topic”, queue_size, [latch])
-
type — 消息类型
-
topic — 话题名称
-
queue_size — 队列长度
-
latch — 设置为ture时,该话题发布的最后一条消息将被保存,并且当有订阅者连接时会将该消息发送给订阅者
以静态地图发送为例(短时间不变的数据)
方案1:可以使用固定频率发送地图数据,但是效率低
方案2:可以将地图发布对象的latch设置为true,并且发布方只发送一次数据,每当订阅者连接时,将地图数据发送给订阅者(只发送一次),这样提高了数据的发送效率
pub = rospy.Publisher(“topic”, type, queue_size, [lathc])
订阅对象
ros::Subscriber sub = nh.subscribe(“topic”, queue_size, callback);
void callback(const std_msgs::String::ConstPtr &msg)
sub = rospy.Subscriber(“topic”, type, callback, queue_size)
def callback(msg):
服务对象
ros::ServiceServer server = nh.advertiseService(“service”, callback);
bool callback(srv type::Request &req, srv type::Response &resp)
server = rospy.Service(“service”, srv type, callback)
def callback(req):
return resp
客户端对象
ros::ServiceClient client = nh.serviceClient< srv type >(“service”);
等待服务函数1 ros::service::waitForService(“service”)
等待服务函数2 client.waitForExistence();
请求写入:srv type req; req.request.input1 = atoi(argv[1]); (类推写完输入)
client = rospy.ServiceProxy(“service”, srv type)
等待服务函数 client.wait_for_service()
请求写入:req = srv typeRequets() req.input1 = sys.argv[1] (类推写完输入)
回调函数
spinOnce() 处理一轮回调,一般用于循环体内
spin() 进入循环去处理回调
不同点:在ros::spin() 后的语句不会执行到,而 ros::spinOnce() 后的语句可以执行
python 只有 rospy.spin()
时间
时刻
获取时刻,或是设置指定时刻
ros::Time::now()
1 |
|
rospy.Time.now()
1 | current_time = rospy.Time.now() |
持续时间
设置一个时间区间(间隔)
ros::Duration du()
1 | ROS_INFO("开始休眠:%.2f",ros::Time::now().toSec()); |
du = rospy.Duration()
1 | rospy.loginfo("持续时间测试开始.....") |
时刻运算
1 | ros::Time begin = ros::Time::now(); |
time 与 duration 可以+/-,duration 之间也可以+/-
但time 之间只可以 - ,不可以 + ,返回的是 ros::Duration 类型
python与c++相同
设置运行频率
1 | ros::Rate rate(1);//指定频率 |
1 | rate = rospy.Rate(1) |
定时器
1 | void cb(const ros::TimerEvent &event){ |
1 | def cb(event): |
其它函数
在发布实现时,一般会循环发布消息,循环的判断条件一般由节点状态来控制
C++中可以通过 ros::ok() 来判断节点状态是否正常,而 python 中则通过 rospy.is_shutdown() 来实现判断,导致节点退出的原因主要有如下几种:
- 节点接收到了关闭信息,比如常用的 ctrl + c(z) 快捷键就是关闭节点的信号
- 同名节点启动,导致现有节点退出
- 程序中的其他部分调用了节点关闭相关的API(C++中是ros::shutdown(),python中是rospy.signal_shutdown())
日志函数:
1 | ROS_DEBUG("hello,DEBUG"); //不会输出 |
1 | rospy.logdebug("hello,debug") #不会输出 |
头文件与源文件
核心内容在于CMakeLists.txt文件的配置
自定义头文件调用
流程:
- 编写头文件;
- 编写可执行文件(同时也是源文件);
- 编辑配置文件并执行。
头文件
在功能包下的include/功能包名
目录下新建头文件: hello.h
1 |
|
为了后续包含头文件时不抛出异常,配置 .vscode 下 _cpp_properties.json 的 includepath属性(和之前配置服务srv文件生成的头文件过程相似)
1 | "includePath": [ |
源文件
src目录下新建 hello.cpp
1 |
|
配置文件
与之前配置多一步
1 | include_directories( |
自定义源文件调用
流程:
- 编写头文件;
- 编写源文件;
- 编写可执行文件;
- 编辑配置文件并执行。
可执行文件
src目录下新建 use_head.cpp
1 |
|
配置文件
头文件与源文件相关配置:
1 | include_directories( |
可执行文件配置:
1 | add_executable(use_hello src/use_hello.cpp) |
Python模块导入
实现:
- 新建两个Python文件,使用 import 实现导入关系;
- 添加可执行权限、编辑配置文件并执行UseA
tool.py文件
1 | #! /usr/bin/env python |
use_tool.py文件
1 | #! /usr/bin/env python |
ROS运行管理
元功能包
完成ROS中一个系统性的功能,可能涉及到多个功能包,逐一安装功能包的效率低下,在ROS中,提供了一种方式可以将不同的功能包打包成一个功能包,当安装某个功能模块时,直接调用打包后的功能包即可,该包又称之为元功能包
MetaPackage
是Linux的一个文件管理系统的概念,是ROS中的一个虚包,里面没有实质性的内容,但是它依赖了其他的软件包,通过这种方法可以把其他包组合起来,可以认为它是一本书的目录索引,告诉我们这个包集合中有哪些子包,并且该去哪里下载
例如:sudo apt install ros-noetic-desktop-full 命令安装ros时就使用了元功能包
实现
新建一个功能包,不需要添加依赖,修改package.xml
1 | <buildtool_depend>catkin</buildtool_depend> |
修改 CMakeLists.txt
1 | cmake_minimum_required(VERSION 3.0.2) |
不能出现换行以及注释
launch文件
在功能包下添加 launch目录, 目录下新建 xxxx.launch 文件,编辑 launch 文件
1 | <launch> |
调用 launch 文件
1 | roslaunch 包名 xxx.launch |
roslaunch 命令执行launch文件时,首先会判断是否启动了 roscore,如果启动了,则不再启动,否则,会自动调用 roscore
launch标签
<launch>
标签是所有 launch 文件的根标签,充当其他标签的容器
deprecated 告知用户当前 launch 文件已经弃用
1 | <launch deprecated="此文件可能过时!"> |
node标签
<node>
标签用于指定 ROS 节点,是最常见的标签,但 roslaunch 命令不能保证按照 node 的声明顺序来启动节点(节点的启动是多进程的)
-
pkg=“包名”
节点所属的功能包
-
type=“nodeType”
节点类型,即与之相同名称的可执行文件,python文件需要带上.py,cpp文件不需要,类似CMakeList配置
-
name=“nodeName”
节点名称,在 ROS 网络拓扑中节点的名称
-
machine=“机器名”
在指定机器上启动节点
-
respawn=“true | false” (可选)
如果节点退出,是否自动重启
-
respawn_delay=" N" (可选)
如果 respawn 为 true,那么延迟 N 秒后启动节点
-
required=“true | false” (可选)
如果为 true,那么该节点退出,将杀死整个 roslaunch,不能和respawn一起用,会报错
-
ns=“xxx” (可选)
在指定命名空间 xxx 中启动节点 ,主要用来避免重名
node list -> /命名空间/name
-
output=“log | screen” (可选)
默认log,但一般改成screen
1 | <node pkg="turtlesim" type="turtlesim_node" name="myTurtle" output="screen"/> |
include标签
用于将另一个 xml 格式的 launch 文件导入到当前文件
file=“$(find 包名)/xxx/xxx.launch”
1 | <launch> |
用于复用
remap标签
用于话题重命名
1 | <node pkg="turtlesim" type="turtlesim_node" name="myTurtle" output="screen"> |
from = “原始话题名称” to = “目标话题名称”
此时原来的控制失效,因为turtle_teleop_key 是以/turtle1/cmd_vel 发布
1 | <node pkg="turtlesim" type="turtle_teleop_key" name="myTurtleContro" output="screen" /> |
这里需要下载一个控制包
1 | sudo apt-get install ros-noetic-teleop-twist-keyboard |
param标签
<param>
标签主要用于在参数服务器上设置参数
可以通过外部文件加载,在<node>
标签中时,相当于私有命名空间
-
name=“[命名空间]/参数名”
参数名称,可以包含命名空间
-
value=“xxx” (可选)
定义参数值,如果此处省略,必须指定外部文件作为参数源
-
type=“str | int | double | bool | yaml” (可选)
指定参数类型,如果未指定,roslaunch 会尝试确定参数类型,规则如下:
- 如果包含 ‘.’ 的数字解析未浮点型,否则为整型
- “true” 和 “false” 是 bool 值(不区分大小写)
- 其他是字符串
1 | <param name="param_A" type = "int" value ="100" /> |
rosparam标签
<rosparam>
标签可以从 YAML 文件导入参数,或将参数导出到 YAML 文件,也可以用来删除参数
- command=“load | dump | delete” (可选,默认 load)
- file=“$(find 功能包)/xxx/yyy…”
- param=“参数名称”
- ns=“命名空间” (可选)
1 | <!-- 导入参数 --> |
由于launch文件也不是按顺序进行,导出虽然写在launch文件较后的位置也可能无法正常导出,所以最好分两个launch文件,在完成后再进行导出
1 | <launch> |
删除操作:
1 | <rosparam command = "delete" param = "bg_B" /> |
group标签
<group>
标签可以对节点分组,具有 ns 属性,可以让节点归属某个命名空间
-
clear_params=“true | false” (可选)
启动前,是否删除组名称空间的所有参数(慎用…此功能危险)
这个时候即使在node中name属性相同也不会报错,因为group使得前面加上前缀,不会产生相同的节点名称
1 | <launch > |
arg标签
<arg>
标签是用于动态传参,类似于函数的参数
-
name=“参数名称”
-
default=“默认值” (可选)
-
value=“数值” (可选)
不可以与 default 并存
-
doc=“描述”
命令行调用传参
1 | roslaunch hello.launch xxx:=值 |
工作空间覆盖
所谓工作空间覆盖,是指不同工作空间中,存在重名的功能包的情形
比如:自定义工作空间A存在功能包 turtlesim,自定义工作空间B也存在功能包 turtlesim,当然系统内置空间也存在turtlesim,如果调用turtlesim包,会调用哪个工作空间中的呢
-
新建工作空间A与工作空间B,两个工作空间中都创建功能包: turtlesim
1
2
3
4
5
6
7
8#include "ros/ros.h"
int main(int argc, char *argv[])
{
ros::init(argc,argv,"hello_ws1");
ROS_INFO("demo01_ws");
return 0;
} -
为了在任何工作空间下调用demo01_ws和demo02_ws,需要在根目录下的.bashrc文件中进行修改
1
2
3
4source /opt/ros/noetic/setup.bash
# 新增
source /home/ros/demo01_ws/devel/setup.bash
source /home/ros/demo02_ws/devel/setup.bash -
刷新环境变量
1
source .bashrc
运行
1
rosrun turtlesim [TAB]
会发现直接跳出hello_ws2
是因为在.bashrc中后刷新的会覆盖前面的
-
查看ROS环境环境变量
1
echo $ROS_PACKAGE_PATH
ROS_PACKAGE_PATH 中的值,和 .bashrc 的配置顺序相反—>后配置的优先级更高
功能包重名时,会按照 ROS_PACKAGE_PATH 查找,配置在前的会优先执行
隐患
比如当前工作空间B优先级更高,意味着当程序调用 turtlesim 时,不会调用工作空间A也不会调用系统内置的 turtlesim,如果工作空间A在实现时有其他功能包依赖于自身的 turtlesim,而按照ROS工作空间覆盖的涉及原则,那么实际执行时将会调用工作空间B的turtlesim,从而导致执行异常,出现安全隐患
BUG 说明:
在 .bashrc 文件中 source 多个工作空间后,可能出现在 ROS PACKAGE PATH 中只包含两个工作空间,可以删除自定义工作空间的 build 与 devel 目录,重新 catkin_make,然后重新载入 .bashrc 文件,问题解决
目前没有特别的解决方案
节点名称重名
ros::init(argc,argv,“”) rospy.init_node(“”) 定义节点名称
在ROS的网络拓扑中,是不可以出现重名的节点的,也不可以启动重名节点或者同一个节点多次
在ROS中给出的解决策略是使用命名空间或名称重映射
命名空间就是为名称添加前缀,名称重映射是为名称起别名
rosrun实现
rosrun设置命名空间
语法: rosrun 包名 节点名 __ns:=新名称
1 | rosrun turtlesim turtlesim_node __ns:=t1 |
rosnode list
1 | /t1/turtlesim |
rosrun名称重映射
语法: rosrun 包名 节点名 __name:=新名称
1 | rosrun turtlesim turtlesim_node __name:=t1 |
设置命名空间同时名称重映射
1 | rosrun turtlesim turtlesim_node __ns:=w1 __name:=t1 |
使用环境变量也可以设置命名空间,启动节点前在终端键入如下命令
1 | export ROS_NAMESPACE=sw1 |
launch实现
1 | <launch > |
在 node 标签中,name 属性是必须的,ns 可选
rosnode list
查看节点信息
1 | /p1/turtlesim |
编码实现
c++
重映射:(设置别名)
1 | ros::init(argc,argv,"name",ros::init_options::AnonymousName); |
在名称后面添加时间戳
命名空间:
1 | std::map<std::string, std::string> map; |
python
重映射:(设置别名)
1 | rospy.init_node("name",anonymous=True) |
话题名称重名
rosrun实现
语法: rorun 包名 节点名 话题名:=新话题名称
实现teleop_twist_keyboard与乌龟显示节点通信方案
方案1:将 teleop_twist_keyboard 节点的话题设置为/turtle1/cmd_vel
1 | rosrun teleop_twist_keyboard teleop_twist_keyboard.py /cmd_vel:=/turtle1/cmd_vel |
这个时候rosrun turtlesim turtle_teleop_key
也能正常控制,相当于两个发布
方案2:将乌龟显示节点的话题设置为 /cmd_vel
1 | rosrun turtlesim turtlesim_node /turtle1/cmd_vel:=/cmd_vel |
launch实现
在之前launch文件标签那里讲过,这里就给个代码
语法<remap from="原话题" to="新话题" />
1 | <launch> |
1 | <launch> |
编码实现
话题的名称与节点的命名空间、节点的名称是有一定关系的,话题名称大致可以分为三种类型:
- 全局(话题参考ROS系统,与节点命名空间平级)
- 相对(话题参考的是节点的命名空间,与节点名称平级)
- 私有(话题参考节点名称,是节点名称的子级)

c++:
全局名称:以/
开头,和节点名称无关
1 | ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter",1000); |
相对名称:非/
开头,参考命名空间(与节点名称平级)来确定话题名称
1 | ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000); |
__ns:=设置命名空间后,则话题位于 /name/chatter
私有名称:以~
开头的名称
需要在节点handle创建时候做出些改变
1 | ros::NodeHandle nh("~"); |
但如果话题以/
开头则话题仍为全局(全局话题优先级更高)
1 | ros::NodeHandle nh("~"); |
python:
全局名称:以/
开头,和节点名称无关
1 | pub = rospy.Publisher("/chatter",String,queue_size=1000) |
相对名称:非/
开头,参考命名空间(与节点名称平级)来确定话题名称
1 | pub = rospy.Publisher("chatter",String,queue_size=1000) |
私有名称:以~
开头的名称
1 | pub = rospy.Publisher("~chatter",String,queue_size=1000) |