Files
code-test-drone/docs/吊舱解算验证方案-模拟飞控喂表3.md
T
keita 257721e035 实测记录: 无激光时吊舱不输出表4(目标经纬度)
20Hz喂伪造表3(lat39.9042/lon116.4074/rel_alt50m),只收到约60Hz表2-2,
表4一帧都没有。判读: 吊舱目标地理解算依赖激光测距,无斜距即不解算。

架构影响:
- guidance.py 引导律在本设备上永久刹车(bridge.py:169 target_valid 依赖表4),
  安全方向正确但吊舱侧不再提供目标坐标
- 目标距离/坐标必须改由自有链路 stereo_yolo -> stereo_ranger 提供
- --auto-unlock 实测禁用: pod_console.py:232 把 last_tgt_s is None 当超时,
  会一锁定就误解锁并与 0x0F 常驻自动重锁循环震荡
- 阶段2/阶段3 精度实验取消(前提不成立)

保留两种待排除可能: 未真锁定、表3时戳/有效性要求,给出复测命令。
2026-10-08 10:04:04 +08:00

305 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 用「模拟飞控」喂表3 验证吊舱目标解算能力的实验方案
> 状态:**Q1 已有实测答案 —— 无激光时吊舱不输出 表4**(2026-10-08,见 §0.1)。
> 工具 `sim_fc.py` 已落地并配套 21 个纯软单测,参数已敲定见 §7。
> 日期:2026-09-29 立项 / 2026-10-08 实测收敛 关联:`docs/树莓派4B部署注意事项.md` §10.0
---
## 0.1 🔴 实测结论(2026-10-08):Q1 = 否,无激光 → 表4 不输出
**实验**:`sim_fc.py` 以 20Hz 持续喂伪造 表3(lat 39.9042 / lon 116.4074 /
rel_alt 50m,真实 UTC 递增时戳),同时观测下行。
**结果**:只收到 表2-2 遥测(≈60Hz),**表4 一帧都没有**(退出码 1)。
**判读**:吊舱的目标地理解算**依赖激光测距**——没有斜距就无法把视场角换算成
地面坐标,于是它**根本不发 表4**(不是发 0,也不是吐旧值)。原先设想的
"是否假设目标贴地"随之作废:**它压根没解算**。
**三个问题的答案**:
| 问题 | 实测答案 |
|---|---|
| **Q1** 无激光时是否输出表4 | ❌ **否**。表4 路径在本设备上不存在 |
| **Q2** 表4 频率 | ❌ 无从谈起(无输出)。`target_stale_ms` 对表4 无意义 |
| **Q3** 目标丢失后表4 行为 | ❌ 无法观测。目标丢失判据**不能**依赖表4(这与 §10.1 的预判一致) |
**架构影响(分叉已选边)**:
1. `guidance.py` 地理位置引导律在本设备上**永久刹车**——`bridge.py:169` 的
`target_valid` 依赖表4,表4 恒缺则每周期走 `_brake()`。安全方向正确(不会乱飞),
但**功能上等于吊舱侧不提供目标坐标**;
2. 目标距离与坐标**必须改由我方自有链路提供**:`stereo_yolo` 检测 →
`stereo_ranger` 双目视距。吊舱退化为"伺服稳定指向 + 表2-2 脱靶量"传感器;
3. 若要保留吊舱侧目标定位,只能换**带激光测距的吊舱型号**或外挂测距模块;
4. `--auto-unlock` **已实测禁用**(`pod_console.py:232` 把 `last_tgt_s is None`
当成超时,会一锁定就误解锁并循环震荡)。
**尚未完全排除的两种可能**(值得再花 10 分钟钉死,钉死前不要把结论当成绝对):
1. 本次测试**未必真的锁定成功**(bit2 未置位)→ 吊舱可能要求先锁定才解算。
复测加 `--detect person --auto-lock`,看汇总里"首次锁定"是否有值;
若**锁定后仍无表4**,结论才完全坐实。
2. 吊舱可能对 表3 的**时戳/位置有效性**有额外要求 → 用 `--freeze-time` 做一次对照。
**阶段2 / 阶段3 处置**:**取消**(精度比对与高度敏感性扫描都以表4 存在为前提)。
除非上面第 1 条复测翻盘,否则不要再往这条路上投时间。
---
## 0. 一句话目标
在**没有飞控、没有激光、无人机不动**的条件下,由树莓派单独向吊舱持续喂一套
「假飞控数据」(表3),逼吊舱输出目标经纬度(表4),从而判定:
**这台无激光吊舱到底能不能自主完成目标地理定位,以及精度是多少。**
如果它能输出表4 → 引导链路有输入;如果不能 → 目标距离与坐标必须由我方
`stereo_ranger` 双目视距链路提供,`pod_bridge` 的架构要改。
所以这是**分叉点实验**,值得先做。
---
## 1. 要回答的三个问题(按重要性排序)
| # | 问题 | 决定什么 |
|---|---|---|
| **Q1** | 无激光时,吊舱**是否仍然解算并输出表4** | 部署文档 §10 悬而未决项。若表4 根本不发 → 吊舱无法提供目标坐标,表4 路径作废 |
| **Q2** | 表4 的**实际发送频率** | 直接决定 `guidance.target_stale_ms` 取值,以及 `--auto-unlock SEC` 能否启用(§10.1) |
| **Q3** | 目标丢失后,表4 是**停止更新**还是**继续吐旧值** | 停止更新 → 现有 1000ms 超时已兜住;继续 → 必须新增停滞检测或外部判据 |
顺带还能白捡两个信息:**表4 的绝对/相对高度怎么填**(回答"是否假设目标在地面"),
以及**表3 是否需要我方按真实 UTC 递增时间戳**(否则吊舱可能判载机数据无效)。
---
## 2. 前置条件(做之前逐条打勾)
- [ ] 串口链路已通:`python -m pod_bridge.listen_pod /dev/ttyAMA0 --seconds 5`
→ 应得 **299~300 帧 / 5s ≈ 60.0 Hz**(已实测达标)
- [ ] PL011 已切到 ttyAMA0、内核 console 已清、`serial-getty` 已禁(部署文档 §4)
- [ ] 台架固定,吊舱视野内有可锁定的目标
- [ ] 已确认 `--detect` + `--auto-lock` 能让 bit2 置位(`pod_console.py`)
- [ ] ⚠ **同一时刻只能有一个进程占用串口**(见 §9 风险第 1 条)
---
## 3. 实验拓扑
```
┌──────────────┐ 表3 (EB91, 20Hz, 我方伪造) ┌──────────┐
│ 树莓派 4B │ ───────────────────────────→ │ │
│ sim_fc.py │ │ 光电吊舱 │
│ (假飞控) │ ←─────────────────────────── │ │
└──────────────┘ 表4 (EE18) + 表2-2 (EE16) └──────────┘
/dev/ttyAMA0 @115200 RX ← 与 TX 争同一个串口,必须收发一体
```
关键点:**不能一边 `listen_pod.py` 一边 `sim_fc.py`** —— 两个进程抢同一个
`/dev/ttyAMA0`,后者直接 `EBUSY` / 前者读数错帧。所以 `sim_fc.py` **必须收发
一体**:同进程里既发也收并自己做统计,不要拆成两个脚本。
---
## 4. 阶段1(里程碑):静态喂表3,看表4 是否出来
### 4.1 做什么
以固定频率循环下发**一套完全静止的假载机数据**:
| 字段(`CarrierAttitude`, `protocol.py:248`) | 阶段1 取值 | 说明 |
|---|---|---|
| roll / pitch / yaw | 全 0 | 台架静止 |
| latitude / longitude | 固定值(待定,见 §7) | 建议填台架近似真实位置,别填 0/0 |
| abs_alt / rel_alt | 固定值(待定) | 无激光,只能估喷雾高度 |
| UTC 时戳 | **建议用真实系统 UTC 实时推进** | 先跑递增时间的版本;若怀疑吊舱校验时间,再单独做「冻结时戳」对照 |
| airspeed / ground_speed | 0 | 台架静止 |
同时对下行做非阻塞统计:表2-2 帧数、bit2 锁定状态、**是否出现表4**、表4 频率与内容。
### 4.2 跑法(`sim_fc.py` 已实现,参数即 §7 敲定值)
```bash
cd utils
# 默认值已按 §7 设定,不必再写 lat/lon/rel-alt/rate
python -m pod_bridge.sim_fc /dev/ttyAMA0 --seconds 60 --json phase1.json
# 台架上验证目标识别 + 自动锁定同时进行解算观测
python -m pod_bridge.sim_fc /dev/ttyAMA0 \
--detect person --auto-lock --seconds 60 --json phase1.json
```
> 全部参数表与退出码见 §8。
### 4.3 判读对照表
| 观测 | 判读 | 下一步 |
|---|---|---|
| **出现表4** 且经纬度非零 | ✅ 无激光仍能解算 | 进阶段2 测精度 |
| 表4 全 0(有帧但内容为 0) | ⚠ 收到了但解算不出 | 加发更高 `--rel-alt`、尝试不同 `pitch`,仍无则问厂商"绝对/相对高度如何解算" |
| **完全不出现表4** | ❌ 该接口依赖激光/未使能 | **立即停下**:要么问厂商,要么转自有双目视距链路(`stereo_ranger`)提供目标坐标 |
| 表4 频率 < 5Hz 或忽快忽慢 | ⚠ 记录实际频率 | `--auto-unlock` 的 SEC 必须 ≥3 倍周期,否则误杀(§10.1) |
### 4.4 本阶段的隐藏产出
**即使 Q1 答案是"能"**,也要顺手记下这三条,它们后面都要用:
1. 表4 的实际频率(填 `--auto-unlock` 的 SEC)
2. 表4 里 `abs_alt` / `rel_alt` 的填法 —— 若恒等于我方喂进去的高度 →
**证明吊舱假设"目标在地面"**,这个假设在城市/楼顶/空中目标上会失效
3. 厂商那个黑盒测试软件显示的**表3 内容是否与我方编码一致** —— 若一致,
即交叉验证通过,可直接确认 `protocol.py` 的编码已对齐 docx 权威规约
---
## 5. 阶段2:拿已知真值评估精度
前提:阶段1 确认表4 会出。
### 5.1 静态精度
- 让一个人/车站在**已知 GPS 坐标**处(手机 GPS 或 RTK,误差记下来),锁定它。
- 采集 ≥60 组表4,看坐标**聚类半径**与**是否跳变**。
- 判据:静态目标下聚类半径应远小于后续控制用的 `stop_radius_m`(现 5m)。
若静态抖动就有好几米,飞行场景不可用。
### 5.2 相对高度敏感性扫描(本设备特有,无激光必须做)
因为**没有激光测距**,吊舱只能靠"目标在画面里的位置 + 我喂的相对高度 + 假设
目标贴地"去反推水平距离。这意味着:**相对高度的误差会被几何放大成水平误差。**
做法:固定同一个目标,把 `--rel-alt` 依次打成 `30 / 50 / 80 / 120 m`,
每档采 60 组,画出水平误差随高度的变化。
| 现象 | 判读 |
|---|---|
| 水平误差随高度线性放大 | 印证"目标贴地假设",此假设已知且可控 |
| 误差与高度无关地跳动 | 另有误差源(视场标定、姿态零点),需要厂商参数 |
| 某个高度档误差突然爆炸 | 该档附近可能超出解算有效范围,飞行高度要避开 |
**这一节的结论要回去更新部署文档 §10 的"待向厂商确认"条目。**
---
## 6. 阶段3(可选):动态姿态一致性
前两阶段通过后,把 yaw 打成匀速绕圈(`--yaw-rate 30`)、pitch 小幅摆动,
验证动态姿态下解算是否自洽:
- 目标真实静止时,表4 解算出的坐标**是否跟着姿态漂**;
- 姿态到 -179°/+179° 跨越时有没有符号/回绕 bug(`_to_s16` 已 clamp,但要验证吊舱侧);
- 姿态更新速率与吊舱解算的相位关系(要不要加大 `--rate`)。
排优先级:**阶段1 > 阶段2 > 阶段3**。阶段3 做不做取决于前两步结论是否已经足够决策。
---
## 7. 参数已敲定(2026-09-29)
四个参数全部做成命令行参数(不写死),默认值即为本次拍板结果:
| 参数 | 敲定值 | 理由 |
|---|---|---|
| 静态经纬度 | **北纬 39.9042° / 东经 116.4074°** | 不填 0/0,防止吊舱侧"经纬度无效"直接拒解算 |
| 相对高度 | **50 m** | 典型作业高度。阶段2 再扫 `--rel-alt 30/80/120` 做敏感性分析 |
| 发送频率 | **20 Hz** | 规格 1~60Hz 的中段。脚本已加守卫,越界直接拒绝启动 |
| 时戳策略 | 默认**真实 UTC 递增**,`--freeze-time` 做对照组 | 隔离"是不是时间不递增导致吊舱不理我"这个变量 |
> `--abs-alt` 未指定时自动取 `--rel-alt`(即假设起飞点海拔 0),避免 abs=0/rel=50
> 这种不合物理的组合吊舱看不懂。
---
## 8. `sim_fc.py`(已实现 · 2026-09-29)
主体:`utils/pod_bridge/sim_fc.py` 单测:`utils/tests/pod_bridge/sim_fc_test.py`
(21 个用例,纯软注入 fake serial,无需硬件;全量回归 372 passed)
```
python -m pod_bridge.sim_fc <port> [options]
位置参数:
port 串口设备(默认 /dev/ttyAMA0)
可选:
--baud 115200
--rate 20 表3 发送频率 Hz(规格 1~60,越界拒绝启动)
--seconds 60 运行时长,0 = 一直跑到 Ctrl+C
--lat <deg> 载机纬度(默认 39.9042)
--lon <deg> 载机经度(默认 116.4074)
--abs-alt <m> 绝对高度(默认取 --rel-alt,即假设起飞点海拔 0)
--rel-alt <m> 相对高度(默认 50)
--roll --pitch --yaw <deg> 静态姿态(默认 0)
--yaw-rate <dps> 阶段3 用:方位匀速推进,360° 自动回绕
--speed <m/s> 地速 >0 时位置沿 --yaw 方向匀速直线推进
--freeze-time 时戳冻结在启动时刻(对照实验)
--detect {person,drone} 顺带发 目标识别开(0x07)
--auto-lock 在 0x07 后追加 0x0F + 0x10(须配合 --detect)
--motor-on 先发 开电机(0x27)
--unlock-on-exit 退出前发 0x0E 停止跟踪
--json <path> 同时落一份原始采样 JSON(含逐帧 east/north 偏移)
--quiet 不打印每秒进度,只在结束输出汇总
退出码(便于脚本串测):
0 = 收到 表4 且经纬度有效(Q1 通过) 1 = 完全未收到 表4
2 = 收到 表4 但经纬度恒为 0 3 = 串口链路错误 / 参数非法
```
### 8.1 上机就跑(用户已敲定的默认值)
```bash
cd utils
python -m pod_bridge.sim_fc /dev/ttyAMA0 \
--detect person --auto-lock --seconds 60 --json phase1.json
```
要在台架上跑阶段2 的高度扫描时,改 `--rel-alt` 重跑即可
(`30 / 50 / 80 / 120` 各一次,`--json` 分别落盘后做聚类对比)。
### 8.2 行为约定(实现已满足)
1. **单进程收发一体**——独占串口,同进程发 表3、收 表2-2/表4;
2. 下行统计在热路径不打印,只在结束时输出**一张汇总表**(帧计数、实测频率、
首次锁定/首次表4 时刻、经纬度均值与 σ、相对我方的 east/north 偏移与散布、
表4 相对高度是否 ≈ 喂入值);
3. `--json` 落盘逐帧原始数据,供阶段2 做散点/聚类分析;
4. `Ctrl+C` 优雅退出仍打印汇总,可选发 `0x0E` 解锁;
5. 零新依赖(仅 `pyserial`),沿用 `pod_serial.py` 的非阻塞轮询;
6. **顺带回环自检**:若收到自己发的 表3 帧,提示 TX/RX 疑似短接。
### 8.3 自动给出的三条判读提示(人工不用自己算)
- 表4 相对高度 ≈ 喂入的载机高度 → 判定吊舱**假设"目标在地面"**,并警告该假设
对空中/楼顶目标失效(回答 §4.4 第 2 条);
- 未收到表4 时按可能性排序列出原因,直接指向下一动作;
- 出现回环帧时提示查接线。
---
## 9. 风险与已知的坑
1. **串口独占**(最容易踩):阶段实验期间不要同时开 `listen_pod.py` /
`pod_console.py` / `bridge.py`。要"边喂边看仪表盘"就得先给 `sim_fc.py`
自己加面板,而不是并行两个进程。
2. **假数据被当真**:本脚本只走串口、不碰 PX4,**绝不能**与真实 offboard
同时接入同一飞控。台架阶段物理断开飞控连线最保险。
3. **闭环误解**:阶段1~3 全是**开环观测**,不产生任何控制输出。别指望它验证
控制律 —— 那是之后飞起来的事。
4. **无激光是硬约束**:表2-2 的激光字段恒 0(厂商确认),任何依赖它的判据都无效。
这也是为什么本实验要专门替吊舱把高度喂进去。
5. **`--auto-unlock` 仍然别贸然开**:本实验只是**测量**它的前置条件(表4 是否存在
与其频率),不等于批准启用。启用与否仍按 §10.1 的次序:先确认表4 在发,再谈 SEC。
---
## 10. 结论的落点(做完写回哪里)
| 结论 | 落到哪个文件 |
|---|---|
| Q1 是否输出表4 | `docs/树莓派4B部署注意事项.md` §10 —— 删掉/回答"待向厂商确认"条目 |
| Q2 表4 频率 | `utils/config/pod_bridge.json` → `guidance.target_stale_ms`、§10.1 的 SEC 建议值 |
| Q3 丢失行为 | §8 / §10.1 的目标丢失判据(补第二判据) |
| 高度假设的证据 | §10 "无激光如何解算高度"小节 |
| 静态精度 | 决定是否敢把 `stop_radius_m` 从 5m 往下调 |