ROS基础


前言

本节是记录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 工作流程中依次调用了 cmakemake

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 可以在节点运行时改变输出信息的详细级别,包括DebugInfoWarnError。 启动命令:

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

上述例子中 AB 是请求, 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文件

参考链接:https://wiki.ros.org/cn/ROS/Tutorials


文章作者: LSJune
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LSJune !
评论
  目录