Files
Vision-Web/SERVER_INTEGRATION.md
T
2026-06-03 00:01:13 +08:00

16 KiB
Raw Blame History

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 端)。

需实现两个接口,完整数据格式见 1.1 和 1.2:

接口 方法 说明
/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 是否正常释放