前言
本节是记录ROS架构的一些基础操作和基本知识。
环境安装
arm64 linux 安装 ros 参考链接:https://wiki.ros.org/cn/noetic/Installation
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
sudo apt update
sudo apt install ros-noetic-desktop-full
source /opt/ros/noetic/setup.bash
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
# rosdep 初始化
sudo rosdep init
rosdep update
创建工作空间
# 尽量在home目录下创建,其他位置可能会报错
mkdir -p catkin_ws/src
cd catkin_ws/
catkin_make
# 将工作空间环境添加到 ~/.bashrc 文件中
source devel/setup.bash
echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
source ~/.bashrc
创建成功后在 catkin_ws
目录下包含三个文件夹,分别为:
build
文件夹:编译空间,用于存放CMake和catkin的缓存信息、配置信息和其他中间文件。
devel
文件夹:开发空间,用于存放编译后生成的目标文件,包括头文件、动态&静态链接库、可执行文件等。
src
文件夹:存放软件包源码。
catkin_make
是一个命令行工具,它简化了标准 catkin
工作流程。可以认为 catkin_make
是在标准 CMake
工作流程中依次调用了 cmake
和 make
。
ROS 软件包
一个 catkin
软件包必须包含:符合 catkin
规范的 package.xml
文件与一个 catkin
版本的 CMakeLists.txt
文件。
- 创建catkin软件包
cd ~/catkin_ws/src
# catkin_create_pkg <package_name> [depend1] [depend2] [depend3] ……
# 例:创建一个名为 beginner_tutorials 的软件包,依赖 std_msgs rospy roscpp
catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
# 查看一级依赖关系
rospack depends1 beginner_tutorials
# 查看间接依赖关系
rospack depends1 rospy
- 构建catkin软件包
# 在catkin工作空间下执行 catkin_make 命令即可构建src目录下的所有catkin项目。
catkin_make
ROS 节点
每个人ROS软件包里面可以有多个节点,每个节点实现一个功能,节点实际上是ROS软件包中的一个可执行文件。
roscore
是运行所有ROS程序前首先要运行的命令。
rosnode list # 显示当前正在运行的ROS节点信息
rosnode info /[node_name] # 返回某个指定节点的信息。例如:rosnode info /rosout
rosrun [package_name] [node_name] # 用包名直接运行软件包内的节点(而不需要知道包的路径)。
节点间通讯
话题
话题(Topic)是节点间进行持续通讯的一种形式。话题通讯的两个节点通过话题的名称建立起话题通讯连接,话题中通讯的数据,叫做 消息(Message)。Message 通常会按照一定的频率持续不断的发送,以保证消息数据的实时性。消息的发送方叫做话题的发布者(Publisher)。消息的接收方叫做话题的订阅者(Subsciber)。
Publisher(发布者)
Subscriber(订阅者)
topic(话题)
1.一个ROS节点网络中,可以同时存在多个topic。
2.一个topic可以有多个publisher,也可以有多个subscriber
3.一个节点可以对多个topic进行订阅,也可以发布多个topic。
4.不同的传感器消息通常会拥有各自独立topic名称,每个topic只有一个publisher。
5.机器人速度指令话题通常会有多个publisher,但是同一时间只能有一个发言人。
message(消息)
ROS话题示例
在终端键入以下命令:
rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key
# 执行成功的话就可以使用键盘上的方向键来控制turtle运动了。
# 如果不能控制,请选中turtle_teleop_key的终端窗口以确保按键输入能够被捕获。
# 上面的两个节点之间的故事可以用动态图来描述
rosrun rqt_graph rqt_graph # 显示节点之间的关系
rosrun rqt_plot rqt_plot # 显示发布到某个话题上的数据
弹出界面如下所示:

