ROS架构及通信机制(ROS Communication System)

一、ROS文件系统

(一)概念

ROS文件系统是组织和管理ROS软件包及相关资源的结构。它为ROS的开发和运行提供了基础框架,使得软件包的开发、分发和使用更加规范和高效。

(二)主要组成部分

1. 工作空间(Workspace)

工作空间是用户进行ROS开发的主要目录。通常,一个工作空间包含多个软件包。可以通过catkin_make命令创建和管理工作空间。例如,创建一个名为my_ws的工作空间,可以在终端输入:

mkdir -p ~/my_ws/src
cd ~/my_ws/
catkin_make

这样就会在my_ws目录下创建一个src文件夹用于存放软件包,以及builddevel等文件夹用于编译和开发过程中的输出。

2. 软件包(Package)

软件包是ROS中最小的开发单元,包含节点、消息、服务定义、库等资源。每个软件包都有一个package.xml文件,用于描述软件包的元信息,如名称、版本、依赖关系等。例如,一个名为my_package的软件包的package.xml文件可能包含如下内容:

<package format="2">
  <name>my_package</name>
  <version>0.0.1</version>
  <description>My custom ROS package</description>
  <maintainer email="user@example.com">User Name</maintainer>
  <license>BSD</license>
  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>roscpp</build_depend>
  <exec_depend>roscpp</exec_depend>
</package>

这表示该软件包名为my_package,依赖于roscpp库。

3. 元软件包(Metapackage)

元软件包是一个特殊的软件包,它不包含可执行代码,而是用于将多个相关的软件包组织在一起。元软件包通过package.xml文件中的<metapackage/>标签标识。例如,一个名为my_metapackage的元软件包可以包含多个子软件包,方便用户一次性安装和管理这些子软件包。

二、计算图

(一)概念

ROS计算图是由节点、话题、服务和参数等组成的分布式计算架构。它描述了ROS系统中各个组件之间的通信和协作关系,是ROS实现分布式机器人软件开发的核心机制。

(二)主要组成部分

1. 节点(Node)

定义

节点是ROS中执行特定功能的程序。每个节点可以独立运行,负责完成特定的任务,如传感器数据采集、数据处理、控制命令发布等。节点是ROS计算图的基本单元,通过与其他节点的通信实现复杂的功能。

示例

例如,一个名为camera_node的节点负责从摄像头获取图像数据,并将其发布到一个名为camera_image的话题上。另一个名为image_processor_node的节点订阅camera_image话题,对图像数据进行处理,如目标检测、边缘提取等。

2. 话题(Topic)

定义

话题是节点之间进行异步通信的渠道。节点可以通过发布(Publish)和订阅(Subscribe)话题来传递息。发布者将消息发送到指定的话题,订阅者从该话题接收消息。这种通信方式是多对多的,一个话题可以有多个发布者和多个订阅者。

示例

在前面的例子中,camera_node发布camera_image话题,image_processor_node订阅该话题。消息类型通常是一个预定义的ROS消息类型,如sensor_msgs/Image,用于描述图像数据的格式。

消息类型

ROS提供了丰富的消息类型,用于描述不同类型的数据。例如,std_msgs包中包含基本的数据类型消息,如std_msgs/Stringstd_msgs/Int32等;sensor_msgs包中包含传感器数据类型消息,如sensor_msgs/Imagesensor_msgs/LaserScan等。用户也可以自定义消息类型,通过创建.msg文件来定义新的消息结构。

3. 服务(Service)

定义

服务是节点之间进行同步通信的机制。服务调用者(Client)向服务提供者(Server)发送请求,服务提供者处理请求并返回响应。服务通信是请求 - 响应模式,适用于需要即时反馈的场景,如参数查询、命令执行等。

示例

例如,一个名为robot_control_node的节点提供一个名为move_robot的服务,用于控制机器人的运动。另一个节点可以通过调用move_robot服务,发送运动命令(如目标位置、速度等),robot_control_node处理这些命令并返回执行结果。

服务类型

ROS提供了多种预定义的服务类型,如std_srvs包中的基本服务类型,用户也可以自定义服务类型,通过创建.srv文件来定义新的服务结构。例如,一个自定义的服务MoveRobot.srv可能包含如下内容:

float64 x
float64 y
---
bool success
string message

这表示请求包含两个浮点数xy,表示目标位置;响应包含一个布尔值success和一个字符串message,表示操作是否成功和相关消息。

4. 参数(Parameter)

定义

参数是ROS中用于配置节点行为的键值对。参数可以存储在ROS参数服务器上,节点可以通过读取和写入参数来动态调整其行为。参数可以是基本数据类型,如整数、浮点数、字符串等,也可以是复杂的数据结构,如列表、字典等。

