实现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
+19 -11
View File
@@ -263,6 +263,24 @@ def _load_accel_limit(root: dict) -> AccelLimitConfig:
return a
def load_from_dict(root: dict) -> TrackingConfig:
"""从已解析的 JSON 对象构造 TrackingConfig(各分组必须齐全,缺字段/非法即抛)。
供 pod_bridge 内嵌复用:桥接配置把本律的整份配置作为一个子对象加载。
"""
if not isinstance(root, dict):
raise ValueError("配置解析失败: 根节点必须为对象")
cfg = TrackingConfig()
cfg.control = _load_control(root)
cfg.camera = _load_camera(root)
cfg.heading = _load_heading(root)
cfg.vertical = _load_vertical(root)
cfg.distance = _load_distance(root)
cfg.accel_limit = _load_accel_limit(root)
return cfg
def load_from_json(path: str) -> TrackingConfig:
"""从 JSON 文件一次性加载配置(启动期调用,调参重启生效)。"""
try:
@@ -273,14 +291,4 @@ def load_from_json(path: str) -> TrackingConfig:
except json.JSONDecodeError as exc:
raise ValueError(f"配置文件解析失败: {path} ({exc})") from None
if not isinstance(root, dict):
raise ValueError(f"配置文件解析失败: {path} (根节点必须为对象)")
cfg = TrackingConfig()
cfg.control = _load_control(root)
cfg.camera = _load_camera(root)
cfg.heading = _load_heading(root)
cfg.vertical = _load_vertical(root)
cfg.distance = _load_distance(root)
cfg.accel_limit = _load_accel_limit(root)
return cfg
return load_from_dict(root)