上图为 turtlesim_node
节点和 turtle_teleop_key
节点以及 rqt_graph
的示例,可以看出当鼠标停放在 rqt_graph
中的图节点上时,相应的ROS节点(蓝色和绿色)和ROS话题(红色)就会高亮显示。
其实上面的 turtlesim_node
节点和 turtle_teleop_key
节点之间是通过一个ROS话题来相互通信的。turtle_teleop_key
在话题上发布键盘按下的消息,turtlesim
则订阅该话题以接收消息。
通过 rostopic
命令工具可以获取ROS话题的相关信息。ROS话题不属于发布者与订阅者,而是由ROS系统创建管理。
rostopic -h # 使用帮助选项查看可用的rostopic的子命令
rostopic echo [topic] # 查看话题里面的内容,用 echo -e [Unicode] 可以解析消息Unicode编码
rostopic hz [topic_name] # 查看话题中每秒发送的数据个数。
rostopic list # 列出当前已被订阅和发布的所有话题。
rostopic list -v # 列出所有发布和订阅的主题及其类型的详细信息。
rostopic type [topic] # 查看所发布话题的消息类型。
rosmsg show [msg_type] # 使用rosmsg查看消息的详细信息
rostopic pub [topic] [msg_type] [args] # 把数据发布到当前某个正在广播的话题上。
publisher 实现
以摄像头取流与YOLO推理为例,发布者为摄像头取流模块,订阅者为YOLO推理模块。
首先创建关于摄像头取流的软件包:catkin_create_pkg camera_pkg rospy roscpp std_msgs
#include <ros/ros.h>
#include <std_msgs/String.h>
int main(int argc, char *argv[])
{
// 初始化 ROS 系统,节点命名为 hik_node
ros::init(argc, argv, "hik_node");
// 创建一个节点句柄,用来与 ROS 系统交互,例如创建发布者、订阅者、服务端、客户端等。
ros::NodeHandle nh;
// 使用节点句柄创建一个发布者,发布的消息类型是 std_msgs::String,话题名为 "HikTopic",队列长度为 10。
ros::Publisher pub = nh.advertise<std_msgs::String>("HikTopic", 10);
// 生成频率对象,控制每秒发送次数
ros::Rate loop_rate(10);
// ros 节点程序不会响应外部信号,所以此处的while true 不会被终端 Ctrl+C 关掉,只能用 Ctrl+Shift+W 强制关闭终端
// while (true) {
while (ros::ok()) { // ros::ok 可以响应外部信号
printf("Hik node send stream to HikTopic!\n");
std_msgs::String msg;
msg.data = "Hik stream data.";
pub.publish(msg); // 将这条消息发布到话题 "HikTopic"。
loop_rate.sleep();
}
return 0;
}
subscriber 实现
订阅者为YOLO推理模块。创建可以实现YOLO推理功能的软件包:catkin_create_pkg yolo_pkg rospy roscpp std_msgs
#include <ros/ros.h>
#include <std_msgs/String.h>
void hik_callback(std_msgs::String msg) {
printf("Yolo receive HikTopic message is: %s\n" ,msg.data.c_str());
}
void dh_callback(std_msgs::String msg) {
printf("Yolo receive DHTopic message is: %s\n" ,msg.data.c_str());
}
int main(int argc, char *argv[]) {
ros::init(argc, argv, "yolo_det_node");
ros::NodeHandle nh;
ros::Subscriber yolo_det = nh.subscribe("HikTopic", 10, hik_callback);
ros::Subscriber yolo_det_2 = nh.subscribe("DHTopic", 10, dh_callback);
while(ros::ok()){
// 执行一次回调处理。与 ros::spin() 不同的是,ros::spinOnce() 只会处理一次回调然后返回,常常用于需要在循环中处理其他逻辑的情况(本例暂时没有其它逻辑)。
ros::spinOnce();
}
return 0;
}

