写在前面:

本文章是根据 ROS 官方文档以及其他相关资料翻译、整理而来。碍于本人学识有限,且非本专业人士,部分叙述难免存在纰漏,请读者注意甄别。

使用版本:

  • Linux 20.04 LTS
  • ROS Noetic (ROS1)

参考资料:

0. 安装

ROS Noetic / linux 20.04

ROS2 Humble / linux 22.04

  1. 首先确保我们所使用的系统环境支持utf-8编码。使用如下命令查看:

    1
    locale	# check for utf-8
  2. 添加 ROS2 apt 的依赖:

    • 首先确保 Ubuntu Universe repository 是可用的:

      1
      2
      sudo apt install software-properties-common
      sudo add-apt-repository universe
    • 使用 apt 添加 ROS2GPG公钥

      1
      2
      sudo apt update && sudo apt install curl -y
      sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
    • 然后添加 ROS2 的 repo 到我们的 sources.list 中:

      1
      echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
  3. 安装 ROS 开发工具

    • 安装公共基础软件包:

      1
      2
      3
      4
      5
      6
      sudo apt update && sudo apt install -y \
      python3-flake8-docstrings \
      python3-pip \
      python3-pytest-cov \
      python3-vcstool \
      ros-dev-tools
    • 安装基于 linux-22.04 版本的软件包:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      sudo apt install -y \
      python3-flake8-blind-except \
      python3-flake8-builtins \
      python3-flake8-class-newline \
      python3-flake8-comprehensions \
      python3-flake8-deprecated \
      python3-flake8-import-order \
      python3-flake8-quotes \
      python3-pytest-repeat \
      python3-pytest-rerunfailures
  4. 当我们要使用 ROS2 相关的命令时,我们每次都需要先输入以下命令:

    1
    2
    3
    # Replace ".bash" with your shell if you're not using bash
    # Possible values are: setup.bash, setup.sh, setup.zsh
    source /opt/ros/humble/setup.bash

    当然,我们可以将其写入 .bashrc 文件(或其他类似配置)中:

    1
    echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc		# 或其他类似的bash配置

    然后检查环境变量是否设置成功:

    1
    printenv | grep -i ROS

    检查ROS_DISTROROS_VERSION是否设置成功:

    1
    2
    3
    ROS_VERSION=2
    ROS_PYTHON_VERSION=3
    ROS_DISTRO=humble
  5. 克隆一个演示项目到 workspacesrc 中:

    1
    2
    3
    mkdir -p ~/ws_ros2_humble/src
    cd ~/ws_ros2_humble
    vcs import --input https://raw.githubusercontent.com/ros2/ros2/humble/ros2.repos src
  6. 使用 rosdep 安装这个演示项目需要使用的依赖:

    (推荐在每次安装新依赖之前更新一遍系统的依赖环境)

    1
    sudo apt upgrade
    1
    2
    3
    sudo rosdep init
    rosdep update
    rosdep install --from-paths src --ignore-src -y --skip-keys "fastcdr rti-connext-dds-6.0.1 urdfdom_headers"
  7. 创建(Build)工作空间下的所有代码:

    注意 ⚠️

    如果安装过其他 ROS 版本,请先确保在 .bashrc(或其他类似配置)中不存在 source /opt/ros/${ROS_DISTRO}/setup.bash,以确保避免其他 ROS 版本的干扰。

    同时,可以使用命令printenv | grep-i ROS,确保其输出为空。

    • Build the code
    1
    2
    cd ~/ros2_humble/
    colcon build --symlink-install
  8. 代码创建完成后,我们首先需要配置局部的运行环境:

    1
    2
    3
    # Replace ".bash" with your shell if you're not using bash
    # Possible values are: setup.bash, setup.sh, setup.zsh
    . ~/ros2_humble/install/local_setup.bash
  9. 然后尝试运行一些实例:

    • 终端1
    1
    2
    3
    # python ver.
    . ~/ros2_humble/install/local_setup.bash
    ros2 run demo_nodes_py listener
    • 终端2
    1
    2
    3
    # python ver.
    . ~/ros2_humble/install/local_setup.bash
    ros2 run demo_nodes_py talker

    观察程序运行的过程。

1. ROS 入门知识

1.1 ROS 简介

ROS(Robotic Operation System,机器人操作系统)。简单点说,ROS 就是一个分布式的通信框架,帮助程序进程之间更方便的通信。通常在一个机器人中包含多个部件,每个部件都有配套的控制程序;或者在机器人集群中协调多个机器人,这正是 ROS 设计的初衷。他并不是一个真正意义上的操作系统,其底层的任务调度、编译、设备驱动等还是由他它的原生操作系统 Linux 完成。

ROS 的核心思想就是将机器人的软件功能做成一个个节点,节点之间通过互相发送消息进行沟通。这些节点可以部署在同一台主机上,也可以部署在不同的主机、甚至互联网上。在 ROS1 中,网络通信机制中的主节点(master)负责对网络中各个节点之间的通信过程进行调度管理,同时提供一个用于配制网络中全局参数的服务。

