ROS简介

官网 Wiki

The Robot Operating System (ROS) is a flexible framework for writing robot software. It is a collection of tools, libraries, and conventions that aim to simplify the task of creating complex and robust robot behavior across a wide variety of robotic platforms.

机器人操作系统(ROS)是一种用于编写机器人软件的灵活框架。 它是工具,库和协议的集合,旨在简化各种机器人平台上,去构建复杂而强大的机器人。

ROS 是 Robot Operating System的简写,翻译过来就是机器人操作系统。它是一个软件框架,目的是提供开发平台,工具及生态给开发人员,让开发人员快速的去开发强大的机器人系统。

ROS 的主要目标是为机器人研究和开发提供代码复用的支持。ROS是一个分布式的进程(也就是“节点”)框架,这些进程被封装在易于被分享和发布的程序包和功能包中。ROS也支持一种类似于代码储存库的联合系统,这个系统也可以实现工程的协作及发布。这个设计可以使一个工程的开发和实现从文件系统到用户接口完全独立决策(不受ROS限制)。同时,所有的工程都可以被ROS的基础工具整合在一起。

主要特点

  • 多语言支持(如C++、Python等)
  • 分布式架构(每一个工作进程都看作一个节点,使用节点管理器统一管理),
  • 良好的伸缩性(既可以写一个节点,也可以通过 roslaunch 将很多节点组织成一个更大的工程)
  • 源码开放(ROS遵循BSD协议,对个体和商业应用及修改完全免费)

安装及环境配置

  • 系统环境: Ubuntu 20.04 LTS (Focal)

  • 安装的ROS版本: ROS Noetic

    官方教程

  1. 设置sources.list

    由于ROS服务器架设在国外, 所以我们使用清华源镜像进行加速

    1
    sudo sh -c '. /etc/lsb-release && echo "deb http://mirrors.tuna.tsinghua.edu.cn/ros/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/ros-latest.list'
  2. 添加Keys

    1. 如果没有安装curl, 需要先安装, 执行

      1
      sudo apt install curl
    2. 添加Keys

      1
      curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add -
  3. 安装ROS

    1. 更新软件源

      1
      sudo apt update
    2. 安装ROS

      这里官方提供了三种安装包, 分别是

      • Desktop-Full Install

        桌面版中的所有内容以及 2D/3D 模拟器和 2D/3D 感知包

      • Desktop Install

        ROS-Base 中的所有内容以及 rqt 和 rviz 等工具

      • ROS-Base

        (Bare Bones) ROS 的打包、构建和通信库。没有 GUI 工具。

      如果没有什么特殊需求选择第一个即可

      1
      2
      3
      4
      5
      6
      7
      8
      #Desktop-Full Install (Recommended)
      sudo apt install ros-noetic-desktop-full

      #Desktop Install
      sudo apt install ros-noetic-desktop

      #ROS-Base
      sudo apt install ros-noetic-ros-base
  4. 环境设置

    每次从终端执行ros指令需要使用下面的命令进行初始化, 十分的繁琐, 我们可以将该指令写入./bashrc文件中, 这样每次启动终端后系统会自动加载下面的指令

    1
    source /opt/ros/noetic/setup.bash

    在终端中输入

    1
    2
    3
    4
    5
    # 写入配置文件
    echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc

    #重新加载终端
    source ~/.bashrc
  5. 配置构建包的依赖关系

    到目前为止,我们已经安装了运行核心 ROS 软件包所需的内容。要创建和管理您自己的 ROS 工作区,需要单独分发各种工具和要求。例如, rosinstall是一种常用的命令行工具,使我们可以通过一个命令轻松下载 ROS 软件包的许多源代码树。

    要安装此工具和其他用于构建 ROS 包的依赖项,运行:

    1
    sudo apt install python3-rosdep python3-rosinstall python3-rosinstall-generator python3-wstool build-essential

    通过下面代码, 初始化rosdep

    1
    2
    sudo rosdep init
    rosdep update
  6. 测试

    到目前为止, 已经完成了ROS所有的安装步骤, 执行下面命令测试ROS是否安装成功

    1
    roscore

    出现如下echo说明安装成功

    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
    $ roscore                                                       

    ... logging to /home/rvosy/.ros/log/3c54bc32-8c80-11ef-930a-ed8d0bf364f7/roslaunch-ubuntu20-72390.log
    Checking log directory for disk usage. This may take a while.
    Press Ctrl-C to interrupt
    Done checking log file disk usage. Usage is <1GB.

    started roslaunch server http://ubuntu20:35583/
    ros_comm version 1.17.0


    SUMMARY
    ========

    PARAMETERS
    * /rosdistro: noetic
    * /rosversion: 1.17.0

    NODES

    auto-starting new master
    process[master]: started with pid [72400]
    ROS_MASTER_URI=http://ubuntu20:11311/

    setting /run_id to 3c54bc32-8c80-11ef-930a-ed8d0bf364f7
    process[rosout-1]: started with pid [72410]
    started core service [/rosout]