服务
服务(Services)是节点之间通讯的另一种方式。服务允许节点发送一个请求(request)并获得一个响应(response)。
rosservice 常用命令
rosservice list # 输出活跃服务的信息
rosservice call [service] [args] # 用给定的参数调用服务
rosservice type [service] # 输出服务的类型
rosservice find # 按服务的类型查找服务
rosservice uri # 输出服务的ROSRPC uri
参数服务器能够存储整型(integer)、浮点(float)、布尔(boolean)、字典(dictionaries)和列表(list)等数据类型。rosparam 使用YAML标记语言的语法。 rosparam
操作参数常用命令。
rosparam list # 列出参数名
rosparam set [param_name] [param_value] # 设置参数
rosparam get [param_name] # 获取参数,"rosparam get /" 显示参数服务器上的所有内容
rosparam load [file_name] [namespace] # 从文件中加载参数
rosparam dump [file_name] [namespace] # 向文件中转储参数
rosparam delete # 删除参数
rqt_console
连接了ROS的日志框架,以显示节点的输出信息。rqt_logger_level
可以在节点运行时改变输出信息的详细级别,包括Debug
、Info
、Warn
和 Error
。 启动命令:
rosrun rqt_console rqt_console # 启动控制台
rosrun rqt_logger_level rqt_logger_level # 启动日志级别设置窗口
创建ROS消息和服务
msg(消息):msg文件就是文本文件,用于描述ROS消息的字段。它们用于为不同编程语言编写的消息生成源代码。msg文件就是简单的文本文件,每行都有一个字段类型和字段名称。
Header header
string lidar_name
srv(服务):一个srv文件描述一个服务。它由两部分组成:请求(request)和响应(response)。 srv 文件包含两个部分:请求和响应。这两部分用一条 ---
线隔开。如下示例:
int64 A
int64 B
---
int64 Sum
上述例子中 A
和 B
是请求, Sum
是响应。
msg文件存放在软件包的msg
目录下,srv文件则存放在srv
目录下。
在使用msg消息时,为了确保msg文件能被转换为C++、Python和其他语言的源代码,需要在 package.xml
添加如下命令:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
在 CMakeLists.txt
文件中添加:
find_package(catkin REQUIRED COMPONENTS message_generation)
add_message_files(FILES MessageName.msg) # 添加msg文件(MessageName.msg)
add_service_files(FILES AddTwoInts.srv) # 添加srv文件(AddTwoInts.srv)
generate_messages(DEPENDENCIES std_msgs) # 添加消息用到的包含.msg文件的软件包(本例中为std_msgs0)
catkin_package(CATKIN_DEPENDS message_runtime)
注意:上面命令中必须按照顺序引入。即:add_message_files
, add_service_files
必须在 generate_messages
之前, generate_messages
必须在 catkin_package
之前。
命令行显示 msg
消息:rosmsg show [message type]
命令行显示 srv
服务:rossrv show [service type]
从msg文件srv文件定义中生成源代码文件:使用 catkin_make
重新make一下软件包即可;
msg
目录中的 .msg
文件生成的C++消息头文件在 ~/catkin_ws/devel/include/pkg_name/
。
srv
目录中的 .srv
文件生成的C++消息头文件在 ~/catkin_ws/devel/include/pkg_name/
。
Service 实现
按照ROS官网教程简单实现一个ROS 服务端(Service Server)示例。
#include <ros/ros.h>
// 自定义的服务消息头文件(.srv 文件生成的 C++ 头文件),其中包含 Request 与 Response 的定义。
#include <service_client_pkg/AddTwoInts.h>
bool add(service_client_pkg::AddTwoInts::Request &req, service_client_pkg::AddTwoInts::Response &res) {
res.sum = req.a + req.b;
ROS_INFO("res.sum: %d = %d + %d ", res.sum, req.a, req.b);
return true;
}
int main(int argc, char **argv){
ros::init(argc, argv, "add_two_ints_server");
ros::NodeHandle nh;
// 创建一个服务服务器,第一个参数 "add_two_ints" 表示服务的名称;第二个参数 add 指向定义好的服务回调函数。
ros::ServiceServer service = nh.advertiseService("add_two_ints", add);
ROS_INFO("Ready to add two ints.");
// 进入循环等待回调,如果客户端调用 "add_two_ints" 服务,就会执行回调函数 add(...)。该函数会一直阻塞,直到节点被关闭(如用户按下 Ctrl+C)。
ros::spin();
return 0;
}
Client 实现
#include "ros/ros.h"
#include "service_client_pkg/AddTwoInts.h"
#include <cstdlib>
int main(int argc, char **argv) {
ros::init(argc, argv, "add_two_ints_client");
if (argc != 3) {
ROS_INFO("usage: add_two_ints_client X Y");
return -1;
}
ros::NodeHandle nh;
// 创建一个客户端对象,用于连接名为 "add_two_ints" 的服务。
// 模板参数 service_client_pkg::AddTwoInts 表示客户端将要发送 / 接收的请求 / 响应类型必须和服务端完全对应。
ros::ServiceClient client = nh.serviceClient<service_client_pkg::AddTwoInts>("add_two_ints");
// 创建一个服务对象 srv,其中包含 request 和 response 两部分。
service_client_pkg::AddTwoInts srv;
srv.request.a = atoll(argv[1]); // 将命令行参数 argv[1] 转换为长长整数并赋值给请求中的 a
srv.request.b = atoll(argv[2]);
// 向服务端发送请求 srv.request,并等待服务端返回结果填入 srv.response。
if (client.call(srv)) {
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
} else {
ROS_ERROR("Failed to call service add_two_ints");
return -1;
}
return 0;
}
调用示例
rosrun service_client_pkg add_two_ints_server # 开启服务
rosrun service_client_pkg add_two_ints_client 1 6 # 客户端调用服务
launch文件
1.使用launch文件,可以通过roslaunch指令一次启动多个节点。
2.在launch文件中,为节点添加 output=“screen”属性,可以让节点信息输出在终端中。(ROS WARN不受该属性控制)
3.在launch文件中,为节点添加launch-prefix=“gnome-terminal -e”属性,可以让节点单独运行在一个独立终端中。
使用命令:roslaunch [package] [filename.launch]
启动定义在launch(启动)文件中的节点。
上文发布者与订阅者launch脚本StreamInfer.launch如下:
<launch>
<node pkg="camera_pkg" type="hik_node" name="hik_node" output="screen"/>
<!-- 节点单独显示在一个终端,方便调试 -->
<node pkg="camera_pkg" type="dh_node" name="dh_node" launch-prefix="gnome-terminal -e"/>
<node pkg="yolo_pkg" type="yolo_det_node" name="yolo_det_node" output="screen"/>
</launch>
启动:cd 到launch脚本目录,运行roslaunch StreamInfer.launch
ROS命令
roscore # 运行所有ROS程序前首先要运行的命令。用launch文件运行可以不用执行该命令
rosnode list # 显示当前正在运行的ROS节点信息
rosnode info /[node_name] # 返回某个指定节点的信息。例如:rosnode info /rosout
rosrun [package_name] [node_name] # 用包名直接运行软件包内的节点(而不需要知道包的路径)。
rosrun rqt_graph rqt_graph # 显示节点之间的关系
rosrun rqt_plot rqt_plot # 显示发布到某个话题上的数据
rosrun rqt_console rqt_console # 启动控制台
rosrun rqt_logger_level rqt_logger_level # 启动日志级别设置窗口
rostopic -h # 使用帮助选项查看可用的rostopic的子命令
rostopic echo [topic] # 查看话题里面的内容,用 echo -e [Unicode] 可以解析消息Unicode编码
rostopic hz [topic_name] # 查看话题中每秒发送的数据个数。
rostopic list # 列出当前已被订阅和发布的所有话题。
rostopic list -v # 列出所有发布和订阅的主题及其类型的详细信息。
rostopic type [topic] # 查看所发布话题的消息类型。
rosmsg show [msg_type] # 使用rosmsg查看消息的详细信息
rostopic pub [topic] [msg_type] [args] # 把数据发布到当前某个正在广播的话题上。
rosservice list # 输出活跃服务的信息
rosservice call [service] [args] # 用给定的参数调用服务
rosservice type [service] # 输出服务的类型
rosservice find # 按服务的类型查找服务
rosservice uri # 输出服务的ROSRPC uri
rosparam list # 列出参数名
rosparam set [param_name] [param_value] # 设置参数
rosparam get [param_name] # 获取参数,"rosparam get /" 显示参数服务器上的所有内容
rosparam load [file_name] [namespace] # 从文件中加载参数
rosparam dump [file_name] [namespace] # 向文件中转储参数
rosparam delete # 删除参数
rosmsg show [message type] # 查看自定义msg结构
rossrv show [service type] # 查看服务的信息
roslaunch [package] [filename.launch] # 运行launch文件