Files
code-test-drone/docs/吊舱解算验证方案-模拟飞控喂表3.md
keita 5e7e0d9713 钉死结论: 锁定后仍无表4,并补协议级硬证据
复测(--detect person --auto-lock, 60s): 表3 1200帧@20Hz, 表2-2 3599帧@60Hz,
首次锁定 0.13s 且全程保持, 表4 仍 0 帧。→ 排除「没锁定所以不解算」。

协议级硬证据: 激光测距字段只存在于表2-2(吊舱→控制器); 表2-1/表3 均无距离
输入字段, 故即便 stereo_ranger 算出距离也无法回灌让吊舱解算 —— 连补救通道都没有。

架构走向: 目标经纬度改由我方自算(视觉测距+载机GPS/姿态+吊舱框架角),
吊舱退化为伺服指向+脱靶量传感器。附待改点 bridge.py:169 / guidance.py 输入。
唯一翻盘途径: 问厂商无激光配置下表4是否设计上就不输出。
2026-10-09 10:01:40 +08:00

17 KiB
Raw Permalink Blame History

用「模拟飞控」喂表3 验证吊舱目标解算能力的实验方案

状态:✅ 已收敛 —— Q1 = 否,无激光时吊舱不输出 表4,结论已钉死。 初测 2026-10-08(§0.1)→ 复测钉死 2026-10-09(§0.2,锁定后仍 0 帧)。 工具 sim_fc.py 已落地并配套 21 个纯软单测,参数见 §7;阶段2/阶段3 已取消。 日期:2026-09-29 立项 / 2026-10-09 收敛 关联:docs/树莓派4B部署注意事项.md §10.0


0. 一句话目标

在没有飞控、没有激光、无人机不动的条件下,由树莓派单独向吊舱持续喂一套 「假飞控数据」(表3),逼吊舱输出目标经纬度(表4),从而判定:

这台无激光吊舱到底能不能自主完成目标地理定位,以及精度是多少。

如果它能输出表4 → 引导链路有输入;如果不能 → 目标距离与坐标必须由我方 stereo_ranger 双目视距链路提供,pod_bridge 的架构要改。 所以这是分叉点实验,值得先做。


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 当成超时,会一锁定就误解锁并循环震荡)。

阶段2 / 阶段3 处置:取消。精度比对与高度敏感性扫描都以表4 存在为前提, 前提已不成立,不要再往这条路上投时间。

0.2 ✅ 结论钉死(2026-10-09 复测)

复测命令(--detect person --auto-lock --seconds 60)关键数据:

观测项 实测值
表3 发送 / 表2-2 遥测 1200 帧 @20.0Hz / 3599 帧 @59.98Hz
锁定 锁定次数 1,首次 0.13s(bit2 已置位,全程保持)
表4 0 帧

两种翻盘可能的处置:

可能 结果
① 本次没真锁定,所以不解算 ❌ 已排除——实测确实锁定了(0.13s)
② 吊舱校验表3 时戳/位置有效性 ❌ 无需再测——本次喂的是真实 UTC 递增 + 合法经纬度,已是它能拿到的最标准输入;--freeze-time 只会更不合法,测了证明不了什么

另有一条比实测更致命的硬证据:激光测距字段只存在于表2-2(吊舱→控制器); 控制器→吊舱方向的表2-1、表3 都没有任何距离输入字段。所以即使 stereo_ranger 算出了距离,也无法回灌给吊舱让它去解算表4——连补救的通道都没有。

唯一还能翻盘的途径:问厂商一句话——该型号无激光配置下表4 是否设计上就不输出、 是否支持外挂测距接入。拿到答复前一律按"表4 不存在"设计架构。


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 敲定值)

cd utils
# 默认值已按 §7 设定,不必再写 lat/lon/rel-alt/rate
python -m pod_bridge.sim_fc /dev/ttyAMA0 --seconds 60 \
    --json ~/pod_logs/phase1.json

# 台架上验证目标识别 + 自动锁定同时进行解算观测
python -m pod_bridge.sim_fc /dev/ttyAMA0 \
    --detect person --auto-lock --seconds 60 \
    --json ~/pod_logs/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 上机就跑(用户已敲定的默认值)

cd utils
mkdir -p ~/pod_logs
python -m pod_bridge.sim_fc /dev/ttyAMA0 \
    --detect person --auto-lock --seconds 60 \
    --json ~/pod_logs/phase1.json

⚠ --json 后面的路径是「输出产物」,不是预置输入文件——仓库里没有、 也不该有 phase1.json,它只有在脚本跑完后才会被写出来。用相对路径会落在 当前工作目录(utils/),建议直接给绝对路径(如上 ~/pod_logs/), 免得跑完找不到。

要在台架上跑阶段2 的高度扫描时,改 --rel-alt 重跑即可 (30 / 50 / 80 / 120 各一次,--json 分别落盘后做聚类对比)。

8.1.1 --json 落盘内容(跑完会得到什么)

顶层三个键:

键 内容
args 本次运行的全部命令行参数(复现用)
summary 汇总:帧计数/频率、首次锁定与首次表4 时刻、经纬度均值与 σ、相对我方的 east/north 偏移与散布、表4 高度 vs 喂入高度
samples 逐帧表4 原始采样:{t_s, lat, lon, abs_alt_m, rel_alt_m, east_m, north_m, locked}

实测拿到后,把这份 JSON 和终端汇总一起回传即可做阶段2 的聚类/散布分析。 注意:samples 为空即代表本次没收任何表4(对应退出码 1)。

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 往下调