ROS基础知识

Node节点和Package包

  1. Node节点

    Ros的项目结构是模块化的, 整个系统由多个不同的节点组合而成, 就如安卓手机一样, 有电话APP, 短信APP, 天气APP. Ros同理, Ros强调不同功能的相互独立, 每个Node节点往往只实现了一个单一的功能, 将这些节点整合起来, 就能实现一个复杂的功能

  2. Package包

    由于单个节点功能较为单一, 同时使用了CmakeCatkin进行编译, 引入了Package包的概念, 正好将散落的节点规整起来, 所以Ros中使用Package来将实现某个功能所需的所有节点进行打包, 一次安装一个包, 也就是一组节点, 省去了一个个安装节点的繁琐操作

Node节点的C++实现

  1. 创建工作空间

    目录结构为**/home/用户名/catkin_ws/src**

    我们的源代码需要放置在src中才能正常的进行编译, 执行:

    1
    2
    3
    4
    mkdir ~/catkin_ws
    cd catkin_ws
    mkdir src
    cd src
  2. 创建功能包

    确保自己在/catkin_ws/src目录下

    使用**catkin_create_pkg**命令创建功能包, 后面跟上包名以及要安装的依赖包

    1
    catkin_create_pkg hello_pkg rospy roscpp std_msgs
    • hello_pkg : 功能包名字, 可以自己指定
    • rospy : 执行python脚本的依赖, 只有安装了才能执行python编写的节点
    • roscpp : 执行C++代码的依赖包, 只有安装了才能执行由C++编写的节点
    • std_msgs : 标准消息包, 用于不同节点之间的通信
  3. 创建Node节点

    /catkin_ws/src/hello_pkg/src目录下新建一个Cpp文件 hello_node.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    #include <ros/ros.h>
    int main(int argc,char **argv[]){
    ros::init(argc, argv, "hello_node");
    while(ros::ok){
    printf("hello, node\n");
    }
    return 0;
    }
    • 使用ros::init()函数来初始化节点, 使节点和ros系统产生联系
    • 在while循环中, 循环条件需要使用ros::ok, 不能使用while(1), 只有使用了ros::ok, 程序才能响应外部的终止信号, 否则将会无法停止ros节点
  4. 源码编译

    1. 配置CMakeList编译规则

      在执行编译操作前, 需要先配置CMakeList文件

      打开CMakeList文件, 找到如下两组注释,

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      ## Declare a C++ executable
      ## With catkin_make all packages are built within a single CMake context
      ## The recommended prefix ensures that target names across packages don't collide
      # add_executable(${PROJECT_NAME}_node src/ssr_pkg_node.cpp)


      ## Specify libraries to link a library or executable target against
      # target_link_libraries(${PROJECT_NAME}_node
      # ${catkin_LIBRARIES}
      # )

      ${PROJECT_NAME}和后面的文件名更改为自己实际的文件名, 添加到CMakeList文件末尾

      1
      2
      3
      4
      5
      6
      7
      #添加节点文件
      add_executable(hello_node src/hello_node.cpp)

      #链接编译库
      target_link_libraries(hello_node
      ${catkin_LIBRARIES}
      )
    2. 编译

      进入/catkin_ws目录, 执行catkin_make

      1
      2
      cd ~/catkin_ws
      catkin_make
    3. 写入环境变量

      终端执行:

      1
      echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
  5. 运行Node节点

    1. 启动ROS系统核心

      打开终端, 执行指令:

      1
      roscore
    2. 启动node节点

      新建一个终端. 执行rosrun <包名> <节点文件名>

      1
      rosrun hello_pkg hello_node

      可以看到终端在不断打印hello, node

Topic话题和Message消息

