# 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 ``` **请求体:** ```json { "mode": "auto" } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `mode` | string | 是 | 目标模式,可选值: `"auto"` \| `"manual"` \| `"debug"` | **成功响应 (200):** ```json { "code": 0, "message": "ok" } ``` **错误响应 (4xx/5xx):** ```json { "code": -1, "message": "unknown mode: xxx" } ``` --- ### 1.2 HTTP API — 激光开关 ``` POST /api/laser Content-Type: application/json ``` **请求体:** ```json { "action": "on" } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `action` | string | 是 | `"on"` 开激光, `"off"` 关激光 | **成功响应 (200):** ```json { "code": 0, "message": "ok" } ``` --- ### 1.3 rosbridge WebSocket — 连接 ``` URL: ws://:9090 协议: rosbridge v2.0 (JSON) 连接后: 前端通过 roslibjs 自动完成协议握手 ``` 连接状态通过前端 UI 实时展示: - `已连接` (绿色) — WebSocket 已建立,rosbridge 协议就绪 - `未连接` (红色) — 断开或错误 --- ### 1.4 ROS 话题 — `/cmd_wheel` (底盘轮速) | 属性 | 值 | |------|-----| | 消息类型 | `std_msgs/Float32MultiArray` | | 方向 | 前端 → ROS2 | | 发布时机 | 手动模式下按 WASD, 值变化时发送; 松键后自动停发 | | 发布频率 | 按需 (值变即发), 约 30~60 Hz | **数据格式:** ```json { "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 | **数据格式:** ```json { "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` 字段中):** ```json { "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}}" } ``` **解析后结构:** ```json { "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): ```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://: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 --boundary Content-Type: image/jpeg Content-Length: 54102 ... ``` **前端消费方式:** ```html ``` **注意事项:** | 事项 | 说明 | |------|------| | 单连接限制 | `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 可配置参数一览 ```js // 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 直接托管: ```bash # 本地构建 cd aimbot-web pnpm build # 上传到服务器 scp -r dist/* user@server:/var/www/aimbot/ ``` ### 2.2 Nginx 配置示例 ```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 反代。 ```bash # 安装 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 推流) ```bash # 安装 sudo apt install ros-${ROS_DISTRO}-web-video-server # 启动 (默认 http://0.0.0.0:8081) # 发布摄像头话题后自动生成 /stream?topic= 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](#11-http-api--模式切换) 和 [1.2](#12-http-api--激光开关): | 接口 | 方法 | 说明 | |------|------|------| | `/api/mode/switch` | POST | `{"mode": "auto"|"manual"|"debug"}` | | `/api/laser` | POST | `{"action": "on"|"off"}` | 此接口需由 ROS2 侧 HTTP 服务(如 FastAPI / Flask / ros2-web-bridge)接收并处理。 --- ## 四、MJPEG 视频流 详细格式见 [1.7 MJPEG 视频流](#17-mjpeg-视频流)。 ### 启动与话题发布 ```bash # 启动 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。 --- ## 五、启动顺序 ```bash # 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 可配置参数一览](#19-可配置参数一览)。 部署前修改 `src/config.js` 中的地址以匹配实际服务器环境,然后重新 `pnpm build`。 --- ## 七、前端 Nginx 完整部署命令 ```bash # 构建 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` 是否正常释放 |