精简仓库结构并增强任务链路:归档双目可选后端、新增LOS判据与自定义返航

归档 legacy/:stereo_yolo/stereo_ranger/target_tracker 三包 + 175 条测试 + demo/配置/权重/三份规格;运行链路不再依赖,默认 pytest 446 条,归档测试按需运行。

目标有效性第二判据改为 LOS 运动学一致性(pod_bridge/los_consistency.py),删除 require_vision;pod_locked() 叠加该判据,状态机随之后退重搜。

自定义返航(task_runner/return_home.py):保持转场高度回起飞点 → 恢复初始航向 → 垂直降落,不再爬 RTL_RETURN_ALT;未起飞/无起飞点/超时/异常仍退回 PX4 原生 RTL。

任务链安全兜底:主循环异常→紧急返航、stop 幂等、landing_stop 保留遥测、in_air 流复用+超时、解锁/起飞与吊舱识别仅执行一次、rtl_timeout 上限。

入口共享件拆到 utils/sitl_support.py;pod_sitl_test.py→l1_sitl_test.py;删除旧架构 test.py;相机脚本移入 tools/。

新增 .gitignore 并剔除历史误跟踪的 59 个 .pyc;requirements 按实际依赖重写;文档全面对齐,新增 legacy/README.md 与 tools/README.md。
This commit is contained in:
keita
2026-10-21 14:48:41 +08:00
parent 53fdd3b13f
commit df91b1110e
159 changed files with 6121 additions and 1617 deletions
+42 -15
View File
@@ -1,19 +1,25 @@
#!/usr/bin/env python3
"""task_runner_sitl.py —— 任务级状态机 SITL 全流程集成测试(预定流程.md 天空侧)。
用状态机驱动完整流程:WAIT → ARM_TAKEOFF → GOTO(L2 坐标引导) → TRACK(L1 航向伺服)
→ APPROACH(悬停) → RTL(返航落地)。相比 pod_sitl_test.py(固定场景、人工编排),
本脚本把「飞行编排 + 控制律切换」交给 TaskRunner 状态机统一驱动。
用状态机驱动完整流程:
WAIT → ARM_TAKEOFF → GOTO(L2 飞向参考点) → SEARCH(原地扫视) → TRACK(L1 缓慢靠近)
→ RTL(自定义返航:回起飞点 → 恢复初始航向 → 垂直降落)
相比 `l1_sitl_test.py`(只跑 L1 控制律闭环、人工编排),本脚本把「飞行编排 + 控制律切换」
交给 TaskRunner 状态机统一驱动;返航也不再走 PX4 原生 RTL(那会先爬到
RTL_RETURN_ALT 再降),而是自定义的低空返航(见 task_runner/return_home.py)。
场景:
* 起飞前注入虚拟目标坐标(模拟地面站给坐标)——目标 = home 北 30m / 东 15m;
* VirtualPod 按无电机语义合成表2-2(框架角恒0、无激光、锁定=目标在视场内);
* 后台任务持续刷新注入坐标(模拟地面站持续上报),坐标始终新鲜;
* GOTO→TRACK 由状态机按「距目标 < switch_radius 且吊舱锁定」守卫切换。
* GOTO→SEARCH 按「距参考点 ≤ arrival_radius」切换;SEARCH→TRACK 按「目标锁定」
切换(含 LOS 运动学一致性判据,见 pod_bridge/los_consistency.py)。
运行前:启动 PX4 SITL(默认本机 14540)。用法:
python task_runner_sitl.py
python task_runner_sitl.py --conn udp://:14540 --config utils/config/pod_bridge.json
python task_runner_sitl.py --conn udpin://0.0.0.0:14540 \
--config utils/config/pod_bridge.json
"""
import argparse
@@ -40,9 +46,10 @@ from pod_bridge.px4_gateway import Px4Gateway
from pod_bridge.virtual_pod import (CarrierView, VirtualPod, VirtualTarget,
VirtualTargetConfig)
from task_runner.log import TaskLogConfig, TaskLogger
from task_runner.return_home import ReturnConfig
from task_runner.runner import TaskRunner
from task_runner.state_machine import TaskStateConfig, TaskStateMachine
from pod_sitl_test import (TelemetryHub, now_ms, print_flight_mode,
from sitl_support import (TelemetryHub, now_ms, print_flight_mode,
set_telemetry_rates, wait_until_ready)
logger = logging.getLogger("task_sitl")
@@ -83,16 +90,35 @@ def build_sm_config() -> TaskStateConfig:
"""SITL 状态机参数(占位值,仅用于仿真验证链路)。"""
return TaskStateConfig(
takeoff_altitude_m=5.0,
switch_radius_m=25.0, # 目标在 30m,飞进 25m 内切 TRACK
arrival_radius_m=25.0, # 目标在 30m,飞进 25m 内转搜索
goto_timeout_s=60.0,
track_timeout_s=60.0,
center_hold_s=3.0,
center_threshold_deg=3.0, # 脱靶量 < 3° 判居中
approach_hover_s=5.0, # 悬停 5s 后 RTL
search_timeout_s=20.0, # 搜索 20s 未发现目标 → 返航
track_timeout_s=20.0, # 追踪/靠近段上限 → 返航
center_threshold_deg=3.0, # 脱靶量 < 3° 判居中(仅日志/诊断)
sweep_rate_dps=15.0, # 搜索扫视角速度(度/秒)
sweep_reverse_s=4.0, # 每 4s 换向
loop_frequency_hz=20.0,
)
def build_return_config() -> ReturnConfig:
"""SITL 自定义返航参数(与真机同口径,仅用于仿真验证)。"""
return ReturnConfig(
enabled=True,
speed_mps=2.0,
arrival_radius_m=3.0,
timeout_s=120.0,
yaw_kp=1.0,
yaw_rate_limit_dps=20.0,
align_kp=1.0,
align_rate_limit_dps=15.0,
yaw_align_tolerance_deg=5.0,
yaw_align_timeout_s=15.0,
alt_kp=0.5,
alt_limit_mps=0.5,
)
async def inject_loop(ground: GroundLink, lat: float, lon: float,
stop: asyncio.Event) -> None:
"""后台持续刷新注入坐标(模拟地面站持续上报,保坐标始终新鲜)。"""
@@ -111,6 +137,7 @@ async def run(args) -> int:
cfg.ground_inject.enabled = True # 启用地面注入(骨架期 JSON 默认关)
cfg.guidance.target_stale_ms = 5000 # 注入坐标超时放宽(表4 默认 1000 对低频注入太紧)
sm_cfg = build_sm_config()
return_cfg = build_return_config()
drone = System()
await drone.connect(system_address=args.conn)
@@ -146,7 +173,7 @@ async def run(args) -> int:
sm = TaskStateMachine(sm_cfg)
task_log = TaskLogger(TaskLogConfig(), now_ms_fn=now_ms)
runner = TaskRunner(cfg, sm_cfg, px4, bridge, sm, ground, drone,
now_ms_fn=now_ms, logger=task_log)
now_ms_fn=now_ms, logger=task_log, return_cfg=return_cfg)
stop = asyncio.Event()
if args.ground_station:
@@ -160,9 +187,9 @@ async def run(args) -> int:
if args.ground_station:
print(f" [地面站模式] 等待 UDP {cfg.ground_inject.port} 注入坐标…")
print(f" 状态机: takeoff={sm_cfg.takeoff_altitude_m}m "
f"switch_radius={sm_cfg.switch_radius_m}m "
f"center={sm_cfg.center_threshold_deg}°/{sm_cfg.center_hold_s}s "
f"hover={sm_cfg.approach_hover_s}s")
f"arrival_radius={sm_cfg.arrival_radius_m}m "
f"search={sm_cfg.search_timeout_s}s@{sm_cfg.sweep_rate_dps}°/s "
f"track_max={sm_cfg.track_timeout_s}s")
# 状态打印 + 总超时保护
started = time.monotonic()