在Ros系统中, 由于不同功能被分散到不同的节点上运行, 不同的节点需要相互通信以进行数据交换, 便引入了话题和消息两个概念

  1. 话题Topic

    话题是一种节点与节点间进行一对多、单向传输的数据通信方式,数据通过话题的发布与订阅实现传送.

    一个话题由话题发布者Publisher订阅者Subscriber组成

    特点:

    • 一个ROS节点网络中, 可以存在多个话题.
    • 一个话题可以有多个发布者, 也可以有多个订阅者.
    • 一个节点可以对多个话题进行订阅, 也可以发布多个话题.
    • 不同传感器消息通常会拥有各自独立的话题名称, 每个话题只有一个发布者.
    • 机器人速度指令话题通常会有多个话题发布者, 但是同一时间只能有一个发言人
  2. 消息Message

    消息的作用是携带消息, 从发布者流动到订阅者, 传感器话题传递的是传感器数据, 速度话题传递的是速度指令, 这些消息传递的内容和数据格式都不同, 所以消息的格式也分为很多种不同的类型, 以满足不同数据的传输要求. 我们在生成消息包的时候需要指定消息的类型. 可以在ROS Index中搜索对应的消息包来查看该消息包有多少数据类型

    1. 搜索std_msgs, 可以看到不同版本的ROS有不同的消息格式, 选择当前Noetic版本的消息包

      image-20241021231112540

    2. 依次点击Wiki-Msg API便可看到当前消息包所包含的所有消息格式

      image-20241021231714168

image-20241021231851976

Publisher发布者的C++实现

  1. 源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #include <ros/ros.h>
    #include <std_msgs/String.h>

    int main(int argc,char **argv[]){
    ros::init(argc, argv, "hello_node");

    ros::NodeHandle nh;
    ros::Publisher pub = nh.advertise<std_msgs::String>("first_topic", 10)

    while(ros::ok){
    printf("hello, node\n");
    std_msgs::String msg;
    msg.data = "hello msgs";
    pub.publish(msg);
    }
    return 0;
    }
  2. 说明

    1. 创建nh句柄和发布对象

      首先需要引入对应消息头文件#include <std_msgs/String.h>

      1
      2
      ros::NodeHandle nh;
      ros::Publisher pub = nh.advertise<std_msgs::String>("年轻人的第一个Topic话题",10)

      通过Nodehandle创建节点句柄, 将nh句柄的advertise方法赋值给pub对象

      advertise方法是一个泛型函数, 需要使用尖括号<>传入话题内传输的数据类型进行初始化

      函数的参数为: nh.advertise(“话题名称”,”缓存长度”)

      • 话题名称
        • 话题取名, 用于分辨不同话题
      • 缓冲长度
        • 当发送数据的速度快于订阅者的接收速度时, 来不及处理的数据会以队列形式存储, 当消息个数大于设定的缓存长度时, 最先到达的消息会被丢弃
    2. 初始化消息类型

      std_msgs::String msg;

      初始化一个std_msgs类String类型的消息msg, 将数据传入msg的data成员msg.data = "你好";

    3. 发布消息

      pub.publish(msg);

      将消息msg交给pub对象的publish函数进行发布消息

  3. 验证

    1. 启动ros系统和ros节点

    2. 查看话题情况

      使用rostopic来对话题进行操作

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      #使用list查看当前活跃的话题
      $ rostopic list
      /first_topic
      /rosout
      /rosout_agg
      #使用 "echo + 话题名称" 来查看选定话题内消息内容
      $ rostopic echo /first_topic
      data: "Hello msgs"
      ---
      data: "Hello msgs"
      ........

      #使用 "hz + 话题名称" 来查看选定话题内消息的发送频率
      $ rostopic hz /first_topic
      subscribed to [/first_topic]
      average rate: 66725.765
      min: 0.000s max: 0.003s std dev: 0.00004s window: 50000
      average rate: 64036.867
      min: 0.000s max: 0.003s std dev: 0.00004s window: 50000
      .....
      • 可以在while循环前加上ros::Rate loop_rate(), 并且使用loop_rate.sleep()来控制while循环的频率, 从而控制消息发送的频率

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        ros::Rate loop_rate(10); //表示一秒10次
        while(ros::ok){
        printf("hello, node\n");
        std_msgs::String msg;
        msg.data = "hello msgs";
        pub.publish(msg);
        //该函数会阻塞while循环,使得while循环的频率能和上面设定10hz对得上
        loop_rate.sleep();
        }

小结:

  • 确定话题名称消息类型
  • 在代码文件中引入消息类型对应的头文件
  • 在main函数中使用NodeHandle发布一个话题并得到消息发送对象
  • 生成要发送的消息包并进行发送数据的赋值
  • 调用消息发送对象的publish()函数将消息包发送到话题当中

Subscriber订阅者的C++实现