实现L1航向伺服(路线A1): 吊舱角度直入控制律 + 桥接胶水 + 干跑入口

- visual_track_control: ControlSnapshot 加可选 los_beta_deg/los_alpha_deg,
  第④步角度直入优先否则回退像素路径(向后兼容); config 拆出 load_from_dict 供内嵌复用
- pod_bridge: BridgeConfig 内嵌 TrackingConfig + 新 HeadingServoConfig;
  bridge._build_snapshot 合成 β/α(姿态来自PX4/角度来自吊舱,信号双路适配无电机),
  step 按 heading_servo.enabled 切 L1/L2, DEGRADED_HOLD 补发刹车保 offboard 心跳;
  run() 循环频率按生效律取(修 L1 频率错配)
- run_bridge.py: 闭环 CLI 入口, --dry-run(S2 只接吊舱不发offboard) 与实飞优雅停机
- 配置落 L1 首飞值(slow_approach/0.2mps、垂直压低、无电机仅用脱靶量)
- 新增测试 29 条(vtc5+config9+bridgeL1 10+runner5); 全量 401 passed
This commit is contained in:
keita
2026-10-09 12:52:14 +08:00
parent b147925d58
commit 5a944af4f5
12 changed files with 827 additions and 43 deletions
+55 -18
View File
@@ -2,7 +2,7 @@
> 目标:**先把"只修正飞行方向、不需要距离"的部分落地**,速度-距离闭环(L2)滞后。
> 基于结论:本吊舱无激光 → 表4 永不出,且不允许二次开发(部署文档 §10.0 / §10)。
> 日期:2026-10-09 状态:**方案待确认,未动代码**
> 日期:2026-10-09 状态:**S0 已实现(路线 A1 + L1 胶水层),全量 396 passed;符号待台架 S1 标定**
---
@@ -68,6 +68,16 @@ distance_available = snapshot.distance_valid and distance>0 and 未超龄
**额外好处**:走角度输入就**与视场 FOV 无关**——吊舱会变焦,若走像素换算
(`PixelToAngle` 反推),变焦后 FOV 变化会直接失真。这是不用像素路径的硬理由。
> ⚠ **本版吊舱未装电机(决策 2,2026-10-09)**:云台刚性固连机体、朝前不动,
> `frame_yaw_deg` 恒 0("框架角无用"),锁定后也**不会**自 servo 回中,故脱靶量
> 长期**不**≈0。此时 `off_target_x_deg` 恰好 = 目标相对机头的视线角 β,是**唯一**
> 有效信号。因此合成做成**配置双路**(`beta_from_frame` / `beta_from_off_target`):
> - 现在(无电机):`beta_from_frame=false`、`beta_from_off_target=true` → β = off_target_x
> - 将来(装电机):两者皆 `true` → β = frame_yaw + off_target_x(即上文 §2.1)
>
> "装不装电机"只是翻配置,架构(姿态来自 PX4、角度来自吊舱)不变。**装电机后 §2.1
> 的"脱靶量长期≈0、必须靠框架角"警告才重新生效。**
### 2.2 如何接入 TrackingControlLaw(三选一,需拍板)
控制律第 ④ 步现在是**硬编码从像素算角度**,没有"直接喂角度"的入口:
@@ -105,6 +115,8 @@ L1 的 `tracked` 判据按阶段递进:
## 3. 改动清单(文件级)
> 计划视图。**实际落地状态与逐文件勾选见 §9**(含计划外新增的 `config.py:load_from_dict`)。
| # | 文件 | 改动 |
|---|---|---|
| 1 | `utils/visual_track_control/types.py` | `ControlSnapshot` 加 `los_beta_deg` / `los_alpha_deg: Optional[float] = None`(路线 A1) |
@@ -122,27 +134,31 @@ L1 的 `tracked` 判据按阶段递进:
## 4. 配置外置(要点,非最终字段名)
```jsonc
// utils/config/pod_bridge.json —— L1 段
// utils/config/pod_bridge.json —— L1 段(已实现,字段名与 HeadingServoConfig 一致)
"heading_servo": {
"enabled": true,
"beta_sign": 1, // 台架标定后填 ±1(框架角 + 脱靶量合成的符号)
"enabled": true, // true=走 L1(TrackingControlLaw),false=L2(GuidanceLaw)
"beta_sign": 1, // 台架 S1 标定后填 ±1(符号错了会反向转)
"alpha_sign": 1,
"use_off_target": true, // β 是否叠加脱靶量(false = 只用框架角)
"vertical_enabled": false, // L1 先禁垂直,避免撞地;vz=0
"require_vision": false // true = 需我方视觉作为第二判据(S3 起开)
"beta_from_frame": false, // 无电机 → 框架角无用;装电机后置 true
"beta_from_off_target": true,// 无电机 → 脱靶量即 β 主信号
"alpha_from_frame": false,
"alpha_from_off_target": true,
"vertical_enabled": true, // 决策3:无人机 L1 开垂直(vz 由 tracking.vertical 压低)
"require_vision": false // true = 需我方视觉第二判据(S3 起开,§2.3)
}
```
```jsonc
// visual_track_control 侧(沿用既有结构,只改值)
"heading": { "mode": "rate", "gain": ..., "yaw_rate_limit_dps": 20.0, "yaw_slew_limit_dps2": 40.0 },
"distance": { "no_distance_action": "hold", // 首次上机 = hold
"no_distance_approach_limit_mps": 0.3 },
"vertical": { "vz_gain_mps_per_deg": 0.0 } // L1 禁用
// tracking 段(内嵌整份 visual_track_control 配置,各分组须齐全;L1 首飞值)
"heading": { "mode": "rate", "gain": 1.0, "yaw_rate_limit_dps": 20.0, "yaw_slew_limit_dps2": 40.0 },
"distance": { "no_distance_action": "slow_approach", // 决策3:低速接近(非 hold)
"no_distance_approach_limit_mps": 0.2 }, // 极低限速起步
"vertical": { "vz_gain_mps_per_deg": 0.05, "vz_limit_mps": 0.5 } // 开垂直但压低
```
> 首次上机一律 `no_distance_action = "hold"`:**只转向、不前近**。
> 航向验证通过后再切 `slow_approach` 并把限速压到 0.3 m/s 起步。
> 决策 3:首次上机 `no_distance_action = "slow_approach"`、限速 **0.2 m/s**(很低),
> 而非原推荐的 `hold`。安全不受影响——**S2 干跑不下发 offboard**,符号先验证过再通电。
> `beta_sign`/`alpha_sign`/增益均为占位,须 S1/S2 标定后回填,标定前不得实飞。
---
@@ -185,8 +201,29 @@ L1 落地时就留好口子,将来 L2 只是"换个输入源",不动结构
---
## 8. 待你拍板
## 8. 已拍板(2026-10-09)
1. **接入路线**:A1(改 `ControlSnapshot` 加可选角度字段,推荐)/ A2(pod_bridge 侧薄封装)/ B(像素反推,不推荐);
2. **目标类型**:人车(地面)/ 无人机(空中)—— 影响 L2 的几何反推法能否用,L1 不受影响;
3. **首次上机无距离动作**:`hold`(只转不前,我建议)/ `slow_approach`(0.3 m/s)。
1. **接入路线 = A1**:`ControlSnapshot` 加可选 `los_beta_deg`/`los_alpha_deg`,第④步角度直入优先、否则回退像素路径。已实现(`types.py` / `tracking_control_law.py`,补 5 条兼容性测试)。
2. **目标类型 = 无人机(空中)**:影响 L2 几何反推法,L1 无关。
3. **首次上机 = `slow_approach` + 极低限速 0.2 m/s**(非 `hold`);**L1 开垂直通道**,但 `vz` 限速压低(`vz_limit_mps=0.5`、`vz_gain=0.05`)防撞。
补充决策(BridgeConfig 结构):**BridgeConfig 直接内嵌 `TrackingConfig` 字段 + `heading_servo` 段**;运行时按 `heading_servo.enabled` 在 L1(`TrackingControlLaw`)/L2(`GuidanceLaw`) 间切换,L2 接口天然保留(对齐 §7)。姿态来自 PX4、β/α 来自吊舱(决策 2 见 §2.1 无电机说明)。
---
## 9. 实现状态(S0 已完成)
| # | 文件 | 状态 |
|---|---|---|
| 1 | `visual_track_control/types.py` | ✅ `ControlSnapshot` 加 `los_beta_deg`/`los_alpha_deg: Optional[float]=None` |
| 2 | `visual_track_control/tracking_control_law.py` | ✅ 第④步角度直入优先,否则回退像素 |
| 3 | `visual_track_control/config.py` | ✅ 拆出 `load_from_dict()` 供 bridge 内嵌复用 |
| 4 | `pod_bridge/config.py` | ✅ 加 `HeadingServoConfig` + 内嵌 `TrackingConfig`,严格校验符号(±1) |
| 5 | `pod_bridge/bridge.py` | ✅ `_build_snapshot()`;`step()` 按 enabled 切 L1/L2;`_dispatch` 对 DEGRADED_HOLD 补发刹车保心跳;`vision_target_fn` 注入点(§2.3 第二判据) |
| 6 | `config/pod_bridge.json` | ✅ 落 L1 首飞值(slow_approach/0.2、垂直压低、无电机 → 仅脱靶量) |
| 7 | `tests/` | ✅ 新增 24 条(vtc 5 + config 9 + bridge L1 10);全量 **396 passed** |
| 8 | `pod_bridge/run_bridge.py` | ✅ 闭环 CLI 入口:`--dry-run`(S2 只接吊舱/不发 offboard/打印 β→yaw_rate) 与实飞(S3+,`PodBridge.connect`+信号优雅停机);配套 `run_bridge_test.py` 5 条,全量 **401 passed** |
| 9 | `pod_bridge/bridge.py:run()` | ✅ 循环周期改按生效律取频率(L1=tracking.control,L2=guidance),修 L1 live 频率错配 |
**待办(非代码,台架)**:S1 标定 `beta_sign`/`alpha_sign` 与框架角定义;S2 干跑
`run_bridge.py --dry-run` 验符号(目标偏左→yaw_rate 负、居中→趋 0),符号错就翻配置里的 sign。