diff --git a/.gitignore b/.gitignore index aba86d7..756dfa3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # ROS2 colcon build artifacts workspace/build/ +workspace/data/ workspace/install/ workspace/log/ diff --git a/README.md b/README.md index aca05d3..65dbb81 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ │ │ application_pkg │ │ │ │ ┌───────────────────────────────┐ │ │ │ │ │ web_node (:8080) │ │ │ -│ │ │ - POST /api/mode/switch │──┼──▶ /gimbal/mode │ +│ │ │ - POST /api/gimbal/mode/switch│──┼──▶ /gimbal/mode │ │ │ │ - POST /api/laser │ │ │ │ │ └───────────────────────────────┘ │ │ │ └─────────────────────────────────────┘ │ @@ -53,7 +53,7 @@ | 目标追踪 | `/vision/target` | vision_node → gimbal_node | `geometry_msgs/Point` | 归一化目标偏移 (x,y) | | 模式切换 | `/gimbal/mode` | web_node → gimbal_node | `std_msgs/String` | auto/manual/debug | | 手动控制 | `/cmd_gimbal` | 前端 → gimbal_node | `geometry_msgs/Twist` | yaw/pitch 角度 | -| PID 参数 | `/pid_config` | 前端 → gimbal_node | `std_msgs/String` | PID 参数 JSON | +| PID 参数 | `/web/gimbal_pid_config` | 前端 → gimbal_node | `std_msgs/String` | PID 参数 JSON | ## 目录结构 diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..fcfdd94 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,105 @@ +# HTTP API 数据格式 + +Base URL: `http://192.168.0.11` + +所有 POST 请求 Content-Type 为 `application/json`。 + +--- + +## 1. 云台模式切换 + +``` +POST /api/gimbal/mode/switch +``` + +**请求体:** + +```json +{ "mode": "auto" } +``` + +`mode` 取值: `"auto"` | `"manual"` | `"debug"` + +--- + +## 2. 底盘模式切换 + +``` +POST /api/chassis/mode/switch +``` + +**请求体:** + +```json +{ "mode": "auto" } +``` + +`mode` 取值: `"auto"` | `"manual"` | `"debug"` + +--- + +## 3. 激光控制 + +``` +POST /api/laser +``` + +**请求体:** + +```json +{ "action": "on" } +``` + +`action` 取值: `"on"` | `"off"` + +--- + +## 4. 云台 PID 配置 + +``` +GET /api/gimbal/pidconfig (获取) +POST /api/gimbal/pidconfig (保存) +``` + +**GET 响应 / POST 请求体:** + +```json +{ + "visual_yaw": { "kp": 0.0, "ki": 0.0, "kd": 0.0 }, + "visual_pitch": { "kp": 0.0, "ki": 0.0, "kd": 0.0 }, + "angle_yaw": { "kp": 0.0, "ki": 0.0, "kd": 0.0 }, + "angle_pitch": { "kp": 0.0, "ki": 0.0, "kd": 0.0 } +} +``` + +| 字段 | 对应 PID 环 | +|------|------------| +| `visual_yaw` | 视觉环 Yaw 电机 | +| `visual_pitch` | 视觉环 Pitch 电机 | +| `angle_yaw` | 角度环 Yaw 电机 | +| `angle_pitch` | 角度环 Pitch 电机 | + +--- + +## 5. 底盘 PID 配置 + +``` +GET /api/chassis/pidconfig (获取) +POST /api/chassis/pidconfig (保存) +``` + +**GET 响应 / POST 请求体:** + +```json +{ + "speed": { "kp": 0.0, "ki": 0.0, "kd": 0.0 }, + "angle": { "kp": 0.0, "ki": 0.0, "kd": 0.0 }, + "position": { "kp": 0.0, "ki": 0.0, "kd": 0.0 } +} +``` + +| 字段 | 对应 PID 环 | +|------|------------| +| `speed` | 速度环 | +| `angle` | 角度环 | +| `position` | 位置环 | diff --git a/docs/front-end_ROS.md b/docs/front-end_ROS.md new file mode 100644 index 0000000..e1806cb --- /dev/null +++ b/docs/front-end_ROS.md @@ -0,0 +1,55 @@ +# ROS 话题格式 + +通过 rosbridge WebSocket(`ws://192.168.0.11:9090`)发布,均为单向 `publish`(前端 → ROS),无订阅。 + +--- + +## 1. `/cmd_chassis` — 底盘控制 + +**类型:** `std_msgs/Float32MultiArray` + +**格式:** + +```json +{ + "layout": { "dim": [], "data_offset": 0 }, + "data": [0.5, 0.0] +} +``` + +`data` 字段: + +| 索引 | 含义 | 单位 | 来源常量 | +|------|------|------|---------| +| `data[0]` | 线速度 | m/s | `BASE_SPEED` (0.5) | +| `data[1]` | 角速度(偏航) | 度/秒 | `YAW_VELOCITY` (1.0) | + +--- + +## 2. `/cmd_gimbal` — 云台控制 + +**类型:** `geometry_msgs/Twist` + +**格式:** + +```json +{ + "linear": { "x": 0, "y": 0, "z": 0 }, + "angular": { "x": 0, "y": 10.0, "z": 0 } +} +``` + +`angular` 字段: + +| 字段 | 含义 | 单位 | 来源常量 | +|------|------|------|---------| +| `angular.y` | 俯仰角速度 (pitch) | 度/秒 | `GIMBAL_PITCH_DEG_S` (10) | +| `angular.z` | 偏航角速度 (yaw) | 度/秒 | `GIMBAL_YAW_DEG_S` (10) | + +`linear` 字段保留填零,云台无平移运动。 + +--- + +## 3. 发布频率 + +发布频率常量 `PUBLISH_RATE = 30` Hz,但在 ManualMode 中实际采用 on-demand 模式:仅当键盘按住、且速度值有变化时才发布。 diff --git a/docs/与前端对接文档.md b/docs/与前端对接文档.md deleted file mode 100644 index acab1ac..0000000 --- a/docs/与前端对接文档.md +++ /dev/null @@ -1,571 +0,0 @@ -# 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` 是否正常释放 | diff --git a/docs/云台通信协议指南.md b/docs/云台通信协议指南.md index e854dfa..330bc55 100644 --- a/docs/云台通信协议指南.md +++ b/docs/云台通信协议指南.md @@ -19,6 +19,35 @@ | 0xA6 | 10 | 手动控制数据 | | 0xA7 | 3 | 命令 | | 0xA8 | 50 | 调试调参数据 | +| 0xA9 | 6 | 前馈输入 | + +--- + +## 5. 前馈输入帧 0xA9 (6 字节) + +**用途**: 下发前馈输入值到 yaw 电机前馈控制器,在自动和手动模式下均可生效。 + +``` +[SOF:0xA9][feedforward:4][checksum:1] +``` + +| 偏移 | 长度 | 类型 | 含义 | +|---|---|---|---| +| 0 | 1 | uint8 | 帧头 0xA9 | +| 1 | 4 | float | 前馈输入值 | +| 5 | 1 | uint8 | checksum | + +**Python 写入示例**: + +```python +def send_feedforward(ser, feedforward: float): + data = bytearray() + data.append(0xA9) + data.extend(struct.pack(' **注意**:调试模式下所有控制任务已关闭,电机停转。修改参数后需切换到自动或手动模式才能观察效果。修改的参数直接写入 PID 实例的 Kp/Ki/Kd 字段,不清除积分等运行时状态。 + +--- + +### 4. 角度速率前馈帧 `0xA9`(6 字节)— MCU → PC + +由 MCU 经 DMA 主动发送,上报位置环计算的角度速率,供云台等设备做前馈控制。 + +``` +┌────────┬─────────────────────┬──────────┐ +│ SOF │ angle_velocity │ checksum │ +│ 0xA9 │ float (4 B) │ 1 B │ +└────────┴─────────────────────┴──────────┘ +``` + +| 偏移 | 长度 | 字段 | 类型 | 说明 | +|---|---|---|---|---| +| 0 | 1 | SOF | uint8_t | 帧头,固定值 `0xA9` | +| 1 | 4 | angle_velocity | float (LE) | 角度速率,单位 °/s | +| 5 | 1 | checksum | uint8_t | `(0xA9 + 前 4 字节之和) & 0xFF` | + +**发送触发条件**:位置环控制任务使能时,每 20ms(50Hz)在 `position_control_task()` 中调用 `command_send_angle_velocity()`。 + +--- + +## 通信流程 + +``` +┌──────────┐ ┌──────────┐ +│ PC │ ──── 0xA7 cmd ────────→ │ MSPM0 │ +│ (上位机) │ ←─── 0xA9 feedback ──── │ (MCU) │ +│ │ ──── 0xA6 manual ─────→ │ │ +│ │ ──── 0xA8 debug ──────→ │ │ +└──────────┘ └──────────┘ +``` + +**典型操作流程:** + +1. **上电默认**:自动模式,位置环+角度环+速度环全部使能 +2. **切换到手动模式**:发送 `A7 02 A9`,然后周期发送 `0xA6` 控制帧驱动底盘 +3. **PID 调参**:发送 `A7 03 AA` 进入调试模式(电机停转),发送 `0xA8` 调参帧更新参数,再发 `A7 01 A8` 或 `A7 02 A9` 切换回运行模式观察效果 + +## 模式切换副作用 + +每次切换模式时,系统会执行以下操作: + +| 操作 | 自动模式 | 手动模式 | 调试模式 | +|---|---|---|---| +| 设置 `current_mode` | AUTO (0) | MANUAL (1) | DEBUG (2) | +| 清除 PID 运行时状态 | 全部 4 个 PID | 速度环+角度环 | — | +| `yaw_ref` | = 当前 `imu_angle.yaw` | = 当前 `imu_angle.yaw` | — | +| `angle_velocity` | 归零 | 归零 | — | +| `motor_speed_ref` | 归零 | 归零 | — | +| `motor_speed_base` | = 10 (AUTO_MODE_BASE_SPEED) | 归零 | — | +| 电机 PWM | 归零 | 归零 | 归零 | +| 速度环使能 | ✅ | ✅ | ❌ | +| 角度环使能 | ✅ | ✅ | ❌ | +| 位置环使能 | ✅ | ❌ | ❌ | +| 蜂鸣器 | 100ms | 200ms | 300ms | + +## C 语言帧构造示例 + +```c +#include +#include + +// --- 构造 0xA7 命令帧 --- +void build_cmd_frame(uint8_t *buf, uint8_t cmd) { + buf[0] = 0xA7; + buf[1] = cmd; + buf[2] = (uint8_t)(0xA7 + cmd); + // 发送 buf, 长度 3 +} + +// --- 构造 0xA6 手动控制帧 --- +void build_manual_frame(uint8_t *buf, int16_t speed, float yaw_rate) { + buf[0] = 0xA6; + memcpy(&buf[1], &speed, 2); // int16_t, 小端 + memcpy(&buf[3], &yaw_rate, 4); // float, 小端 + uint8_t sum = 0xA6; + for (int i = 1; i < 7; i++) sum += buf[i]; + buf[7] = sum; + // 发送 buf, 长度 8 +} + +// --- 构造 0xA8 调试调参帧 --- +void build_debug_frame(uint8_t *buf, float kp_s, float ki_s, float kd_s, + float kp_y, float ki_y, float kd_y, + float kp_p, float ki_p, float kd_p) { + buf[0] = 0xA8; + memcpy(&buf[1], &kp_s, 4); + memcpy(&buf[5], &ki_s, 4); + memcpy(&buf[9], &kd_s, 4); + memcpy(&buf[13], &kp_y, 4); + memcpy(&buf[17], &ki_y, 4); + memcpy(&buf[21], &kd_y, 4); + memcpy(&buf[25], &kp_p, 4); + memcpy(&buf[29], &ki_p, 4); + memcpy(&buf[33], &kd_p, 4); + uint8_t sum = 0xA8; + for (int i = 1; i < 37; i++) sum += buf[i]; + buf[37] = sum; + // 发送 buf, 长度 38 +} +```