- Reflects Stage 2 hexagonal layout: components/, hot_types/, obs/, pipeline/ - Updated architecture diagram with ports-and-adapters structure - Correct test count: 236 passed, 8 skipped (was: 216 from Stage 1) - New sections: AC traceability, taxonomy markers, per-env config, Roadmap table - ADR links to 0001-0004 - Component table with Protocol / dev / Jetson adapter columns - Removed stale Stage 1 next steps and sprint backlog Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
14 KiB
GPS-Denied Onboard
Бортова система GPS-denied навігації для фіксованого крила БПЛА на Jetson Orin Nano Super.
Замінює GPS-сигнал власною оцінкою позиції на основі відеопотоку (cuVSLAM), IMU та супутникових знімків. Позиція подається у польотний контролер ArduPilot у форматі GPS_INPUT через MAVLink при 5–10 Гц.
Гілка розробки: stage2 | Фаза: 2/6 (завершено) | Тести: 236 passed, 8 skipped
Архітектура (Stage 2 — Hexagonal / Ports-and-Adapters)
IMU (MAVLink RAW_IMU) ──────────────────────────────────────────▶ ESKF.predict()
│
ADTI 20L V1 ──▶ pipeline/image_input ──▶ ImageRotationManager │
│ │
┌───────────────┼───────────────┐ │
▼ ▼ ▼ │
components/vio components/gpr components/ │
cuVSLAM (Jetson) Faiss+DINOv2 satellite_ │
ORB-SLAM3 (dev) numpy (dev) matcher │
│ │ │ │
▼ ▼ ▼ │
ESKF.update_vo() GPR norm MetricRefinement│
│ (XFeat TRT) │
└──────────────────────▶ ESKF.update_sat()│
│
ESKF state ◀──┘
│
┌───────────────┼──────────────┐
▼ ▼ ▼
components/mavlink_io core/factor_graph SSE stream
GPS_INPUT 5-10Hz (GTSAM ISAM2) → ground station
→ ArduPilot FC
Стейт-машина (FlightProcessor.process_frame):
NORMAL ──(VO fail)──▶ LOST ──▶ RECOVERY ──(GPR+Metric ok)──▶ NORMAL
Правило залежностей: pipeline/orchestrator.py імпортує лише Protocols. Тільки pipeline/composition.py (build_pipeline(env)) знає про конкретні адаптери.
Стек
| Підсистема | Dev / CI | Jetson (production) |
|---|---|---|
| Visual Odometry | ORBVisualOdometry / CuVSLAMMonoDepthVO |
CuVSLAMMonoDepthVO (PyCuVSLAM v15, barometer як synthetic depth) |
| AI Inference | MockInferenceEngine |
TRTInferenceEngine (TensorRT FP16) |
| Place Recognition | numpy L2 fallback | Faiss GPU + DINOv2-VLAD TRT FP16 |
| MAVLink | MockMAVConnection |
pymavlink over UART |
| ESKF | numpy 15-state | numpy 15-state |
| Factor Graph | stub | GTSAM 4.3 ISAM2 |
| Логування | structlog ConsoleRenderer | structlog JSON (orjson) |
| API | FastAPI + Pydantic v2 + SSE | FastAPI + Pydantic v2 + SSE |
| БД | SQLite + SQLAlchemy 2 async | SQLite |
Швидкий старт
Вимоги
- Python ≥ 3.11
- ~500 MB дискового простору (GTSAM wheel)
Встановлення
git clone https://github.com/azaion/gps-denied-onboard.git
cd gps-denied-onboard
git checkout stage2
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
Запуск
# Прямий запуск (env=x86_dev за замовчуванням)
ENV=x86_dev uvicorn gps_denied.app:app --host 0.0.0.0 --port 8000
# Docker
docker compose up --build
Сервер: http://127.0.0.1:8000
Конфігурація
Вибір середовища задається змінною ENV:
ENV=x86_dev # за замовчуванням — ORB-SLAM3, моки, консольні логи
ENV=jetson # cuVSLAM + TRT + pymavlink + JSON logs
ENV=ci # усі моки, без hardware
ENV=sitl # ArduPilot SITL
Кожне середовище має overlay у config/{env}.yaml. Усі параметри — у src/gps_denied/config.py.
Ключові змінні середовища:
DB_URL=sqlite+aiosqlite:///./flight_data.db
SATELLITE_TILE_DIR=.satellite_tiles
MAVLINK_CONNECTION=serial:/dev/ttyTHS1:57600 # або tcp:host:port
MAVLINK_OUTPUT_HZ=5.0
ESKF_VO_POSITION_NOISE=0.3
ESKF_SATELLITE_MAX_AGE=30.0
MODEL_WEIGHTS_DIR=weights
API
| Endpoint | Метод | Опис |
|---|---|---|
/health |
GET | Health check |
/flights |
POST | Створити політ |
/flights/{id} |
GET | Деталі польоту |
/flights/{flight_id} |
DELETE | Видалити політ |
/flights/{flight_id}/images/batch |
POST | Батч зображень → обробка |
/flights/{flight_id}/user-fix |
POST | GPS-якір від оператора → ESKF update |
/flights/{flight_id}/status |
GET | Статус обробки |
/flights/{flight_id}/stream |
GET | SSE стрім (позиція + confidence) |
/flights/{flight_id}/frames/{frame_id}/object-to-gps |
POST | Pixel → GPS (ray-ground) |
Тести
# Всі тести (236 passed, 8 skipped)
python -m pytest tests/ -q --ignore=tests/e2e
# За категорією (taxonomy маркери)
python -m pytest -m unit -q
python -m pytest -m integration -q
python -m pytest -m blackbox -q
# Тести прив'язані до Acceptance Criteria
python -m pytest -m ac -q # тільки ac-marked тести
python -m pytest -m ac --ac-dump # + таблиця покриття AC
# SITL (потребує ArduPilot SITL)
docker compose -f docker-compose.sitl.yml up -d
pytest -m sitl tests/ -v
# E2E на публічних UAV-датасетах
pytest tests/e2e/ -q # skip якщо датасет відсутній
pytest tests/e2e/ -m "e2e and not e2e_slow" -v # CI-tier
pytest tests/e2e/ -m e2e_slow -v # nightly-tier (VPAIR, MARS-LVIG)
E2E результати (EuRoC MH_01–05, indoor):
| Датасет | Кадри | ESKF ATE RMSE | Статус |
|---|---|---|---|
| EuRoC MH_01 (easy) | 100 | 0.205 м | PASS |
| EuRoC MH_02 (easy) | 100 | 0.131 м | PASS |
| EuRoC MH_03 (medium) | 100 | 0.008 м | PASS |
| EuRoC MH_04 (difficult) | 100 | 0.009 м | PASS |
| EuRoC MH_05 (difficult) | 100 | 0.007 м | PASS |
| VPAIR sample (fixed-wing) | 200 | — | xfail (немає raw IMU) |
AC Traceability
# Звіт: 39 AC total — 14 covered, 21 pending-phase-3+, 4 deferred-hardware
python scripts/gen_ac_traceability.py
# CI gate (виходить 0 якщо всі непокриті позначені pending/deferred)
python scripts/gen_ac_traceability.py --check
Матриця: .planning/AC-TRACEABILITY.md
Структура проєкту
gps-denied-onboard/
├── src/gps_denied/
│ ├── app.py # FastAPI factory + lifespan
│ ├── config.py # AppSettings / RuntimeConfig (pydantic-settings)
│ ├── components/ # Hexagonal adapters (ports + impls)
│ │ ├── vio/ # Visual Odometry
│ │ ├── satellite_matcher/ # Tile loading + XFeat metric refinement
│ │ ├── gpr/ # Global Place Recognition (Faiss/numpy)
│ │ ├── mavlink_io/ # MAVLink bridge + mock
│ │ ├── anchor_verifier/ # stub — Phase 3
│ │ ├── safety_state/ # stub — Phase 3
│ │ ├── flight_recorder/ # stub — Phase 4
│ │ └── coordinate_transforms/ # stub — Phase 5
│ ├── core/ # Concentrated math (без DI)
│ │ ├── eskf.py # 15-state ESKF (IMU+VO+satellite fusion)
│ │ ├── factor_graph.py # FactorGraphOptimizer (GTSAM ISAM2)
│ │ ├── coordinates.py # ENU↔GPS↔pixel transforms
│ │ ├── chunk_manager.py # RouteChunkManager
│ │ ├── recovery.py # FailureRecoveryCoordinator
│ │ ├── rotation.py # ImageRotationManager
│ │ └── models.py # ModelManager + TRTInferenceEngine
│ ├── hot_types/ # @dataclass(slots=True, frozen=True) — hot path
│ ├── obs/ # Observability (Phase 2)
│ │ ├── logging_config.py # configure_logging(env) — JSON/console
│ │ └── log_schemas.py # Pydantic v2 boundary log event schemas
│ ├── pipeline/ # Orchestration layer
│ │ ├── orchestrator.py # FlightProcessor + process_frame
│ │ ├── composition.py # build_pipeline(env) — composition root
│ │ ├── image_input.py
│ │ ├── result_manager.py
│ │ └── sse_streamer.py
│ ├── api/routers/flights.py # REST endpoints + SSE
│ ├── schemas/ # Pydantic REST schemas + shims до hot_types
│ └── db/ # SQLAlchemy ORM + async repository
├── tests/ # 37 test-модулів з pytestmark (236 passed)
│ └── e2e/ # E2E на публічних UAV-датасетах
├── config/ # Per-env YAML overlays
│ ├── jetson.yaml
│ ├── x86_dev.yaml
│ ├── ci.yaml
│ └── sitl.yaml
├── scripts/
│ ├── gen_ac_traceability.py # AC coverage report + CI gate
│ └── benchmark_accuracy.py # Synthetic trajectory accuracy CLI
├── _docs/
│ ├── 00_problem/
│ │ └── acceptance_criteria.md # 39 AC (AC-1.1..AC-8.6 + AC-NEW-1..8)
│ └── 01_solution/decisions/ # ADRs 0001–0004
├── Dockerfile
├── docker-compose.yml
├── docker-compose.sitl.yml
└── .github/workflows/
├── ci.yml # lint → {unit, integration, blackbox, ac-gate} → docker
├── nightly.yml # sitl + e2e slow (cron 03:00 UTC)
└── sitl.yml # SITL integration
Компоненти (Stage 2)
| Компонент | Protocol | Dev adapter | Jetson adapter |
|---|---|---|---|
| Visual Odometry | vio/protocol.py |
ORBVisualOdometry |
CuVSLAMMonoDepthVO |
| Satellite Matcher | satellite_matcher/protocol.py |
LocalTileLoader + MetricRefinement |
те саме |
| Place Recognition | gpr/protocol.py |
FaissGPR (numpy fallback) |
FaissGPR (GPU) |
| MAVLink I/O | mavlink_io/protocol.py |
MockMAVConnection |
MAVLinkBridge (pymavlink) |
| Anchor Verifier | anchor_verifier/protocol.py |
stub | stub (Phase 3) |
| Safety State | safety_state/protocol.py |
stub | stub (Phase 3) |
| Flight Recorder | flight_recorder/protocol.py |
stub | stub (Phase 4) |
| Coord. Transforms | coordinate_transforms/protocol.py |
stub | stub (Phase 5) |
Acceptance Criteria
39 AC з ідентифікаторами AC-N.M / AC-NEW-N у _docs/00_problem/acceptance_criteria.md.
Ключові порогові значення:
| Критерій | Поріг |
|---|---|
| Точність 80% кадрів | ≤ 50 м |
| Точність 60% кадрів | ≤ 20 м |
| End-to-end latency | < 400 мс |
| Пам'ять (shared CPU/GPU) | < 8 GB |
| Сторадж місії | < 64 GB |
| GSD супутникового знімку | ≤ 0.5 м/px |
| Час до першої позиції | ≤ 30 с |
Архітектурні рішення (ADRs)
| ADR | Рішення |
|---|---|
| 0001 | E2E dataset strategy (EuRoC / VPAIR / MARS-LVIG) |
| 0002 | Hexagonal / ports-and-adapters для Stage 2 |
| 0003 | @dataclass(slots=True, frozen=True) на hot path; Pydantic лише на межах |
| 0004 | Stage 2 як незалежна ітерація зі своїми фазами 1–6 |
Roadmap Stage 2
| Фаза | Назва | Статус |
|---|---|---|
| 1 | Hexagonal Refactor | done (2026-05-11) |
| 2 | AC Doc + Test Taxonomy + Observability Spine | done (2026-05-11) |
| 3 | Safety Anchor State Machine + Geometry-Gated Anchor Verifier | planned |
| 4 | Conditional VPR + Flight Data Recorder | planned |
| 5 | MAVLink source labels + dual-channel scaffold | planned |
| 6 | Azaion 10.05.2026 real-flight integration fixture | planned |
Benchmark
python scripts/benchmark_accuracy.py --frames 50
Результати на синтетичній траєкторії (20 м/с, 0.7 fps, шум VO 0.3 м, супутник кожні 5 кадрів):
| Критерій | Результат | Ліміт |
|---|---|---|
| 80% кадрів ≤ 50 м | 100% | ≥ 80% |
| 60% кадрів ≤ 20 м | 100% | ≥ 60% |
| p95 latency | ~9 мс | < 400 мс |
| VO дрейф за 1 км | ~11 м | < 100 м |
Ліцензія
Приватний репозиторій. Усі права захищено.