Files
gps-denied-onboard/README.md
T
Yuzviak 1273ec8eaf docs: rewrite README for Stage 2 Phase 2 (hexagonal arch + AC + structlog)
- 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>
2026-05-11 19:06:54 +03:00

326 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GPS-Denied Onboard
Бортова система GPS-denied навігації для фіксованого крила БПЛА на Jetson Orin Nano Super.
Замінює GPS-сигнал власною оцінкою позиції на основі відеопотоку (cuVSLAM), IMU та супутникових знімків. Позиція подається у польотний контролер ArduPilot у форматі `GPS_INPUT` через MAVLink при 510 Гц.
**Гілка розробки:** `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)
### Встановлення
```bash
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]"
```
### Запуск
```bash
# Прямий запуск (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
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`.
**Ключові змінні середовища:**
```env
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) |
---
## Тести
```bash
# Всі тести (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_0105, 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
```bash
# Звіт: 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 00010004
├── 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](_docs/01_solution/decisions/0001-e2e-dataset-strategy.md) | E2E dataset strategy (EuRoC / VPAIR / MARS-LVIG) |
| [0002](_docs/01_solution/decisions/0002-hexagonal-architecture-stage2.md) | Hexagonal / ports-and-adapters для Stage 2 |
| [0003](_docs/01_solution/decisions/0003-hot-path-dataclasses-vs-pydantic.md) | `@dataclass(slots=True, frozen=True)` на hot path; Pydantic лише на межах |
| [0004](_docs/01_solution/decisions/0004-stage2-as-independent-iteration.md) | 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
```bash
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 м |
---
## Ліцензія
Приватний репозиторій. Усі права захищено.