同时,ROS 时松耦合软件架构,提供了丰富开源的功能库。

1.2 ROS 开发环境的搭建

1.2.1 ROS 的安装

使用 ROS 进行开发时,一般需要机器人工作台两个部分。机器人通常采用低功耗的 ARM 平台嵌入式主板作为主机;工作台大多选择 X86 平台作为主机。

image-20231109101914944

官员 ROS 的安装,详见第〇章

1.2.2 ROS 文件的组织方式

ROS 的文件被放在系统空间工作空间两个地方。

系统空间

系统空间是存放 ROS 系统安装包的目录,在 /opt/ros/ 目录中存放着 roscorervizrqt 等 ROS 核心工具。要使用他们,需要先使用如下命令激活系统空间:

1
2
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc

工作空间

工作空间是用户开发自己程序的文件夹,里面用来存放用户开发的功能包程序,文件是源代码形式。ROS 的开源社区有非常多的功能包,用户可以直接使用 apt 命令安装二进制功能包到系统空间,也可以将源代码下载到工作空间中,然后手动编译。工作空间的功能包可以直接覆盖系统空间下的同名功能包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 新建工作空间
mkdir ~/catkin_ws/src

# 初始化 src 目录
cd ~/catkin_ws/src
catkin_init_workspace

# 对工作空间进行编译
cd ~/catkin_ws
catkin_make

# 激活工作空间
echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
source ~/.bashrc

1.2.3 ROS 网络通行配置

在构成 ROS 网络通信的各台主机中,必须指明一台主机作为主节点负责管理整个 ROS 网络通信,同时要声明参与通信的各个主机(配置每台主机中的环境变量 MASTERHOST)。

打开 ~/.bashrc 文件,添加如下环境变量

1
2
export ROS_MASTER_URI=http://localhost:[PORT]
export ROS_HOSTNAME=localhost

1.3 ROS 系统架构

按照官方的说法,可以分别从计算图结构、文件系统和开源社区视角来理解 ROS 架构。

1.3.1 从计算图视角理解 ROS 结构

ROS 中可执行程序的基本单位叫节点(node),节点之间通过消息机制进行通讯,这样就组成了一张网状图,也叫计算图。如下图

image-20231109111439507

附录1. 一些常用的命令

1. 关于 ROS 文件系统的导航

  • 如何在 ROS 中找到某个 package 在本机文件系统中的位置:

    1
    rospack find [PACKAGE_NAME]

    rospack 命令可以允许我们得到和 package 相关的信息。

  • 如何查看 ROS 环境中生效的所有 package 在本机文件系统中的位置:

    1
    echo $ROS_PACKAGE_PATH
  • 如何直接切换到某个 package 在本机文件系统中的位置:ROS + cd

    1
    roscd <PACKAGE_NAME> [/SUBDIR]
  • 如何直接查看某个 package 下的内容:ROS + ls

    1
    rosls <PACKAGE_NAME> [/SUBDIR]

2. 关于 package 的创建

  • ROS Neotic 中 Workspace 的文件组成结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    workspace_floder/					-- WORKSPACE
    src/ -- SOURCE SPACE
    CMakeLists.txt -- 'Toplevel' Cmake file, provided by catkin
    package_1/
    CMakeLists.txt -- 'CMakeLists.txt' file for package_1
    package.xml -- Package manifest for package_1
    ...
    ...
    package_n/
    CMakeLists.txt -- 'CMakeLists.txt' file for package_n
    package.xml -- Package manifest for package_n
    ...
  • 如何创建一个 catkin package

    1
    cd [YOUR_CATKIN_WORKSPACE]/src
    1
    2
    catkin_create_pkg <PACKAGE_NAME> [DEPENDS...]
    # 这里的 [DEPENDS...] 可以有多个参数,这些 depends 都是该 package 的直接(first-order)依赖
  • 如何 build 整个工作空间并 source 这个工作空间的 setup.bash

    1
    2
    cd [YOUR_CATKIN_WORKSPACE]
    catkin_make

    此时,一些文件夹会在 [YOUR_CATKIN_WORKSPACE] 中创建,如 devel/

    1
    2
    # 这里取决于你用的bash的类型,普通的bash:setup.bash, zsh:setup.zsh
    source [YOUR_CATKIN_WORKSPACE]/devel/setup.bash
  • 如何查询一个 package 的依赖 (dependency):

    1
    2
    # 查询一个 package 的直接(first-order)依赖
    rospack depends1 [PACKAGE_NAME]
    1
    2
    # 查询一个 package 的所有依赖
    rospack depends [PACKAGE_NAME]

