ROS 学习笔记
写在前面:
本文章是根据 ROS 官方文档以及其他相关资料翻译、整理而来。碍于本人学识有限,且非本专业人士,部分叙述难免存在纰漏,请读者注意甄别。
使用版本:
Linux 20.04 LTS
ROS Noetic
(ROS1)
参考资料:
- ROS 官方网站
- ROS Wiki
- ROS (NOETIC) 官方教程
- ROS2 (HUMBLE) 官方文档
- 张虎(2022):《机器人SLAM导航:核心技术与实战》,北京:机械工业出版社。
0. 安装
ROS Noetic / linux 20.04
以下提供一个基于 Ubuntu20.04.6 系统版本的 ROS Noetic 安装脚本:
1 | CURRENT_SOURCE=$(grep -E "http(s?)://(.*\.)?mirrors.tuna.tsinghua.edu.cn/ubuntu/" /etc/apt/sources.list /etc/apt/sources.list.d/* 2>/dev/null) |
ROS2 Humble / linux 22.04
-
首先确保我们所使用的系统环境支持
utf-8
编码。使用如下命令查看:1
locale # check for utf-8
-
添加
ROS2
apt
的依赖:-
首先确保 Ubuntu Universe repository 是可用的:
1
2sudo apt install software-properties-common
sudo add-apt-repository universe -
使用
apt
添加ROS2
的GPG公钥
:1
2sudo 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
-
-
安装 ROS 开发工具
-
安装公共基础软件包:
1
2
3
4
5
6sudo 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
10sudo 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
-
-
当我们要使用
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_DISTRO
和ROS_VERSION
是否设置成功:1
2
3ROS_VERSION=2
ROS_PYTHON_VERSION=3
ROS_DISTRO=humble -
克隆一个演示项目到
workspace
的src
中:1
2
3mkdir -p ~/ws_ros2_humble/src
cd ~/ws_ros2_humble
vcs import --input https://raw.githubusercontent.com/ros2/ros2/humble/ros2.repos src -
使用
rosdep
安装这个演示项目需要使用的依赖:(推荐在每次安装新依赖之前更新一遍系统的依赖环境)
1
sudo apt upgrade
1
2
3sudo rosdep init
rosdep update
rosdep install --from-paths src --ignore-src -y --skip-keys "fastcdr rti-connext-dds-6.0.1 urdfdom_headers" -
创建(Build)工作空间下的所有代码:
注意 ⚠️
如果安装过其他
ROS
版本,请先确保在.bashrc
(或其他类似配置)中不存在source /opt/ros/${ROS_DISTRO}/setup.bash
,以确保避免其他ROS
版本的干扰。同时,可以使用命令
printenv | grep-i ROS
,确保其输出为空。- Build the code
1
2cd ~/ros2_humble/
colcon build --symlink-install -
代码创建完成后,我们首先需要配置局部的运行环境:
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 -
然后尝试运行一些实例:
终端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观察程序运行的过程。
使用 RoboStack
安装 ROS
RoboStack 是一个 ROS 的软件包管理器,只作***为条件不具备情况下的备选方案***。推荐将 ***ROS 的完整版安装在物理宿主机***中:
【开发环境】
ROS Noetic
Ubuntu 20.04.6 LTS
Python Ver. 3.8.20
(Ubuntu 20.04.6 LTS 默认版本)
1. 简介
RoboStack 是一个将机器人操作系统(ROS)打包为适用于 Linux、Mac 和 Windows 的 Conda 软件包管理器的项目,由 Open Robotics 提供支持,提供 ROS1 版本的 Noetic
和 ROS2 版本的 Humble
(以上均为 LTS
版本),同时还为 Jupyter Notebook
提供各种与 ROS 相关的插件。
这里是 RoboStack 使用的几个重要理由:
- 任意平台上的 ROS: 它支持在 Linux(任何发行版,新旧版本)、MacOS 和 Windows 上安装ROS。这种灵活性使开发者能够在不同操作系统上工作,避免兼容性问题。
- 便捷的安装: 用户可以在 Conda 环境中将ROS与流行的机器学习和计算机视觉库(如PyTorch和TensorFlow)一起安装。这简化了需要与这些库集成的机器人应用程序的设置过程。
- 无需 root 访问权限: 与传统的ROS安装方式不同,RoboStack 不需要 root 访问权限。这一特性使得在共享工作站和高性能计算机上安装ROS变得更加容易,无需管理员权限。
- 可重现性: RoboStack 支持可重现的环境,确保机器人研究和开发中的一致性。这对于复制实验和在研究社区中共享代码至关重要。
- 基于浏览器的 RViz 可视化: RoboStack 可以在浏览器中提供类似RViz的可视化效果,即使没有完整的ROS安装也可以实现。这增强了远程可视化和调试机器人应用程序的能力。
- 与 Jupyter Notebooks 集成: 它与Jupyter笔记本和JupyterLab无缝集成,允许开发者在笔记本环境中控制机器人并进行ROS功能的实验。
- 简化的软件包创建: 创建ROS组件的Conda软件包比传统的Debian软件包更简单,使开发者能够更轻松地分发和管理他们的ROS模块。
- 跨平台的持续集成: RoboStack支持ROS软件包的跨平台持续集成(CI),确保代码变更能够在不同操作系统上无缝测试。
2. 如何安装
RoboStack 是一个使用 Conda 软件包管理器为 Linux、Mac 和 Windows 打包的 ROS,基于 conda-forge。
2.1 安装 conda
要开始使用 Conda(或 mamba)作为软件包管理器,您需要先安装一个基本的 Conda 环境。建议不要使用 Anaconda(可能存在商用授权等风险),可以使用由开源软件组织 conda-forge 开发维护的开源软件 Miniforge
。具体安装步骤请参考 GitHub 中的官方指南。
以 Linux 系统(类Unix)为例:可以使用 curl
或 wget
或其他程序下载安装程序,然后运行脚本
1 | curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" |
或
1 | wget "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" |
脚本执行结束后可以使用如下命令检查是否成功安装:
1 | conda -V |
2.2 安装 mamba
(可选)
1 | conda install conda-forge::mamba |
2.3 安装 ROS
框架
注意:
- 确保不要在
base
环境中安装 ROS 软件包。因为这会导致后续问题。另一方面,conda 和 mamba 不能安装在 ros_env,它们只能安装在基本环境中。- 不要
source
本地系统中的 ROS 框架。当系统中安装了 ros 时,在非 conda 环境中会出现环境干扰。因为安装脚本中设置的PYTHONPATH
会与 conda 环境冲突。- Windows 系统只支持
CMD
终端,不支持 Powershell。
在 conda 中创建 ros 环境:
1 | conda create -n ros1_env python=3.11 |
安装 ros 框架:
1 | conda activate ros1_env |
安装 ros 相关本地开发工具包
1 | conda install compilers cmake pkg-config make ninja colcon-common-extensions catkin_tools rosdep |
如果安装
ros-noetic-rosbridge-suite
包时出现依赖问题,可以尝试解决该依赖冲突,或者使用源码编译的方式解决该问题。方法如下:
1 | # 首先安装 rosbridge-suite 包运行时所需的依赖 |
3. 编译项目
后续编译、运行方式与正常安装操作方式无异,具体请参考相关文档。
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 平台作为主机。
官员 ROS 的安装,详见第〇章。
1.2.2 ROS 文件的组织方式
ROS 的文件被放在系统空间和工作空间两个地方。
系统空间
系统空间是存放 ROS 系统安装包的目录,在 /opt/ros/
目录中存放着 roscore
、rviz
、rqt
等 ROS 核心工具。要使用他们,需要先使用如下命令激活系统空间:
1 | echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc |
工作空间
工作空间是用户开发自己程序的文件夹,里面用来存放用户开发的功能包程序,文件是源代码形式。ROS 的开源社区有非常多的功能包,用户可以直接使用 apt
命令安装二进制功能包到系统空间,也可以将源代码下载到工作空间中,然后手动编译。工作空间的功能包可以直接覆盖系统空间下的同名功能包。
1 | # 新建工作空间 |
1.2.3 ROS 网络通行配置
在构成 ROS 网络通信的各台主机中,必须指明一台主机作为主节点负责管理整个 ROS 网络通信,同时要声明参与通信的各个主机(配置每台主机中的环境变量 MASTER
和 HOST
)。
打开 ~/.bashrc
文件,添加如下环境变量
1 | export ROS_MASTER_URI=http://localhost:[PORT] |
1.3 ROS 系统架构
按照官方的说法,可以分别从计算图结构、文件系统和开源社区视角来理解 ROS 架构。
1.3.1 从计算图视角理解 ROS 结构
ROS 中可执行程序的基本单位叫节点(node),节点之间通过消息机制进行通讯,这样就组成了一张网状图,也叫计算图。如下图
附录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
12workspace_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
2catkin_create_pkg <PACKAGE_NAME> [DEPENDS...]
# 这里的 [DEPENDS...] 可以有多个参数,这些 depends 都是该 package 的直接(first-order)依赖 -
如何
build
整个工作空间并source
这个工作空间的setup.bash
1
2cd [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. 关于 package
的 build
(构建)
-
如何使用
catkin_make
命令行工具:catkin_make
命令行工具将标准 CMake 工作流程中对cmake
和make
的调用结合在了一起。1
catkin_make [MAKE_TARGETS] [-DCMAKE_VARIABLES=...]
上述命令将构建在
src/
文件夹中找到的任何 catkin 项目。如果您的源代码放在不同的地方,比如my_src/
,那么您可以这样调用catkin_make
:1
catkin_make --source my_src
-
如何
build
整个工作空间并source
这个工作空间的setup.bash
1
2cd [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 list1
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 | rostopic bw # 显示 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. rosservice
和 rosparam
的相关操作
-
rosservice
:1
2
3
4
5rosservice list # 打印有关活动服务的信息
rosservice call # 使用提供的参数调用服务
rosservice tpye # 打印服务类型
rosservice find # 按服务类型查找服务
rosservice uri # 打印服务 ROSRPC uri -
rosparam
:1
2
3
4
5
6rosparam 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_console
和rqt_logger_level
工具:rqt_console
工具用于查看和管理 ROS 系统中的日志消息。它主要用于调试和监视 ROS 节点的日志消息,帮助开发人员在开发和运行 ROS 应用程序时快速发现和解决问题。rqt_logger_level
工具用于查看和管理ROS节点的日志级别。1
2rosrun rqt_console rqt_console
rosrun rqt_logger_level rqt_logger_level
8. 使用 .launch
文件启动节点
1 | roslaunch [PACKAGE] [FILENAME.launch] |
附录2. 远程控制ROS机器人的两种方式
注意 ⚠️:
远程控制 ROS 机器人的方式可能并非两种,这里只是笔者接触到的两种最常用的控制形式。在此记录,防止日后忘记。同时也将记录使用这两种方法遇到的问题,以及当时的解决方法。
参考文章:
1. ROS 主从节点控制(ROS Noetic)
在 ROS 第一个版本中(这里指 ROS Noetic),可以设置一台主机(MASTER
)和多台从机(SLAVE
)。
注意 ⚠️:
这种“主从机制”只针对与 ROS 第一个版本,ROS2 中似乎不再有“主从”的概念。详细请参考ROS2。
我们可以在主机中启动 MASTER
节点,从机启动其他节点,这样就可以在这些配置好的主从机之间相互通信,其中的网络通信过程对于我们而言是透明的,就像是在同一台主机上。
以下是配置过程:
-
使用该方法的前提是两台均已安装好 ROS 环境的机器(最好是同版本,不同版本未测试)。
-
设置两台主机的 IP 地址:
在同一网段下设置两台机器的静态 IP,以笔者测试环境举例:
- 网关地址:
192.168.2.1
- 子网掩码:
255.255.255.0
- 主机 IP(Jetson):
192.168.2.200
- 从机 IP(Ubuntu):
192.168.2.201
- 网关地址:
-
修改
.bashrc
或.zshrc
等系统环境配置:-
主机(Jetson)中添加:
1
2
3export ROS_HOSTNAME=$YOUR_HOSTNAME # 设置本机的Host,一般为IP地址
export ROS_IP=$YOUR_IP # 设置本机的IP地址
export ROS_MASTER_URI=http://$YOUR_IP:11311 # 将本机设置为主机 -
从机(Ubuntu)中添加:
1
2
3export 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,受同时主机控制的从机该属性均相同。 -
-
修改
/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
-
-
测试:
现在我们就可以测试主从机之间能否顺利通信。
-
主机上运行:
1
2roscore # 开启 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 | { "op": "subscribe", |
该规范与编程语言和传输层无关。其目的在于,任何可以发送 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 | sudo apt install ros-$ROS_DISTRO-rosbridge-suite |
- 使用源码编译:
rosbridge_suite
源代码
2.3 ROSBridge
的使用
要启动 ROSBridge
及其软件包(如 rosbridge_server
和 rosapi
),安装中包含 .launch
文件。要启动该文件,输入如下命令运行:
1 | roslaunch rosbridge_server rosbridge_websocket.launch address:=$YOUR_IP port:=$PORT # default port 9090 |
-
我们在自己的工作空间下,从创建
package
开始:1
2cd $YOUR_WORKSPACE/src
catkin_create_pkg test_rosbridge --catkin-deps rosbridge_server -
在
package
中创建一个.launch
文件:1
2mkdir $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> -
然后我们测试
rosbridge_server
:1
2
3
4cd $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
文件
-
我们从最简单的本机控制开始,创建一个
html
文件:1
2mkdir $YOUR_WORKSPACE/web
vim $YOUR_WORKSPACE/web/test_websocket.html1
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
<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
3var 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
4ros.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 订阅一个主题
-
下一步,我们将订阅一个主题,接受字符串信息并将其显示在浏览器窗口中。我们将把代码放在 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_listener
的ROSLIB.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
2Connection status: Connected
Last /test_topic received: Hello from Master Node!
-
2.4.3 发布一个话题
-
与订阅主题的做法类似,我们依然使用
test_topic
的ROSLIB.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
创建的设备文件。赋值键可以处理一个多值列表。
***【规则说明】***
udev
规则的所有操作符
操作符 含义 ==
比较键、值,若等于,则该条件满足 !=
比较键、值,若不等于,则该条件满足 =
对一个键赋值 +=
为一个表示多个条目的键赋值 :=
对一个键赋值,并拒绝之后所有对该键的改动。目的是防止后面的规则文件对该键赋值
- udev 规则的匹配键
匹配键 含义 ACTION
事件 ( uevent
) 的行为,例如:add( 添加设备 )、remove( 删除设备 )KERNEL
内核设备名称,例如: sda
,cdrom
。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
的返回结果
udev
的重要赋值键
赋值键 含义 NAME
在 /dev
下产生的设备文件名。只有第一次对某个设备的NAME
的赋值行为生效,之后匹配的规则再对该设备的NAME
赋值行为将被忽略。如果没有任何规则对设备的NAME
赋值,udev
将使用内核设备名称来产生设备文件。SYMLINK
为 /dev/
下的设备文件产生符号链接。由于udev
只能为某个设备产生一个设备文件,所以为了不覆盖系统默认的udev
规则所产生的文件,推荐使用符号链接。OWNER, GROUP, MODE
为设备设定权限 ENV{key}
导入一个环境变量
-
我们以向系统内接入一个 USB-HUB 连接着两个 USB-CAMERA 为例,我们可以使用
dmesg
命令打印自开机以来的相关信息:1
2# 我们可以将 dmesg 打印的信息存入 ~/Desktop/dmesg.txt 文件中
dmesg > ~/Desktop/dmesg.txt -
我们在
~/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 -
在
/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
2KERNELS=="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" - 如果系统内接入多个相同型号的设备(即
-
-
重新加载修改过后的规则:
1
2sudo udevadm control --reload-rules
sudo udevadm trigger -
重启后查看规则是否生效:
1
2
3
4ls -al /dev/usb*_hub
ls -al /dev/rgb_cam*
udevadm info -n /dev/usb2_hub
2. 网卡设备
参考文章:
udev
通常会根据系统物理特性分配接口名称。例如,enp2s1
。如果你不确定你的接口名称,你可以在系统启动后运行如下命令查看:
1 | ip link |
为了使 ROS 启动脚本顺利的运行,我们需要将这些端口号固定下来。创建手动命名规则,比方说,通过MAC地址将接口命名成internet0
,dmz0
或 lan0
这样。
为此,请在 /etc/systemd/network/
中创建 .link
文件,为其中的一个、一些或者说你全部的接口赋予明确的名字或是更妥善的命名规则。示例:
1 | sudo vim /etc/systemd/network/10-ether0.link |
1 | [Match] |
1 | # 编辑完之后重启网络服务 |