实现预定流程全链路: 任务状态机 + 编排器 + 地面站 + 真机/SITL入口
- utils/task_runner: 6态状态机(WAIT→ARM_TAKEOFF→GOTO(L2)→TRACK(L1)→APPROACH→RTL) + 编排器(快照采集/状态动作) + 双层日志(事件JSONL/遥测CSV) + 吊舱控制指令(0x07/0x0F/0x0E/0x08) - pod_bridge: ground_link 地面坐标UDP注入(超龄判无效,无激光替代表4); set_heading_servo_enabled() 运行时L1/L2切换(切换即reset清积分防输出跳变); pod_locked 提为公共门控; config 新增 ground_inject 段 - virtual_pod 模拟吊舱(无电机语义) + verify_px4 增量④仅飞控验证 + pod_sitl_test L1 SITL全流程(飞行编排不塞进 run_bridge, 保持纯offboard闭环入口) - 入口: task_runner_sitl(VirtualPod+虚拟目标, SITL端到端跑通约1m24s) / task_runner_real(真串口+UDP注入+吊舱控制) - 地面站 ground_station: 坐标注入 + 状态可视化(Flask) - S1标定回填(2026-10-09): beta_sign=+1 / alpha_sign=-1, 加标定回归测试守护 - 新增测试: task_runner x4 + ground_link/virtual_pod*/bridge/px4_gateway 增量; 全量 483 passed - 文档: 预定流程(需求) + 预定流程-操作指南(操作/待办) + L1方案补S1结论与实现状态
This commit is contained in:
@@ -13,6 +13,7 @@ import pytest
|
||||
|
||||
from pod_bridge.bridge import PodBridge
|
||||
from pod_bridge.config import BridgeConfig, load_from_json
|
||||
from pod_bridge.ground_link import GroundTarget
|
||||
from pod_bridge.protocol import (ControlCode, ControlCommand, TargetGeo,
|
||||
Telemetry, decode_attitude, decode_control)
|
||||
from pod_bridge.px4_gateway import OwnState
|
||||
@@ -523,3 +524,75 @@ def test_l1_require_vision_passes_with_source():
|
||||
assert out.mode.name == "VELOCITY_HEADING"
|
||||
assert out.yaw_rate_dps == pytest.approx(10.0, abs=1e-9)
|
||||
|
||||
|
||||
# --- 地面坐标注入(A2)-------------------------------------------------------
|
||||
def _inject_config(timeout_ms=5000):
|
||||
cfg = BridgeConfig()
|
||||
cfg.ground_inject.enabled = True
|
||||
cfg.ground_inject.timeout_ms = timeout_ms
|
||||
return cfg
|
||||
|
||||
|
||||
def test_ground_inject_priority_over_table4():
|
||||
"""注入坐标优先于表4:吊舱无表4/无锁定(本设备常态),注入新鲜坐标照常引导。"""
|
||||
px4 = FakePx4(_valid_state(1000))
|
||||
pod = FakePod()
|
||||
injected = GroundTarget(latitude_deg=0.001, longitude_deg=0.0, time_ms=1000)
|
||||
b = PodBridge(_inject_config(), px4, pod, now_ms_fn=lambda: 0,
|
||||
ground_target_fn=lambda: injected)
|
||||
out = asyncio.run(b.step(1000))
|
||||
assert out.mode.name == "VELOCITY_HEADING"
|
||||
assert len(px4.velocity_calls) == 1
|
||||
|
||||
|
||||
def test_ground_inject_stale_falls_back_to_brake():
|
||||
"""注入坐标超龄 → 视为无目标 → 刹车(不得朝过期坐标飞)。"""
|
||||
px4 = FakePx4(_valid_state(2000))
|
||||
pod = FakePod()
|
||||
injected = GroundTarget(latitude_deg=0.001, longitude_deg=0.0,
|
||||
time_ms=1000) # 1000ms 前注入
|
||||
cfg = _inject_config(timeout_ms=500)
|
||||
b = PodBridge(cfg, px4, pod, now_ms_fn=lambda: 0,
|
||||
ground_target_fn=lambda: injected)
|
||||
out = asyncio.run(b.step(2000)) # 差值 1000ms > 500ms → 超龄
|
||||
assert out.mode.name == "BRAKE_HOVER"
|
||||
assert px4.velocity_calls == []
|
||||
|
||||
|
||||
def test_ground_inject_disabled_ignores_injection():
|
||||
"""未启用地面注入 → 忽略注入坐标,回退表4 原路径(无表4 → 刹车)。"""
|
||||
px4 = FakePx4(_valid_state(1000))
|
||||
pod = FakePod()
|
||||
injected = GroundTarget(latitude_deg=0.001, longitude_deg=0.0, time_ms=1000)
|
||||
cfg = BridgeConfig() # ground_inject.enabled=False
|
||||
b = PodBridge(cfg, px4, pod, now_ms_fn=lambda: 0,
|
||||
ground_target_fn=lambda: injected)
|
||||
out = asyncio.run(b.step(1000))
|
||||
assert out.mode.name == "BRAKE_HOVER"
|
||||
|
||||
|
||||
# --- 运行时切换控制律(A3:GOTO(L2) ↔ TRACK(L1))-----------------------------
|
||||
def test_set_heading_servo_enabled_runtime_switch():
|
||||
"""运行时切换控制律:初始 L1 → 切 L2(刹车)→ 切回 L1(速度)。"""
|
||||
px4 = FakePx4(_valid_state(1000))
|
||||
pod = FakePod()
|
||||
b = _bridge(px4, pod, _l1_config()) # enabled=True → 初始 L1
|
||||
|
||||
# 初始 L1:锁定 + 脱靶量 → 速度
|
||||
pod.queue(_locked_off_target(x=10.0))
|
||||
out = asyncio.run(b.step(1000))
|
||||
assert out.mode.name == "VELOCITY_HEADING"
|
||||
assert out.yaw_rate_dps == pytest.approx(10.0, abs=1e-9)
|
||||
|
||||
# 切 L2:无表4/无锁定 → 刹车
|
||||
b.set_heading_servo_enabled(False)
|
||||
out = asyncio.run(b.step(1100))
|
||||
assert out.mode.name == "BRAKE_HOVER"
|
||||
|
||||
# 切回 L1:重新喂锁定脱靶量 → 速度
|
||||
b.set_heading_servo_enabled(True)
|
||||
pod.queue(_locked_off_target(x=10.0))
|
||||
out = asyncio.run(b.step(1200))
|
||||
assert out.mode.name == "VELOCITY_HEADING"
|
||||
assert out.yaw_rate_dps == pytest.approx(10.0, abs=1e-9)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user