docs: update gimbal protocol and frontend integration specs
This commit is contained in:
+383
-62
@@ -1,4 +1,4 @@
|
|||||||
# 与前端对接文档
|
# AimBot 上游服务器对接文档
|
||||||
|
|
||||||
## 整体架构
|
## 整体架构
|
||||||
|
|
||||||
@@ -6,8 +6,8 @@
|
|||||||
┌──────────────────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────────────────┐
|
||||||
│ 浏览器 (Vue3 前端) │
|
│ 浏览器 (Vue3 前端) │
|
||||||
│ - 自动模式:观看 MJPEG 视频流 │
|
│ - 自动模式:观看 MJPEG 视频流 │
|
||||||
│ - 手动模式:键盘遥控 + MJPEG 视频流 │
|
│ - 手动模式:键盘遥控(WASD/方向键/J) + MJPEG 视频流 │
|
||||||
│ - 调试模式:PID 调参 │
|
│ - 调试模式:PID 调参 + 激光开关 │
|
||||||
└──────┬──────────────┬──────────────┬─────────────────────┘
|
└──────┬──────────────┬──────────────┬─────────────────────┘
|
||||||
│ HTTP │ WebSocket │ HTTP (MJPEG)
|
│ HTTP │ WebSocket │ HTTP (MJPEG)
|
||||||
▼ ▼ ▼
|
▼ ▼ ▼
|
||||||
@@ -23,14 +23,362 @@
|
|||||||
│ - /cmd_gimbal (云台控制) │
|
│ - /cmd_gimbal (云台控制) │
|
||||||
│ - /pid_config (PID参数) │
|
│ - /pid_config (PID参数) │
|
||||||
│ - /api/mode/switch (模式切换) │
|
│ - /api/mode/switch (模式切换) │
|
||||||
|
│ - /api/laser (激光开关) │
|
||||||
└────────────────────────────────┘
|
└────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
## 一、Nginx 配置
|
## 一、数据结构详解
|
||||||
|
|
||||||
|
前端与 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://<host>: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://<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>
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**前端消费方式:**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 浏览器原生支持 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 可配置参数一览
|
||||||
|
|
||||||
|
```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。
|
Nginx 负责三件事:托管 Vue 前端静态文件、反代 MJPEG 视频流、反代 HTTP API。
|
||||||
|
|
||||||
### 1.1 前端部署
|
### 2.1 前端部署
|
||||||
|
|
||||||
构建产物在 `dist/` 目录,部署到服务器后由 Nginx 直接托管:
|
构建产物在 `dist/` 目录,部署到服务器后由 Nginx 直接托管:
|
||||||
|
|
||||||
@@ -43,7 +391,7 @@ pnpm build
|
|||||||
scp -r dist/* user@server:/var/www/aimbot/
|
scp -r dist/* user@server:/var/www/aimbot/
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1.2 Nginx 配置示例
|
### 2.2 Nginx 配置示例
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
server {
|
server {
|
||||||
@@ -87,7 +435,7 @@ server {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1.3 关键说明
|
### 2.3 关键说明
|
||||||
|
|
||||||
| 配置项 | 说明 |
|
| 配置项 | 说明 |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
@@ -98,9 +446,9 @@ server {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 二、ROS2 环境配置
|
## 三、ROS2 环境配置
|
||||||
|
|
||||||
### 2.1 rosbridge (WebSocket 服务)
|
### 3.1 rosbridge (WebSocket 服务)
|
||||||
|
|
||||||
前端通过 roslibjs 直连 rosbridge WebSocket,不走 Nginx 反代。
|
前端通过 roslibjs 直连 rosbridge WebSocket,不走 Nginx 反代。
|
||||||
|
|
||||||
@@ -112,7 +460,7 @@ sudo apt install ros-${ROS_DISTRO}-rosbridge-server
|
|||||||
ros2 run rosbridge_server rosbridge_websocket
|
ros2 run rosbridge_server rosbridge_websocket
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.2 web_video_server (MJPEG 推流)
|
### 3.2 web_video_server (MJPEG 推流)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 安装
|
# 安装
|
||||||
@@ -123,57 +471,50 @@ sudo apt install ros-${ROS_DISTRO}-web-video-server
|
|||||||
ros2 run web_video_server web_video_server
|
ros2 run web_video_server web_video_server
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.3 话题一览
|
### 3.3 话题一览
|
||||||
|
|
||||||
rosbridge 发布/订阅的话题需要与前端 `config.js` 一致:
|
rosbridge 发布/订阅的话题需要与前端 `config.js` 一致:
|
||||||
|
|
||||||
| 话题 | 消息类型 | 方向 | 用途 |
|
| 话题 | 消息类型 | 方向 | 用途 |
|
||||||
|------|----------|------|------|
|
|------|----------|------|------|
|
||||||
| `/cmd_wheel` | `std_msgs/Float32MultiArray` | 前端→ROS2 | 左右轮速 `[left, right]` |
|
| `/cmd_wheel` | `std_msgs/Float32MultiArray` | 前端→ROS2 | 左右轮速 `[left, right]` |
|
||||||
| `/cmd_gimbal` | `geometry_msgs/Twist` | 前端→ROS2 | 云台角速度 `angular.y`(pitch) `angular.z`(yaw) |
|
| `/cmd_gimbal` | `geometry_msgs/Twist` | 前端→ROS2 | 云台角度速度(单位度/秒) `angular.y`(pitch) `angular.z`(yaw) |
|
||||||
| `/pid_config` | `std_msgs/String` | 前端→ROS2 | PID 参数 JSON |
|
| `/pid_config` | `std_msgs/String` | 前端→ROS2 | PID 参数 JSON |
|
||||||
|
|
||||||
### 2.4 模式切换 API
|
### 3.4 模式切换与激光 API
|
||||||
|
|
||||||
前端 HTTP POST → Nginx 反代 → ROS2 节点(用户自行实现 HTTP 端):
|
前端 HTTP POST → Nginx 反代 → ROS2 节点(用户自行实现 HTTP 端)。
|
||||||
|
|
||||||
```
|
需实现两个接口,完整数据格式见 [1.1](#11-http-api--模式切换) 和 [1.2](#12-http-api--激光开关):
|
||||||
POST /api/mode/switch
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{ "mode": "auto" | "manual" | "debug" }
|
| 接口 | 方法 | 说明 |
|
||||||
```
|
|------|------|------|
|
||||||
|
| `/api/mode/switch` | POST | `{"mode": "auto"|"manual"|"debug"}` |
|
||||||
|
| `/api/laser` | POST | `{"action": "on"|"off"}` |
|
||||||
|
|
||||||
此接口需由 ROS2 侧 HTTP 服务(如 FastAPI / Flask / ros2-web-bridge)接收并处理模式切换逻辑。
|
此接口需由 ROS2 侧 HTTP 服务(如 FastAPI / Flask / ros2-web-bridge)接收并处理。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 三、MJPEG 视频流地址
|
## 四、MJPEG 视频流
|
||||||
|
|
||||||
`web_video_server` 的 URL 规则:`http://<host>:8081/stream?topic=<话题名>`
|
详细格式见 [1.7 MJPEG 视频流](#17-mjpeg-视频流)。
|
||||||
|
|
||||||
前端 `config.js` 中配置对应 URL:
|
### 启动与话题发布
|
||||||
|
|
||||||
```js
|
|
||||||
// src/config.js
|
|
||||||
export const MJPEG_AUTO_URL = '/stream?topic=/camera_auto'
|
|
||||||
export const MJPEG_MANUAL_URL = '/stream?topic=/camera_manual'
|
|
||||||
```
|
|
||||||
|
|
||||||
使用 `/stream` 相对路径时由 Nginx 反代到 `web_video_server`,避免跨域问题。
|
|
||||||
|
|
||||||
### 摄像头话题发布示例
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 启动 web_video_server
|
||||||
|
ros2 run web_video_server web_video_server
|
||||||
|
|
||||||
# 确保摄像头话题有数据
|
# 确保摄像头话题有数据
|
||||||
ros2 run image_transport republish raw in:=/camera_auto/image_raw out:=/camera_auto/compressed
|
ros2 run image_transport republish raw in:=/camera/image_raw out:=/camera/compressed
|
||||||
```
|
```
|
||||||
|
|
||||||
`web_video_server` 自动识别话题类型并推送 MJPEG。
|
`web_video_server` 自动识别话题类型并推送 MJPEG。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 四、启动顺序
|
## 五、启动顺序
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 启动 ROS2 核心节点
|
# 1. 启动 ROS2 核心节点
|
||||||
@@ -194,37 +535,15 @@ sudo systemctl reload nginx
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 五、前端 config.js 配置
|
## 六、前端 config.js 配置
|
||||||
|
|
||||||
部署前修改 `src/config.js` 中的地址以匹配实际服务器环境:
|
完整配置项说明见 [1.9 可配置参数一览](#19-可配置参数一览)。
|
||||||
|
|
||||||
```js
|
部署前修改 `src/config.js` 中的地址以匹配实际服务器环境,然后重新 `pnpm build`。
|
||||||
// 后端 HTTP API(模式切换)
|
|
||||||
export const HTTP_API_BASE = '' // 同域代理可留空或填 '/api'
|
|
||||||
|
|
||||||
// rosbridge WebSocket
|
|
||||||
export const ROSBRIDGE_URL = 'ws://192.168.1.100:9090' // 改成 ROS2 主机 IP
|
|
||||||
|
|
||||||
// MJPEG 视频流 — 经 Nginx 代理
|
|
||||||
export const MJPEG_AUTO_URL = '/stream?topic=/camera_auto'
|
|
||||||
export const MJPEG_MANUAL_URL = '/stream?topic=/camera_manual'
|
|
||||||
|
|
||||||
// ROS 话题 — 必须与 ROS2 侧一致
|
|
||||||
export const CMD_WHEEL_TOPIC = '/cmd_wheel'
|
|
||||||
export const CMD_GIMBAL_TOPIC = '/cmd_gimbal'
|
|
||||||
export const PID_CONFIG_TOPIC = '/pid_config'
|
|
||||||
|
|
||||||
// 键盘控制参数 — 按实际机器人调整
|
|
||||||
export const MAX_LINEAR = 0.5 // 最大线速度 m/s
|
|
||||||
export const MAX_ANGULAR = 1.0 // 最大角速度 rad/s
|
|
||||||
export const WHEEL_SEPARATION = 0.2 // 轮距 m
|
|
||||||
export const GIMBAL_PITCH_SPEED = 0.5 // 云台 pitch 角速度 rad/s
|
|
||||||
export const GIMBAL_YAW_SPEED = 0.5 // 云台 yaw 角速度 rad/s
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 六、前端 nginx 完整部署命令
|
## 七、前端 Nginx 完整部署命令
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 构建
|
# 构建
|
||||||
@@ -239,7 +558,7 @@ sudo nginx -t && sudo systemctl reload nginx
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 七、故障排查
|
## 八、故障排查
|
||||||
|
|
||||||
| 现象 | 检查项 |
|
| 现象 | 检查项 |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
@@ -247,4 +566,6 @@ sudo nginx -t && sudo systemctl reload nginx
|
|||||||
| rosbridge 连不上 | 防火墙是否开放 9090,`ROSBRIDGE_URL` 是否用 `ws://` 协议 |
|
| rosbridge 连不上 | 防火墙是否开放 9090,`ROSBRIDGE_URL` 是否用 `ws://` 协议 |
|
||||||
| 按键无反应 | rosbridge 连接状态是否为"已连接",话题名是否与 ROS2 侧一致 |
|
| 按键无反应 | rosbridge 连接状态是否为"已连接",话题名是否与 ROS2 侧一致 |
|
||||||
| 模式切换报错 | `/api/mode/switch` 的 HTTP 服务是否实现并运行 |
|
| 模式切换报错 | `/api/mode/switch` 的 HTTP 服务是否实现并运行 |
|
||||||
|
| 激光开关无反应 | `/api/laser` 的 HTTP 服务是否实现,J 键或按钮是否正确触发 |
|
||||||
| 页面空白 | Nginx `try_files` 是否配了 `$uri /index.html` |
|
| 页面空白 | Nginx `try_files` 是否配了 `$uri /index.html` |
|
||||||
|
| 页面切换后画面不显示 | MJPEG 服务端是否单连接限制,检查 `onBeforeRouteLeave` 是否正常释放 |
|
||||||
|
|||||||
+9
-11
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
| 参数 | 值 |
|
| 参数 | 值 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 波特率 | 115200 |
|
| 波特率 | 921600 |
|
||||||
| 数据位 | 8 |
|
| 数据位 | 8 |
|
||||||
| 停止位 | 1 |
|
| 停止位 | 1 |
|
||||||
| 校验位 | None |
|
| 校验位 | None |
|
||||||
@@ -53,30 +53,28 @@ def send_auto_tracking(ser, error_x: float, error_y: float):
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 手动控制数据帧 0xA6 (11 字节)
|
## 2. 手动控制数据帧 0xA6 (10 字节)
|
||||||
|
|
||||||
**用途**: 手动模式下发送角度目标值。
|
**用途**: 手动模式下发送角速度目标值。
|
||||||
|
|
||||||
```
|
```
|
||||||
[SOF:0xA6][zero_force:1][yaw_ref:4][pitch_ref:4][checksum:1]
|
[SOF:0xA6][yaw_velocity:4][pitch_velocity:4][checksum:1]
|
||||||
```
|
```
|
||||||
|
|
||||||
| 偏移 | 长度 | 类型 | 含义 |
|
| 偏移 | 长度 | 类型 | 含义 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 0 | 1 | uint8 | 帧头 0xA6 |
|
| 0 | 1 | uint8 | 帧头 0xA6 |
|
||||||
| 1 | 1 | uint8 | 零力标志 (0=正常, 1=零力) |
|
| 1 | 4 | float | Yaw 角速度 (degrees/s) |
|
||||||
| 2 | 4 | float | Yaw 角度参考 (degrees) |
|
| 5 | 4 | float | Pitch 角速度 (degrees/s) |
|
||||||
| 6 | 4 | float | Pitch 角度参考 (degrees) |
|
| 9 | 1 | uint8 | checksum |
|
||||||
| 10 | 1 | uint8 | checksum |
|
|
||||||
|
|
||||||
**Python 写入示例**:
|
**Python 写入示例**:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def send_manual_control(ser, yaw_deg: float, pitch_deg: float, zero_force: int = 0):
|
def send_manual_control(ser, yaw_deg_per_s: float, pitch_deg_per_s: float):
|
||||||
data = bytearray()
|
data = bytearray()
|
||||||
data.append(0xA6)
|
data.append(0xA6)
|
||||||
data.append(0 if zero_force else 0)
|
data.extend(struct.pack('<ff', yaw_deg_per_s, pitch_deg_per_s))
|
||||||
data.extend(struct.pack('<ff', yaw_deg, pitch_deg))
|
|
||||||
checksum = sum(data) & 0xFF
|
checksum = sum(data) & 0xFF
|
||||||
data.append(checksum)
|
data.append(checksum)
|
||||||
ser.write(data)
|
ser.write(data)
|
||||||
|
|||||||
Reference in New Issue
Block a user