3. 关于 packagebuild(构建)

  • 如何使用 catkin_make 命令行工具:

    catkin_make 命令行工具将标准 CMake 工作流程中对 cmakemake 的调用结合在了一起。

    1
    catkin_make [MAKE_TARGETS] [-DCMAKE_VARIABLES=...]

    上述命令将构建在 src/ 文件夹中找到的任何 catkin 项目。如果您的源代码放在不同的地方,比如 my_src/,那么您可以这样调用 catkin_make

    1
    catkin_make --source my_src
  • 如何 build 整个工作空间并 source 这个工作空间的 setup.bash

    1
    2
    cd [YOUR_CATKIN_WORKSPACE]
    catkin_make

    此时,一些文件夹会在 [YOUR_CATKIN_WORKSPACE] 中创建,如 devel/

    1
    2
    # 这里取决于你用的bash的类型,普通的bash:setup.bash, zsh:setup.zsh
    source [YOUR_CATKIN_WORKSPACE]/devel/setup.bash

4. 关于 rosnode 的一些操作

  • 当我们使用 ROS 时,roscore 是第一个应该被执行的命令:

    1
    roscore
  • rosnode 显示当前正在运行的 ROS 节点信息:

    1
    2
    # 该命令会列出所有处于活动状态的 node
    rosnode list
    1
    2
    # 该命令会返回指定 node 的信息
    rosnode info <SPECIFIC_NODE>
    1
    2
    # 该命令会测试指定 node 是否可访问
    rosnode ping <SPECIFIC_NODE>

注意⚠️

每当打开一个新终端时,你的环境会被重置,你的 ~/.bashrc 文件也会被 source

如果在运行 rosnode 等命令时遇到困难,则可能需要在 ~/.bashrc 中添加一些环境设置文件,或手动重新获取它们。

  • rosrun 直接运行包中的节点:

    1
    rosrun <PACKAGE_NAME> <NODE_NAME>

5. 关于 rostopic 的操作

使用 rostopic 工具可以获取有关 ROS topic 的信息。

你可以使用帮助选项(-h)获取 rostopic 的可用子命令:

1
rostopic -h
1
2
3
4
5
6
rostopic bw 		# 显示 topic 使用的带宽
rostopic echo # 打印 topic 信息到屏幕
rostopic hz # 显示 topic 的发布率
rostopic list # 打印 topic 主题的信息
rostopic pub # 向 topic 发布数据
rostopic type # 打印 topic 类型
  • rostopic list

    rostopic list 返回当前订阅和发布的所有主题的列表。

    1
    rostopic list -h
    1
    2
    3
    4
    5
    6
    7
    8
    使用方法: rostopic list [/topic] (主题列表)

    选项
    -h、--help 显示帮助信息并退出
    -b BAGFILE, --bag=BAGFILE 列出 .bag 文件中的主题
    -v, --verbose 列出每个主题的全部详细信息
    -p 只列出发布者
    -s 只列出订阅者
  • rostopic type

    rostopic type 返回正在发布的任何主题的信息类型。

    1
    rostopic type [TOPIC]

    我们可以使用 Linux 系统中的管道符 | 来直接查看指定 topic 的内容格式定义,以消息 msg 为例:

    1
    rostopic type [TOPIC] | rosmsg show
  • rostopic pub

    rostopic pub 向当前指定 topic 的数据。

    1
    rostopic pub [TOPIC] [MSG_TYPE] [ARGS]

    turtlrsim 为例,

    1
    rostopic pub -1 /turtle1/cmd_vel geometry_msgs/Twist -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, 1.8]'

    其中:

    • rostopic pub 命令将向指定主题发布消息
    • 选项(-1)会导致 rostopic 只发布一条消息,然后退出
    • /turtle1/cmd_vel 是要发布到的主题名称
    • geometry_msgs/Twist 是发布到主题时要使用的信息类型
    • 选项(--)告诉选项解析器以下参数都不是选项。如果参数前有破折号"-",例如负数,则需要使用此选项
    • geometry_msgs/Twist 类型的 msg 有两个向量,每个向量有三个浮点元素:线性和角度。在本例中,"[2.0, 0.0, 0.0]" 是线性值,其中 x=2.0、y=0.0、z=0.0"[0.0, 0.0, 1.8]" 是角度值,其中 x=0.0、y=0.0、z=1.8。这些参数实际上是 YAML 语法,在 YAML 命令行文档中有更多描述。

    我们可以使用 rostopic pub -r 命令发布重复连续的命令。同样以 turtlrsim 为例,我们希望发送命令的频率为 1Hz,则有如下命令

    1
    rostopic pub /turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'

6. rosservicerosparam 的相关操作

  • rosservice

    1
    2
    3
    4
    5
    rosservice list 		# 打印有关活动服务的信息
    rosservice call # 使用提供的参数调用服务
    rosservice tpye # 打印服务类型
    rosservice find # 按服务类型查找服务
    rosservice uri # 打印服务 ROSRPC uri
  • rosparam

    1
    2
    3
    4
    5
    6
    rosparam set 		# 设置参数
    rosparam get # 获取参数
    rosparam load # 从文件加载参数
    rosparam dump # 将参数转储到文件
    rosparam delete # 删除参数
    rosparam list # 列出参数名称

