16 KiB
AimBot 上游服务器对接文档
整体架构
┌──────────────────────────────────────────────────────────┐
│ 浏览器 (Vue3 前端) │
│ - 自动模式:观看 MJPEG 视频流 │
│ - 手动模式:键盘遥控(WASD/方向键/J) + MJPEG 视频流 │
│ - 调试模式:PID 调参 + 激光开关 │
└──────┬──────────────┬──────────────┬─────────────────────┘
│ HTTP │ WebSocket │ HTTP (MJPEG)
▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌───────────────────┐
│ Nginx │ │rosbridge │ │ web_video_server │
│ 静态文件 │ │ :9090 │ │ :8081 │
│ 反向代理 │ └────┬─────┘ └────────┬──────────┘
└──────┬───────┘ │ │
│ ▼ ▼
│ ┌────────────────────────────────┐
└─────►│ ROS2 节点 │
│ - /cmd_wheel (轮速控制) │
│ - /cmd_gimbal (云台控制) │
│ - /pid_config (PID参数) │
│ - /api/mode/switch (模式切换) │
│ - /api/laser (激光开关) │
└────────────────────────────────┘
一、数据结构详解
前端与 ROS2 之间通过 HTTP API 和 rosbridge WebSocket 两种通道通信。以下为所有接口和消息的完整数据格式。
1.1 HTTP API — 模式切换
前端发起模式切换请求,ROS2 后端需实现对应 HTTP 接口。
POST /api/mode/switch
Content-Type: application/json
请求体:
{
"mode": "auto"
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
mode |
string | 是 | 目标模式,可选值: "auto" | "manual" | "debug" |
成功响应 (200):
{
"code": 0,
"message": "ok"
}
错误响应 (4xx/5xx):
{
"code": -1,
"message": "unknown mode: xxx"
}
1.2 HTTP API — 激光开关
POST /api/laser
Content-Type: application/json
请求体:
{
"action": "on"
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
action |
string | 是 | "on" 开激光, "off" 关激光 |
成功响应 (200):
{
"code": 0,
"message": "ok"
}
1.3 rosbridge WebSocket — 连接
URL: ws://<host>:9090
协议: rosbridge v2.0 (JSON)
连接后: 前端通过 roslibjs 自动完成协议握手
连接状态通过前端 UI 实时展示:
已连接(绿色) — WebSocket 已建立,rosbridge 协议就绪未连接(红色) — 断开或错误
1.4 ROS 话题 — /cmd_wheel (底盘轮速)
| 属性 | 值 |
|---|---|
| 消息类型 | std_msgs/Float32MultiArray |
| 方向 | 前端 → ROS2 |
| 发布时机 | 手动模式下按 WASD, 值变化时发送; 松键后自动停发 |
| 发布频率 | 按需 (值变即发), 约 30~60 Hz |
数据格式:
{
"layout": {
"dim": [],
"data_offset": 0
},
"data": [1.0, 0.5]
}
| 字段 | 类型 | 说明 |
|---|---|---|
data[0] |
float64 | 左轮线速度 (m/s), 正值前转, 负值后转 |
data[1] |
float64 | 右轮线速度 (m/s), 正值前转, 负值后转 |
速度计算 (差速驱动运动学):
给定:
linear = 线速度 (m/s), WASD 合成
angular = 角速度 (rad/s), WASD 合成
sep = 轮距 (m)
左轮速度 = linear - angular × (sep / 2)
右轮速度 = linear + angular × (sep / 2)
典型值示例:
| 操作 | data[0] (左) | data[1] (右) | 效果 |
|---|---|---|---|
| W (前进) | 0.5 | 0.5 | 直行前进 |
| S (后退) | -0.5 | -0.5 | 直行后退 |
| A (左转) | -0.1 | 0.1 | 原地左转 |
| D (右转) | 0.1 | -0.1 | 原地右转 |
| W+A (前左) | 0.4 | 0.6 | 前进 + 左转 |
| W+D (前右) | 0.6 | 0.4 | 前进 + 右转 |
1.5 ROS 话题 — /cmd_gimbal (云台角速度)
| 属性 | 值 |
|---|---|
| 消息类型 | geometry_msgs/Twist |
| 方向 | 前端 → ROS2 |
| 发布时机 | 手动模式下按方向键, 值变化时发送; 松键后自动停发 |
| 发布频率 | 按需 (值变即发), 约 30~60 Hz |
数据格式:
{
"linear": { "x": 0, "y": 0, "z": 0 },
"angular": { "x": 0, "y": 30.0, "z": -15.0 }
}
| 字段 | 类型 | 说明 |
|---|---|---|
linear |
Vector3 | 不使用, 始终为 0 |
angular.x |
float64 | 不使用, 始终为 0 |
angular.y |
float64 | 云台 pitch 角度速率 (度/秒). 正值抬头, 负值低头 |
angular.z |
float64 | 云台 yaw 角度速率 (度/秒). 正值左转, 负值右转 |
注意: 虽然底层消息类型是
geometry_msgs/Twist(语义上是 rad/s),但前端注入的数值单位是 度/秒。ROS2 侧接收后如需 rad/s,需自行转换:rad/s = deg/s × π / 180。
典型值示例:
| 操作 | angular.y (pitch) | angular.z (yaw) | 效果 |
|---|---|---|---|
| ↑ | 30.0 | 0 | 云台抬头 |
| ↓ | -30.0 | 0 | 云台低头 |
| ← | 0 | 30.0 | 云台左转 |
| → | 0 | -30.0 | 云台右转 |
| ↑+← | 30.0 | 30.0 | 抬头+左转 |
1.6 ROS 话题 — /pid_config (PID 参数)
| 属性 | 值 |
|---|---|
| 消息类型 | std_msgs/String |
| 方向 | 前端 → ROS2 |
| 发布时机 | 调试页面点击「发布参数」按钮 |
数据格式 (JSON 字符串, 放在 data 字段中):
{
"data": "{\"visual_yaw\":{\"kp\":1.0,\"ki\":0.1,\"kd\":0.01},\"visual_pitch\":{\"kp\":1.0,\"ki\":0.1,\"kd\":0.01},\"angle_yaw\":{\"kp\":2.0,\"ki\":0.2,\"kd\":0.02},\"angle_pitch\":{\"kp\":2.0,\"ki\":0.2,\"kd\":0.02}}"
}
解析后结构:
{
"visual_yaw": { "kp": 1.0, "ki": 0.1, "kd": 0.01 },
"visual_pitch": { "kp": 1.0, "ki": 0.1, "kd": 0.01 },
"angle_yaw": { "kp": 2.0, "ki": 0.2, "kd": 0.02 },
"angle_pitch": { "kp": 2.0, "ki": 0.2, "kd": 0.02 }
}
| 字段 | 类型 | 说明 |
|---|---|---|
visual_yaw.kp |
float64 | 视觉环 Yaw 电机 — 比例系数 |
visual_yaw.ki |
float64 | 视觉环 Yaw 电机 — 积分系数 |
visual_yaw.kd |
float64 | 视觉环 Yaw 电机 — 微分系数 |
visual_pitch.kp |
float64 | 视觉环 Pitch 电机 — 比例系数 |
visual_pitch.ki |
float64 | 视觉环 Pitch 电机 — 积分系数 |
visual_pitch.kd |
float64 | 视觉环 Pitch 电机 — 微分系数 |
angle_yaw.kp |
float64 | 角度环 Yaw 电机 — 比例系数 |
angle_yaw.ki |
float64 | 角度环 Yaw 电机 — 积分系数 |
angle_yaw.kd |
float64 | 角度环 Yaw 电机 — 微分系数 |
angle_pitch.kp |
float64 | 角度环 Pitch 电机 — 比例系数 |
angle_pitch.ki |
float64 | 角度环 Pitch 电机 — 积分系数 |
angle_pitch.kd |
float64 | 角度环 Pitch 电机 — 微分系数 |
ROS2 侧接收解析示例 (Python):
import json
msg = json.loads(ros_msg.data) # ros_msg 是 std_msgs/String
visual_yaw_kp = msg["visual_yaw"]["kp"] # → 1.0
1.7 MJPEG 视频流
| 属性 | 值 |
|---|---|
| 协议 | HTTP multipart/x-mixed-replace |
| 帧格式 | JPEG 图像 |
| 提供方 | web_video_server |
| URL 模板 | http://<host>:8081/stream?topic=<话题名> |
URL 参数:
| 参数 | 必填 | 说明 |
|---|---|---|
topic |
是 | ROS 图像话题名, 如 /camera/image_raw |
mode |
否 | 前端附加参数, 标识当前页面,服务端可忽略 |
请求头:
GET /stream?topic=/camera/image_raw HTTP/1.1
Accept: image/jpeg
响应流 (multipart/x-mixed-replace):
HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=--boundary
--boundary
Content-Type: image/jpeg
Content-Length: 54321
<JPEG binary data>
--boundary
Content-Type: image/jpeg
Content-Length: 54102
<JPEG binary data>
...
前端消费方式:
<!-- 浏览器原生支持 MJPEG, 直接 <img src="..."> 即可 -->
<img src="http://192.168.0.11/stream?topic=/camera/image_raw&mode=auto" />
注意事项:
| 事项 | 说明 |
|---|---|
| 单连接限制 | web_video_server 默认每客户端仅允许一个连接。前端通过 onBeforeRouteLeave 在页面切换前主动断开旧连接 |
| Nginx 缓冲 | 反代 MJPEG 时必须 proxy_buffering off, 否则画面延迟严重 |
| 跨域 | 同域部署时使用相对路径可避免 CORS; 跨域场景需 Nginx 添加 CORS 头或使用 Access-Control-Allow-Origin |
1.8 键盘映射速查表
底盘控制 (WASD), 手动模式:
| 按键 | e.code | 动作 |
|---|---|---|
| W | KeyW |
线速度 +0.5 m/s |
| S | KeyS |
线速度 -0.5 m/s |
| A | KeyA |
角速度 +1.0 rad/s |
| D | KeyD |
角速度 -1.0 rad/s |
云台控制 (方向键), 手动模式:
| 按键 | e.code | 动作 |
|---|---|---|
| ↑ | ArrowUp |
pitch +30 度/秒 |
| ↓ | ArrowDown |
pitch -30 度/秒 |
| ← | ArrowLeft |
yaw +30 度/秒 |
| → | ArrowRight |
yaw -30 度/秒 |
激光开关:
| 按键 | e.code | 动作 |
|---|---|---|
| J | KeyJ |
激光 开/关 切换 |
多键策略:
- 同轴冲突 (W+S, A+D, ↑+↓, ←+→): 相互抵消, 净值为 0
- 异轴组合 (W+A 等): 同时生效, 合成运动不冲突
- 重复按键 (
e.repeat): 仅 J 键过滤 repeat, WASD/方向键允许 repeat (持续按住)
1.9 可配置参数一览
// src/config.js — 部署时按实际情况修改
HTTP_API_BASE = 'http://192.168.0.11' // HTTP API 基础地址
ROSBRIDGE_URL = 'ws://192.168.0.11:9090' // rosbridge WebSocket
MJPEG_AUTO_URL = '.../stream?topic=/camera/image_raw&mode=auto' // 自动模式视频流
MJPEG_MANUAL_URL = '.../stream?topic=/camera/image_raw&mode=manual' // 手动模式视频流
CMD_WHEEL_TOPIC = '/cmd_wheel' // 底盘轮速话题
CMD_GIMBAL_TOPIC = '/cmd_gimbal' // 云台角速度话题
PID_CONFIG_TOPIC = '/pid_config' // PID 参数话题
MAX_LINEAR = 0.5 // 最大线速度 (m/s)
MAX_ANGULAR = 1.0 // 最大角速度 (rad/s)
WHEEL_SEPARATION = 0.2 // 轮距 (m)
GIMBAL_PITCH_DEG_S = 30 // 云台 pitch 速率 (度/秒)
GIMBAL_YAW_DEG_S = 30 // 云台 yaw 速率 (度/秒)
二、Nginx 配置
Nginx 负责三件事:托管 Vue 前端静态文件、反代 MJPEG 视频流、反代 HTTP API。
2.1 前端部署
构建产物在 dist/ 目录,部署到服务器后由 Nginx 直接托管:
# 本地构建
cd aimbot-web
pnpm build
# 上传到服务器
scp -r dist/* user@server:/var/www/aimbot/
2.2 Nginx 配置示例
server {
listen 80;
server_name your-domain.com;
# 前端静态文件
root /var/www/aimbot;
index index.html;
# SPA 路由回退 — 所有非文件请求回 index.html
location / {
try_files $uri $uri/ /index.html;
}
# MJPEG 视频流反代 (web_video_server)
location /stream {
proxy_pass http://127.0.0.1:8081/stream;
proxy_http_version 1.1;
proxy_buffering off;
# MJPEG multipart 边界必须保持完整
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 长连接支持
proxy_set_header Connection "";
chunked_transfer_encoding off;
# 超时配置 — 视频流需要长连接
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
# 模式切换 API (HTTP → ROS2 节点)
location /api/ {
proxy_pass http://127.0.0.1:8080/api/;
proxy_set_header Host $host;
proxy_set_header Content-Type application/json;
}
}
2.3 关键说明
| 配置项 | 说明 |
|---|---|
proxy_buffering off |
MJPEG 流必须关闭缓冲,否则画面延迟严重 |
chunked_transfer_encoding off |
multipart 边界依赖原始编码 |
proxy_read_timeout 86400s |
视频流是长连接,超时必须足够大 |
try_files $uri /index.html |
Vue Hash 路由支持,直接访问 /#/manual 等路径 |
三、ROS2 环境配置
3.1 rosbridge (WebSocket 服务)
前端通过 roslibjs 直连 rosbridge WebSocket,不走 Nginx 反代。
# 安装
sudo apt install ros-${ROS_DISTRO}-rosbridge-server
# 启动 (默认 ws://0.0.0.0:9090)
ros2 run rosbridge_server rosbridge_websocket
3.2 web_video_server (MJPEG 推流)
# 安装
sudo apt install ros-${ROS_DISTRO}-web-video-server
# 启动 (默认 http://0.0.0.0:8081)
# 发布摄像头话题后自动生成 /stream?topic=<topic_name>
ros2 run web_video_server web_video_server
3.3 话题一览
rosbridge 发布/订阅的话题需要与前端 config.js 一致:
| 话题 | 消息类型 | 方向 | 用途 |
|---|---|---|---|
/cmd_wheel |
std_msgs/Float32MultiArray |
前端→ROS2 | 左右轮速 [left, right] |
/cmd_gimbal |
geometry_msgs/Twist |
前端→ROS2 | 云台角度速度(单位度/秒) angular.y(pitch) angular.z(yaw) |
/pid_config |
std_msgs/String |
前端→ROS2 | PID 参数 JSON |
3.4 模式切换与激光 API
前端 HTTP POST → Nginx 反代 → ROS2 节点(用户自行实现 HTTP 端)。
| 接口 | 方法 | 说明 |
|---|---|---|
/api/mode/switch |
POST | `{"mode": "auto" |
/api/laser |
POST | `{"action": "on" |
此接口需由 ROS2 侧 HTTP 服务(如 FastAPI / Flask / ros2-web-bridge)接收并处理。
四、MJPEG 视频流
详细格式见 1.7 MJPEG 视频流。
启动与话题发布
# 启动 web_video_server
ros2 run web_video_server web_video_server
# 确保摄像头话题有数据
ros2 run image_transport republish raw in:=/camera/image_raw out:=/camera/compressed
web_video_server 自动识别话题类型并推送 MJPEG。
五、启动顺序
# 1. 启动 ROS2 核心节点
ros2 launch your_robot bringup.launch.py
# 2. 启动 rosbridge
ros2 run rosbridge_server rosbridge_websocket &
# 3. 启动视频推流
ros2 run web_video_server web_video_server &
# 4. 启动模式切换 HTTP 服务(自行实现)
python3 mode_api_server.py &
# 5. 启动/重载 Nginx
sudo systemctl reload nginx
六、前端 config.js 配置
完整配置项说明见 1.9 可配置参数一览。
部署前修改 src/config.js 中的地址以匹配实际服务器环境,然后重新 pnpm build。
七、前端 Nginx 完整部署命令
# 构建
cd aimbot-web && pnpm build
# 部署
sudo cp -r dist/* /var/www/aimbot/
sudo cp nginx-aimbot.conf /etc/nginx/sites-available/aimbot
sudo ln -s /etc/nginx/sites-available/aimbot /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
八、故障排查
| 现象 | 检查项 |
|---|---|
| MJPEG 不出画面 | web_video_server 是否运行,话题是否有数据,Nginx proxy_buffering 是否设为 off |
| rosbridge 连不上 | 防火墙是否开放 9090,ROSBRIDGE_URL 是否用 ws:// 协议 |
| 按键无反应 | rosbridge 连接状态是否为"已连接",话题名是否与 ROS2 侧一致 |
| 模式切换报错 | /api/mode/switch 的 HTTP 服务是否实现并运行 |
| 激光开关无反应 | /api/laser 的 HTTP 服务是否实现,J 键或按钮是否正确触发 |
| 页面空白 | Nginx try_files 是否配了 $uri /index.html |
| 页面切换后画面不显示 | MJPEG 服务端是否单连接限制,检查 onBeforeRouteLeave 是否正常释放 |