示例

例如,一个节点可能需要配置摄像头的分辨率参数。可以在ROS参数服务器上设置一个名为camera_resolution的参数,节点在启动时读取该参数,根据参数值调整摄像头的分辨率。参数可以通过命令行、配置文件或节点代码动态设置和读取。

三、消息传输机制

(一)话题通信机制

1. 发布 - 订阅模型

发布者(Publisher)

发布者节点创建一个发布者对象,指定要发布的话题名称和消息类型。发布者定期将消息发送到指定的话题。例如,使用roscpp库创建一个发布者:

#include <ros/ros.h>
#include <std_msgs/String.h>

int main(int argc, char **argv)
{
    ros::init(argc, argv, "talker");
    ros::NodeHandle nh;
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter", 1000);
    ros::Rate rate(10); // 10 Hz

    while (ros::ok())
    {
        std_msgs::String msg;
        msg.data = "hello world";
        pub.publish(msg);
        rate.sleep();
    }

    return 0;
}

这个节点每秒发布一次"hello world"消息到chatter话题。

订阅者(Subscriber)

订阅者节点创建一个订阅者对象,指定要订阅的话题名称和消息类型。订阅者通过回调函数接收消息。例如,使用roscpp库创建一个订阅者:

#include <ros/ros.h>
#include <std_msgs/String.h>

void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
    ROS_INFO("I heard: [%s]", msg->data.c_str());
}

int main(int argc, char **argv)
{
    ros::init(argc, argv, "listener");
    ros::NodeHandle nh;
    ros::Subscriber sub = nh.subscribe("chatter", 1000, chatterCallback);
    ros::spin();
    return 0;
}

这个节点订阅chatter话题,每当收到消息时,调用chatterCallback函数处理消息。

2. 消息队列

每个订阅者可以设置一个消息队列,用于缓存从发布者接收到的消息。队列的大小可以通过参数设置。如果队列已满,新的消息会替换掉队列中最旧的消息。这可以防止消息丢失,但也会增加内存使用。

3. 连接管理

ROS会自动管理发布者和订阅者之间的连接。当新的订阅者订阅某个话题时,ROS会通知发布者,建立连接。当订阅者取消订阅或关闭时,ROS会断开连接。发布者和订阅者之间通过TCP/IP协议进行通信,确保消息的可靠传输。

(二)服务通信机制

1. 请求 - 响应模型

服务提供者(Server)

服务提供者节点创建一个服务对象,指定服务名称和类型。服务提供者通过回调函数处理请求并返回响应。例如,使用roscpp库创建一个服务提供者:

#include <ros/ros.h>
#include <beginner_tutorials/AddTwoInts.h>

bool add(beginner_tutorials::AddTwoInts::Request &req,
         beginner_tutorials::AddTwoInts::Response &res)
{
    res.sum = req.a + req.b;
    ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
    ROS_INFO("sending back response: [%ld]", (long int)res.sum);
    return true;
}

int main(int argc, char **argv)
{
    ros::init(argc, argv, "add_two_ints_server");
    ros::NodeHandle nh;
    ros::ServiceServer service = nh.advertiseService("add_two_ints", add);
    ROS_INFO("Ready to add two ints.");
    ros::spin();
    return 0;
}

这个节点提供一个名为add_two_ints的服务,处理两个整数的加法请求。

服务调用者(Client)

服务调用者节点创建一个服务客户端对象,指定服务名称和类型。服务调用者通过调用服务并等待响应。例如,使用roscpp库创建一个服务调用者:

#include <ros/ros.h>
#include <beginner_tutorials/AddTwoInts.h>

int main(int argc, char **argv)
{
    ros::init(argc, argv, "add_two_ints_client");
    ros::NodeHandle nh;
    ros::ServiceClient client = nh.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");

    beginner_tutorials::AddTwoInts srv;
    srv.request.a = 42;
    srv.request.b = 7;

    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;
}

这个节点调用add_two_ints服务,发送两个整数427,并接收返回的和。

2. 同步通信

服务通信是同步的,服务调用者在发送请求后会阻塞,直到收到响应。这种机制确保了请求和响应的即时性,适用于需要即时反馈的场景,如参数查询、命令执行等。

3. 服务发现

ROS会自动管理服务提供者和服务调用者之间的连接。当服务调用者调用某个服务时,ROS会查找服务提供者并建立连接。如果服务提供者不存在,调用会失败。服务提供者和调用者之间通过TCP/IP协议进行通信,确保请求和响应的可靠传输。

通过这些机制,ROS实现了节点之间的高效、灵活的通信,支持复杂的机器人应用开发。

视频讲解

BiliBili: 视睿网络-哔哩哔哩视频 (bilibili.com)