7. 使用 rqt 相关工具

  • 使用 apt 工具安装 rqt

    1
    sudo apt install ros-$ROS_DISTRO-rqt ros-$ROS_DISTRO-rqt-commom-plugins
  • 使用 rqt_graph 工具:

    rqt_graph 可以用于可视化 ROS 节点(nodes)、话题(topics)、服务(services)和参数(parameters)之间的关系,以帮助用户更好地理解和调试 ROS 系统。

    1
    rosrun rqt_graph rqt_graph
  • 使用 rqt_plot 工具:

    rqt_plot 工具用于实时绘制和可视化 ROS 话题(topics)中的数据。它通常用于监视传感器数据、控制输出或其他实时生成的数值数据,以便用户可以更轻松地理解和分析这些数据。

    1
    rosrun rqt_plot rqt_plot
  • 使用 rqt_consolerqt_logger_level 工具:

    rqt_console 工具用于查看和管理 ROS 系统中的日志消息。它主要用于调试和监视 ROS 节点的日志消息,帮助开发人员在开发和运行 ROS 应用程序时快速发现和解决问题。

    rqt_logger_level 工具用于查看和管理ROS节点的日志级别。

    1
    2
    rosrun rqt_console rqt_console
    rosrun rqt_logger_level rqt_logger_level

8. 使用 .launch 文件启动节点

关于 .launch 文件的讲解

1
roslaunch [PACKAGE] [FILENAME.launch]

附录2. 远程控制ROS机器人的两种方式

注意 ⚠️:

远程控制 ROS 机器人的方式可能并非两种,这里只是笔者接触到的两种最常用的控制形式。在此记录,防止日后忘记。同时也将记录使用这两种方法遇到的问题,以及当时的解决方法。

参考文章:

1. ROS 主从节点控制(ROS Noetic)

在 ROS 第一个版本中(这里指 ROS Noetic),可以设置一台主机(MASTER)和多台从机(SLAVE)。

注意 ⚠️:

这种“主从机制”只针对与 ROS 第一个版本,ROS2 中似乎不再有“主从”的概念。详细请参考ROS2。

我们可以在主机中启动 MASTER 节点,从机启动其他节点,这样就可以在这些配置好的主从机之间相互通信,其中的网络通信过程对于我们而言是透明的,就像是在同一台主机上。

以下是配置过程:

  1. 使用该方法的前提是两台均已安装好 ROS 环境的机器(最好是同版本,不同版本未测试)。

  2. 设置两台主机的 IP 地址:

    在同一网段下设置两台机器的静态 IP,以笔者测试环境举例:

    • 网关地址:192.168.2.1
    • 子网掩码:255.255.255.0
    • 主机 IP(Jetson):192.168.2.200
    • 从机 IP(Ubuntu):192.168.2.201
  3. 修改 .bashrc.zshrc 等系统环境配置:

    • 主机(Jetson)中添加:

      1
      2
      3
      export ROS_HOSTNAME=$YOUR_HOSTNAME	# 设置本机的Host,一般为IP地址
      export ROS_IP=$YOUR_IP # 设置本机的IP地址
      export ROS_MASTER_URI=http://$YOUR_IP:11311 # 将本机设置为主机
    • 从机(Ubuntu)中添加:

      1
      2
      3
      export ROS_HOSTNAME=$YOUR_HOSTNAME	# 设置本机的Host,一般为IP地址
      export ROS_IP=$YOUR_IP # 设置本机的IP地址
      export ROS_MASTER_URI=http://$YOUR_MASTER_NODE_IP:11311 # 将本机设置为主机

    注意 ⚠️:

    这里的 ROS_MASTER_URI 特指主机的 URI,受同时主机控制的从机该属性均相同。

  4. 修改 /etc/hosts 文件:

    我们需要修改主从机的 /etc/hosts 文件,这样主从机之间才可以相互通信。如果主机的 /etc/hosts 文件中没有添加从机的信息,从机只能接收到主机发来的消息,而不能向主机发送消息。

    1
    sudo vim /etc/hosts
    • 主机中添加:

      1
      2
      3
      # ROS_SLAVE_MACHINE
      # MACHINE_IP MACHINE_HOST
      $YOUR_SLAVE_IP $YOUR_SLAVE_HOSTNAME
    • 从机中添加:

      1
      2
      3
      # ROS_MASTER_MACHINE
      # MACHINE_IP MACHINE_HOST
      $YOUR_MASTER_IP $YOUR_MASTER_HOSTNAME
  5. 测试:

    现在我们就可以测试主从机之间能否顺利通信。

    • 主机上运行:

      1
      2
      roscore				# 开启 ROS 节点
      rostopic echo /test_topic # 监听 /test_topic 话题
    • 从机上运行:

      1
      2
      # 向 /test_topic 话题发送1次内容为 'Hello from the other side!' 的 std_msgs/String 消息
      rostopic pub /test_topic std_msgs/String -1 -- 'Hello from the other side!'
    • 观察主机输出:

      1
      data: "Hello from the other side!"

