综合案例:ROS 2 计数器 Web 界面
- [注:下面步骤默认已安装ROS2 Jazzy + Anaconda,所有步骤在conda环境下进行](#注:下面步骤默认已安装ROS2 Jazzy + Anaconda,所有步骤在conda环境下进行)
- [一、后端 ROS 2 环境设置](#一、后端 ROS 2 环境设置)
-
- [1. 安装 ROS 2 核心组件和 rosbridge](#1. 安装 ROS 2 核心组件和 rosbridge)
- [2、安装 Python 依赖(Tornado 和 PyMongo)](#2、安装 Python 依赖(Tornado 和 PyMongo))
- [二、创建 ROS 2 Python 包](#二、创建 ROS 2 Python 包)
-
- 1、创建工作空间和包
- [2、编写 Python 发布者节点 (simple_publisher.py)](#2、编写 Python 发布者节点 (simple_publisher.py))
- [3、创建 Launch 文件并修复参数类型(提前避免可能出现的这个问题:Trying to set parameter 'delay_between_messages' to '0' of type 'INTEGER', expecting type 'DOUBLE')](#3、创建 Launch 文件并修复参数类型(提前避免可能出现的这个问题:Trying to set parameter ‘delay_between_messages’ to ‘0’ of type ‘INTEGER’, expecting type ‘DOUBLE’))
- [4、配置 setup.py 并添加 Launch 文件](#4、配置 setup.py 并添加 Launch 文件)
- [5、构建并 Source 工作空间](#5、构建并 Source 工作空间)
- 三、运行系统
-
- [1、启动 ROS 2 发布者节点 (终端 A)](#1、启动 ROS 2 发布者节点 (终端 A))
- [2、启动 rosbridge_server (终端 B)](#2、启动 rosbridge_server (终端 B))
-
- [ModuleNotFoundError: No module named 'yaml'](#ModuleNotFoundError: No module named 'yaml')
- [ModuleNotFoundError: No module named 'tornado'](#ModuleNotFoundError: No module named 'tornado')
- [no module named 'bson'](#no module named ‘bson’)
- [四、前端 Web 界面](#四、前端 Web 界面)
-
- [1、创建 web_interface.html 文件](#1、创建 web_interface.html 文件)
- [2、 打开 Web 界面](#2、 打开 Web 界面)
注:下面步骤默认已安装ROS2 Jazzy + Anaconda,所有步骤在conda环境下进行
python
# 创建虚拟环境
conda create -n ros2_ws python=3.12
# 激活conda环境
conda activate
一、后端 ROS 2 环境设置
1. 安装 ROS 2 核心组件和 rosbridge
bash
# 更新系统
sudo apt update && sudo apt upgrade -y
# 安装 ROS 2 基础环境和 rosbridge
sudo apt install ros-jazzy-ros-base ros-jazzy-rosbridge-server ros-jazzy-rosbridge-suite python3-pip -y
# 确保 rosdep 可用
sudo apt install python3-rosdep -y
sudo rosdep init
rosdep update
2、安装 Python 依赖(Tornado 和 PyMongo)
bash
pip3 install tornado pymongo
二、创建 ROS 2 Python 包
1、创建工作空间和包
bash
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create web_demo --build-type ament_python --dependencies rclpy std_msgs
cd ~/ros2_ws
colcon build
source install/setup.bash
2、编写 Python 发布者节点 (simple_publisher.py)
bash
编辑文件:~/ros2_ws/src/web_demo/web_demo/simple_publisher.py
bash
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class SimplePublisher(Node):
def __init__(self):
super().__init__('simple_publisher')
self.publisher_ = self.create_publisher(String, 'web_counter', 10)
self.timer_period = 0.5 # 发布频率 (0.5秒一次)
self.timer = self.create_timer(self.timer_period, self.timer_callback)
self.i = 0
def timer_callback(self):
msg = String()
msg.data = f'Hello ROS 2 Jazzy Web! Count: {self.i}'
self.publisher_.publish(msg)
self.get_logger().info(f'Publishing: "{msg.data}"')
self.i += 1
def main(args=None):
rclpy.init(args=args)
node = SimplePublisher()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
3、创建 Launch 文件并修复参数类型(提前避免可能出现的这个问题:Trying to set parameter 'delay_between_messages' to '0' of type 'INTEGER', expecting type 'DOUBLE')
创建一个 ~/ros2_ws/src/web_demo/launch 目录,并将rosbridge的配置复制过来。
python
mkdir -p ~/ros2_ws/src/web_demo/launch
# 复制默认文件并重命名
cp /opt/ros/jazzy/share/rosbridge_server/launch/rosbridge_websocket_launch.xml ~/ros2_ws/src/web_demo/launch/web_bridge.launch.xml
编辑 ~/ros2_ws/src/web_demo/launch/web_bridge.launch.xml 文件。找到 delay_between_messages 参数,确保 value 是 0.0 (浮点数):
xml
<!-- ... 文件顶部内容 ... -->
<launch>
<node pkg="rosbridge_server" exec="rosbridge_websocket" name="rosbridge_websocket">
<!-- 确保这个值是 "0.0" 而不是 "0" -->
<param name="delay_between_messages" value="0.0"/>
<param name="port" value="9090"/>
<!-- ... 其他参数 ... -->
</node>
</launch>
4、配置 setup.py 并添加 Launch 文件
编辑 ~/ros2_ws/src/web_demo/setup.py 文件,确保 data_files 和 entry_points 配置正确:
bash
from setuptools import find_packages, setup
import os
from glob import glob
package_name = 'web_demo'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages', ['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
# 精确添加 launch 文件,避免 glob(..*.*) 的问题
(os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.xml'))),
(os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.launch.py'))),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='your_name',
maintainer_email='your_email@example.com',
description='Web Demo Package',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'simple_publisher = web_demo.simple_publisher:main',
],
},
)
5、构建并 Source 工作空间
xml
cd ~/ros2_ws
colcon build
# 每次打开新终端都需要执行 source
source install/setup.bash
三、运行系统
1、启动 ROS 2 发布者节点 (终端 A)
bash
source ~/ros2_ws/install/setup.bash
ros2 run web_demo simple_publisher
2、启动 rosbridge_server (终端 B)
bash
source ~/ros2_ws/install/setup.bash
ros2 launch web_demo web_bridge.launch.xml
ModuleNotFoundError: No module named 'yaml'
python
[rosbridge_websocket-1] import rclpy
[rosbridge_websocket-1] File "/opt/ros/jazzy/lib/python3.12/site-packages/rclpy/__init__.py", line 48, in <module>
[rosbridge_websocket-1] from rclpy.parameter import Parameter
[rosbridge_websocket-1] File "/opt/ros/jazzy/lib/python3.12/site-packages/rclpy/parameter.py", line 27, in <module>
[rosbridge_websocket-1] import yaml
[rosbridge_websocket-1] ModuleNotFoundError: No module named 'yaml'
解决------>安装PyYAML:
python
pip3 install PyYAML
ModuleNotFoundError: No module named 'tornado'
python
[rosbridge_websocket-1] File "/opt/ros/jazzy/lib/rosbridge_server/rosbridge_websocket", line 45, in <module>
[rosbridge_websocket-1] from tornado.httpserver import HTTPServer
[rosbridge_websocket-1] ModuleNotFoundError: No module named 'tornado'
解决------> 安装tornado:
python
pip install tornado
no module named 'bson'
python
# 1. 卸载可能已安装的任何冲突的 bson 或 pymongo 包
pip3 uninstall bson pymongo -y
# 2. 正确安装 pymongo。它会自动包含正确的 bson 模块
pip3 install pymongo
四、前端 Web 界面
1、创建 web_interface.html 文件
html
<!DOCTYPE html>
<html>
<head>
<title>ROS 2 Jazzy Web Interface</title>
<!-- 引入 roslibjs 库 -->
<script src="https://cdn.jsdelivr.net/npm/roslib/build/roslib.min.js"></script>
<style>
body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }
#status { font-size: 20px; font-weight: bold; }
#counter_display { font-size: 60px; color: #007BFF; margin-top: 20px; }
.connected { color: green; }
.disconnected { color: red; }
</style>
</head>
<body>
<h1>ROS 2 Web Counter Demo</h1>
<p>WebSocket Status: <span id="status" class="disconnected">Disconnected</span></p>
<div id="counter_display">Waiting for ROS 2 data...</div>
<script>
// --- 1. 连接到 rosbridge WebSocket 服务器 ---
var ros = new ROSLIB.Ros({
url : 'ws://localhost:9090'
});
ros.on('connection', function() {
document.getElementById('status').innerText = 'Connected';
document.getElementById('status').className = 'connected';
});
ros.on('error', function(error) {
document.getElementById('status').innerText = 'Error';
document.getElementById('status').className = 'disconnected';
console.error(error);
});
ros.on('close', function() {
document.getElementById('status').innerText = 'Disconnected';
document.getElementById('status').className = 'disconnected';
});
// --- 2. 订阅 ROS 话题 ---
var counterListener = new ROSLIB.Topic({
ros : ros,
name : '/web_counter', // 对应 Python 节点发布的话题名
messageType : 'std_msgs/msg/String'
});
counterListener.subscribe(function(message) {
// 收到消息后更新 HTML 元素
document.getElementById('counter_display').innerText = message.data;
});
</script>
</body>
</html>
2、 打开 Web 界面
在您的浏览器中双击打开 web_interface.html 文件。
您将看到页面连接状态变为"Connected",并且实时显示来自 ROS 2 后端的计数器消息。