2. 通过 ROSBridge_server 的方法实现 websocket 控制

参考文章:

2.1 介绍 rosbridge_suite

ROSBridge非 ROS 程序提供了 ROS 功能的 JSON API。有多种前端与 rosbridge 交互,包括用于 Web 浏览器交互的 WebSocket 服务器。rosbridge_suite 是一个元包,包含 rosbridge、rosbridge 的各种前端包(如 WebSocket 包)和帮助程序包。

rosbridge 有两个部分:协议和实现。

2.1.1 ROSBridge 协议

ROSBridge 协议是用于向 ROS 发送基于JSON 的命令的规范。订阅主题的协议示例:

1
2
3
4
{ "op": "subscribe",
"topic": "/cmd_vel",
"type": "geometry_msgs/Twist"
}

该规范与编程语言和传输层无关。其目的在于,任何可以发送 JSON的语言或传输都可以通过 ROSBridge 协议与 ROS 交互。该协议涵盖订阅(Subscribe)和发布(Publish)主题、服务(Service)调用、获取和设置参数(Parameter),甚至压缩消息等。

2.1.2 ROSBridge 的实现

rosbridge_suite 包是实现 ROSBridge 协议并提供 WebSocket 传输层的包的集合。

rosbridge_suite 包括:

  • rosbridge_library:核心 rosbridge 包。rosbridge_library 负责获取 JSON 字符串并将命令发送到 ROS,反之亦然。
  • rosapi:通过通常为ROS 客户端库保留的服务调用来访问某些 ROS 操作。这包括获取和设置参数、获取主题列表等等。
  • rosbridge_server:虽然 rosbridge_library 提供 JSON<->ROS 转换,但它将传输层留给了其他人。rosbridge_server 提供WebSocket连接,以便浏览器可以“与 rosbridge 对话”。roslibjs是一个用于浏览器的JavaScript库,可以通过 rosbridge_server 与 ROS 通信。

2.2 ROSBridge 的安装

  • ROSBridge 已提供 Debian 版本以供安装。
1
2
sudo apt install ros-$ROS_DISTRO-rosbridge-suite
sudo apt install ros-$ROS_DISTRO-rosbridge-server

2.3 ROSBridge 的使用

要启动 ROSBridge 及其软件包(如 rosbridge_serverrosapi),安装中包含 .launch 文件。要启动该文件,输入如下命令运行:

1
roslaunch rosbridge_server rosbridge_websocket.launch address:=$YOUR_IP port:=$PORT	# default port 9090
  1. 我们在自己的工作空间下,从创建 package 开始:

    1
    2
    cd $YOUR_WORKSPACE/src
    catkin_create_pkg test_rosbridge --catkin-deps rosbridge_server
  2. package 中创建一个 .launch 文件:

    1
    2
    mkdir $YOUR_WORKSPACE/test_rosbridge/launch
    vim $YOUR_WORKSPACE/test_rosbridge/launch/test_websocket.launch

    test_websocket.launch 文件中写入如下的内容:

    1
    2
    3
    4
    5
    6
    <launch>
    <include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch">
    <-- 默认端口为 9090 --/>
    <arg name="port" value="9090"/>
    </include>
    </launch>
  3. 然后我们测试 rosbridge_server

    1
    2
    3
    4
    cd $YOUR_WORKSPACE
    catkin_make
    source devel/setup.bash # 或 devel/setup.zsh
    roslaunch test_rosbridge test_websocket.launch

    最后我们可以观察 terminal 中的输出:

    1
    [INFO] [1561100304.196110]: Rosbridge WebSocket server started at ws://0.0.0.0:9090

2.4 JavaScript 通过 WebSocket 使用 ROSBridge

  • 【目标】通过网页(JavaScript)与主机的 ROS 环境通信
  • 【前提配置】在同一网段下设置主机的静态 IP,以笔者测试环境举例:
    • 网关地址:192.168.2.1
    • 子网掩码:255.255.255.0
    • 主机 IP(Jetson):192.168.2.200

注意 ⚠️

这里是一个没有 ROS 环境的机器ROS 主机之间的通信。

2.4.1 创建一个 html 文件

  1. 我们从最简单的本机控制开始,创建一个 html 文件:

    1
    2
    mkdir $YOUR_WORKSPACE/web
    vim $YOUR_WORKSPACE/web/test_websocket.html
    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    <!DOCTYPE html>
    <html>

    <head>
    <meta charset="utf-8" />

    <!-- 导入 robot web tool JS 包 -->
    <script type="text/javascript"
    src="https://cdn.jsdelivr.net/npm/eventemitter2@6.4.9/lib/eventemitter2.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/roslib@1/build/roslib.min.js"></script>

    <script type="text/javascript" type="text/javascript">
    var ros = new ROSLIB.Ros({
    url: 'ws://192.168.2.200:9090'
    });

    ros.on('connection', function () {
    document.getElementById("status").innerHTML = "Connected";
    console.log('Connected to websocket server.');
    });

    ros.on('error', function (error) {
    document.getElementById("status").innerHTML = "Error";
    console.log('Error connecting to websocket server: ', error);
    });

    ros.on('close', function () {
    document.getElementById("status").innerHTML = "Closed";
    console.log('Connection to websocket server closed.');
    });
    </script>
    </head>

    <body>
    <h1>Simple ROS User Interface</h1>
    <p>Connection status: <span id="status"></span></p>
    <p>Last /test_topic received: <span id="msg"></span></p>
    </body>

    </html>

    💡代码解释:

    • 首先,我们从 Robot Web Tools CDN 导入需要的 JS 包;

    • 我们创建一个 ROS 对象,用于稍后与其他节点通信。目前,我们在同一台机器上运行 JS 和 rosbridge,因此监听的是 localhost 和默认的 9090 端口:

      1
      2
      3
      var ros = new ROSLIB.Ros({
      url: 'ws://localhost:9090'
      });

      当我们需要访问从远端访问同网段下的 ROS 主机时,我们需要将这里的 url 参数改为 ROS 主机参数:

      1
      url: 'ws://192.168.2.200:9090'
    • 我们为 ROS 事件(本例中为连接事件)创建了监听器。当事件发生时,我们会找到一个 id="status" 的 HTML 元素(在本例中是我们在 <body> 中定义的 <span> 元素),然后将 HTML 的内部更改为我们想要显示的信息,并在控制台打印消息。

      1
      2
      3
      4
      ros.on('connection', function () {
      document.getElementById("status").innerHTML = "Connected";
      console.log('Connected to websocket server.');
      });
    • 此时,我们用浏览器打开这个 HTML 文件,我们可以看到页面上显示:
    1
    Connection status: Connected
    • 打开浏览器的控制台(快捷键 F12),我们可以看到:
    1
    test_websocket.html:19 Connected to websocket server.
    • ROS 主机的 log 打印:
    1
    2
    [INFO] [1562100304.196110]: Client connected. 1 clients total.
    [INFO] [1562101304.196110]: Client disconnected. 0 clients total.

2.4.2 订阅一个主题

  1. 下一步,我们将订阅一个主题,接受字符串信息并将其显示在浏览器窗口中。我们将把代码放在 HTML 脚本中,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script type="text/javascript" type="text/javascript">
    var test_topic = new ROSLIB.Topic({
    ros: ros,
    name: '/test_topic',
    messageType: 'std_msgs/String'
    });

    test_topic.subscribe(function (data) {
    document.getElementById("msg").innerHTML = data.data;
    });
    </script>

    这段代码声明了一个名为 test_topic_listenerROSLIB.Topic 对象,该对象将监听 /test_topic主题上的消息,并接受 std_msgs/String 类型的消息。然后,我们创建一个事件监听器(test_topic.subscribe()),它将调用一个匿名函数,将 "msg"<span> 元素的内部 HTML 更改为消息的数据字段。

    要测试这段代码,请按以下步骤操作:

    • ROS 主机的 log 打印:

      1
      2
      [INFO] [1762100304.196110]: Client connected. 1 clients total.
      [INFO] [1762142345.496110]: [Client 2] Subscribed to /test_topic

      思考 ❓:

      在这里我们看到,[Client 2] 表现出 ROSBridge_server 应该存在一个数据结构来维护连接的 Client,而且刷新后数字会自增。那么通过用户的不断刷新,当这个数据结构的资源枯竭时,ROSBridge_server 的策略是什么?会直接退出?还是报错?或者会自动清除失效的连接(Session?)?

    • 在 ROS 主机上打开一个终端,输入:

      1
      2
      # 向 /test_topic 话题发送1次内容为 'Hello from Master Node!' 的 std_msgs/String 消息
      rostopic pub /test_topic std_msgs/String -1 -- 'Hello from Master Node!'
    • 观察 HTML 页面,信息应出现在我们为此创建的 <span> 字段中:

      1
      2
      Connection status: Connected
      Last /test_topic received: Hello from Master Node!

2.4.3 发布一个话题

  1. 与订阅主题的做法类似,我们依然使用 test_topicROSLIB.Topic 对象,该对象将在/test_topic 主题上发布 std_msgs/String 消息。然后,我们创建一个按钮(button),通过点击按钮来触发名为 test_publish() 的函数,通过 test_topic 对象发布该消息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <script type="text/javascript" type="text/javascript">
    var test_topic = new ROSLIB.Topic({
    ros: ros,
    name: '/test_topic',
    messageType: 'std_msgs/String'
    });

    function test_publish(){
    var msg = new ROSLIB.Message({
    data: 'Hello from JS!'
    });
    test_topic.publish(msg)
    }
    </script>

    <body>
    <button class="button" onclick="test_publish()">Publish</button>
    </body>

附录3. 关于 Linux 系统外接设备固定端口号的问题

1. USB 及串口设备

参考文件:


在开发 ROS 机器人的过程中经常需要会遇到的多个设备的接驳,但是在 Linux 系统中对于外部设备端口号的分配和上电顺序相关,所以系统每次读取到的设备端口号都不同。如果是开机启动或者接在同一 HUB 下面,则由系统枚举各 USB 设备的顺序相关。以下介绍通过 udev 工具实现为 USB 转串口设备固定串口名称的实现原理与方法,该方法也适用于其他 USB 设备驱动。`

为了使启动脚本顺利的运行,我们需要将这些端口号固定下来。

udev 运行在用户态,脱离驱动层的关联,基于这种设计实现,用户可以通过编写规则来动态删除和修改 /dev下的设备文件,任意命名设备。除了设备重命名外,还拥有修改设备访问权限的功能,可以实现在普通用户模式下操作 /dev 下系统设备,无需root模式下进行。

每当 udevd 收到 uevent 事件时就会去匹配规则,匹配成功后执行规则对应的操作。用户自定义规则放在 /etc/udev/rules.d/ 目录下,以 .rules 为扩展名。这些规则文件的文件名通常是两个数字开头,它表示系统应用该规则的顺序

规则文件里的规则有一系列的键/值对组成,键/值对之间用逗号分割。每一个键或者是用户匹配键,或者是一个赋值键。匹配键确定规则是否被应用,而赋值键表示分配某值给该键。这些值将影响 udev 创建的设备文件。赋值键可以处理一个多值列表。

***【规则说明】***

  1. udev 规则的所有操作符
操作符 含义
== 比较键、值,若等于,则该条件满足
!= 比较键、值,若不等于,则该条件满足
= 对一个键赋值
+= 为一个表示多个条目的键赋值
:= 对一个键赋值,并拒绝之后所有对该键的改动。目的是防止后面的规则文件对该键赋值
  1. udev 规则的匹配键
匹配键 含义
ACTION 事件 (uevent) 的行为,例如:add( 添加设备 )、remove( 删除设备 )
KERNEL 内核设备名称,例如:sdacdrom
DEVPATH 设备的 devpath 路径
SUBSYSTEM 设备的子系统名称,例如:sda 的子系统为 block
BUS 设备在 devpath 里的总线名称,例如:usb
DRIVER 设备在 devpath 里的设备驱动名称,例如:ide-cdrom
ID 设备在 devpath 里的识别号
SYSFS{filename} 设备的 devpath 路径下,设备的属性文件“filename”里的内容。
例如:SYSFS{model}=="ST936701SS"表示:如果设备的型号为 ST936701SS,则该设备匹配该匹配键
ENV{key} 环境变量。在一条规则中,可以设定最多五条环境变量的匹配键
PROGRAM 调用外部命令
RESULT 外部命令 PROGRAM 的返回结果
  1. udev 的重要赋值键
赋值键 含义
NAME /dev下产生的设备文件名。只有第一次对某个设备的 NAME 的赋值行为生效,之后匹配的规则再对该设备的 NAME 赋值行为将被忽略。如果没有任何规则对设备的 NAME 赋值,udev 将使用内核设备名称来产生设备文件。
SYMLINK /dev/ 下的设备文件产生符号链接。由于 udev 只能为某个设备产生一个设备文件,所以为了不覆盖系统默认的 udev 规则所产生的文件,推荐使用符号链接
OWNER, GROUP, MODE 为设备设定权限
ENV{key} 导入一个环境变量
  1. 我们以向系统内接入一个 USB-HUB 连接着两个 USB-CAMERA 为例,我们可以使用 dmesg 命令打印自开机以来的相关信息:

    1
    2
    # 我们可以将 dmesg 打印的信息存入 ~/Desktop/dmesg.txt 文件中
    dmesg > ~/Desktop/dmesg.txt
  2. 我们在 ~/Desktop/dmesg.txt 中查找相关的硬件相关的加载信息:

    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
    28
    29
    30
    31
    32
    33
    34
    # USB-HUB 相关信息。经过实测,USB3.0 Hub 在硬件识别时会出现两套设备 usb 1-3 和 usb 2-3
    [ 2248.987744] usb 1-3: new high-speed USB device number 16 using xhci_hcd
    [ 2249.141283] usb 1-3: New USB device found, idVendor=05e3, idProduct=0610, bcdDevice= 6.56
    [ 2249.141304] usb 1-3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
    [ 2249.141312] usb 1-3: Product: USB2.1 Hub
    [ 2249.141319] usb 1-3: Manufacturer: GenesysLogic
    [ 2249.142815] hub 1-3:1.0: USB hub found
    [ 2249.143079] hub 1-3:1.0: 4 ports detected
    [ 2249.267971] usb 2-3: new SuperSpeed USB device number 4 using xhci_hcd
    [ 2249.291145] usb 2-3: New USB device found, idVendor=05e3, idProduct=0626, bcdDevice= 6.56
    [ 2249.291164] usb 2-3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
    [ 2249.291171] usb 2-3: Product: USB3.1 Hub
    [ 2249.291176] usb 2-3: Manufacturer: GenesysLogic
    [ 2249.294201] hub 2-3:1.0: USB hub found
    [ 2249.294522] hub 2-3:1.0: 4 ports detected

    # 摄像头 1 相关信息
    [ 2471.254480] usb 1-3.3: new high-speed USB device number 17 using xhci_hcd
    [ 2471.451382] usb 1-3.3: New USB device found, idVendor=05a3, idProduct=9230, bcdDevice= 1.00
    [ 2471.451406] usb 1-3.3: New USB device strings: Mfr=2, Product=1, SerialNumber=0
    [ 2471.451416] usb 1-3.3: Product: USB 2.0 Camera
    [ 2471.451424] usb 1-3.3: Manufacturer: LRCP V1080P
    [ 2471.458203] usb 1-3.3: Found UVC 1.00 device USB 2.0 Camera (05a3:9230)
    [ 2471.508051] input: USB 2.0 Camera: LRCP V1080P as /devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3.3/1-3.3:1.0/input/input22

    # 摄像头 2 相关信息
    [ 2475.426695] usb 1-3.4: new high-speed USB device number 18 using xhci_hcd
    [ 2475.590900] usb 1-3.4: New USB device found, idVendor=0c45, idProduct=ae11, bcdDevice= 1.00
    [ 2475.590920] usb 1-3.4: New USB device strings: Mfr=2, Product=1, SerialNumber=3
    [ 2475.590929] usb 1-3.4: Product: LRCP imx291
    [ 2475.590936] usb 1-3.4: Manufacturer: LRCP Technology Co., Ltd.
    [ 2475.590942] usb 1-3.4: SerialNumber: SN0001
    [ 2475.593679] usb 1-3.4: Found UVC 1.00 device LRCP imx291 (0c45:ae11)
    [ 2475.620419] input: LRCP imx291: LRCP imx291 as /devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3.4/1-3.4:1.0/input/input23
  3. /etc/udev/rules.d/ 文件夹下创建几个用户自定义规则,以 .rules 为扩展名。

    • 修改 USB-HUB 的相关信息

      1
      sudo vim /etc/udev/rules.d/10-usb-hub.rules    
      1
      2
      3
      4
      # cat /etc/udev/rules.d/10-usb-hub.rules 
      # 修改 USB-HUB 的相关信息
      SUBSYSTEM=="usb", ATTRS{idVendor}=="05e3", ATTRS{idProduct}=="0610", SYMLINK+="usb2_hub" # KERNEL=="1.x"
      SUBSYSTEM=="usb", ATTRS{idVendor}=="05e3", ATTRS{idProduct}=="0626", SYMLINK+="usb3_hub" # KERNEL=="2.x"
    • 修改两个相机的信息

      1
      sudo vim /etc/udev/rules.d/10-usb-cam.rules   
      1
      2
      3
      4
      # cat /etc/udev/rules.d/10-usb-cam.rules 
      # 修改 USB-CAMERA 的相关信息
      KERNEL=="video*", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="05a3", ATTRS{idProduct}=="9230", ATTR{index}=="0", MODE:="0777", SYMLINK+="rgb_cam_wide"
      KERNEL=="video*", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="ae11", ATTR{index}=="0", MODE:="0777", SYMLINK+="rgb_cam_1"
      • 如果系统内接入多个相同型号的设备(即 ATTRS{idVendor}ATTRS{idProduct} 都一样)时,可以用接入接口的不同来区分他们:
      1
      2
      KERNELS=="1-[0-9].3", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="ae11", ATTR{index}=="0", MODE:="0777", SYMLINK+="rgb_cam_1"
      KERNELS=="1-[0-9].4", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="ae11", ATTR{index}=="0", MODE:="0777", SYMLINK+="rgb_cam_2"
  4. 重新加载修改过后的规则:

    1
    2
    sudo udevadm control --reload-rules
    sudo udevadm trigger
  5. 重启后查看规则是否生效:

    1
    2
    3
    4
    ls -al /dev/usb*_hub
    ls -al /dev/rgb_cam*

    udevadm info -n /dev/usb2_hub

2. 网卡设备

参考文章:


udev 通常会根据系统物理特性分配接口名称。例如,enp2s1。如果你不确定你的接口名称,你可以在系统启动后运行如下命令查看:

1
ip link

为了使 ROS 启动脚本顺利的运行,我们需要将这些端口号固定下来。创建手动命名规则,比方说,通过MAC地址将接口命名成internet0dmz0lan0 这样。

为此,请在 /etc/systemd/network/ 中创建 .link 文件,为其中的一个、一些或者说你全部的接口赋予明确的名字或是更妥善的命名规则。示例:

1
sudo vim /etc/systemd/network/10-ether0.link
1
2
3
4
5
6
[Match]
# 可以使用网卡设备的MAC地址或pci编号用来匹配
MACAddress=<your-network-device-MAC-address>

[Link]
Name=ether0
1
2
3
4
5
# 编辑完之后重启网络服务
sudo systemctl restart systemd-networkd

# 重启系统
reboot