mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-23 04:26:36 +00:00
78dcf7b4e7
Phase A — Runtime bugs: - SSE: add push_event() method to SSEEventStreamer (was missing, masked by mocks) - MAVLink: satellites_visible=10 (was 0, triggers ArduPilot failsafe) - MAVLink: horiz_accuracy=sqrt(P[0,0]+P[1,1]) per spec (was sqrt(avg)) - MAVLink: MEDIUM confidence → fix_type=3 per solution.md (was 2) Phase B — Functional gaps: - handle_user_fix() injects operator GPS into ESKF with noise=500m - app.py uses create_vo_backend() factory (was hardcoded SequentialVO) - ESKF: Mahalanobis gating on satellite updates (rejects outliers >5σ) - ESKF: public accessors (position, quaternion, covariance, last_timestamp) - Processor: no more private ESKF field access Phase C — Documentation: - README: correct API endpoints, CLI command, 40+ env vars documented - Dockerfile: ENV prefixes match pydantic-settings (DB_, SATELLITE_, MAVLINK_) - tech_stack.md marked ARCHIVED (contradicts solution.md) Phase D — Hardening: - JWT auth middleware (AUTH_ENABLED=false default, verify_token on /flights) - TLS config env vars (AUTH_SSL_CERTFILE, AUTH_SSL_KEYFILE) - SHA-256 tile manifest verification in SatelliteDataManager - AuthConfig, ESKFSettings, MAVLinkConfig, SatelliteConfig in config.py Also: conftest.py shared fixtures, download_tiles.py, convert_to_trt.py scripts, config wiring into app.py lifespan, config-driven ESKF, calculate_precise_angle fix. Tests: 196 passed / 8 skipped. Ruff clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
72 lines
2.7 KiB
Python
72 lines
2.7 KiB
Python
"""Error-State Kalman Filter schemas."""
|
|
|
|
from enum import Enum
|
|
from typing import Optional
|
|
|
|
import numpy as np
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class ConfidenceTier(str, Enum):
|
|
"""ESKF confidence tier for GPS_INPUT fix_type mapping."""
|
|
HIGH = "HIGH" # Satellite match <30s ago, covariance < 400 m^2
|
|
MEDIUM = "MEDIUM" # VO tracking OK, no recent satellite match
|
|
LOW = "LOW" # IMU-only, cuVSLAM lost
|
|
FAILED = "FAILED" # 3+ consecutive total failures
|
|
|
|
|
|
class IMUMeasurement(BaseModel):
|
|
"""Single IMU reading from flight controller."""
|
|
model_config = {"arbitrary_types_allowed": True}
|
|
|
|
accel: np.ndarray # (3,) m/s^2 in body frame
|
|
gyro: np.ndarray # (3,) rad/s in body frame
|
|
timestamp: float # seconds since epoch
|
|
|
|
|
|
class ESKFConfig(BaseModel):
|
|
"""ESKF tuning parameters."""
|
|
|
|
# Process noise (from IMU datasheet — ICM-42688-P)
|
|
accel_noise_density: float = 6.86e-4 # 70 ug/sqrt(Hz) -> m/s^2/sqrt(Hz)
|
|
gyro_noise_density: float = 5.24e-5 # 3.0e-3 deg/s/sqrt(Hz) -> rad/s/sqrt(Hz)
|
|
accel_random_walk: float = 2.0e-3 # m/s^3/sqrt(Hz)
|
|
gyro_random_walk: float = 8.73e-7 # 5.0e-5 deg/s^2/sqrt(Hz) -> rad/s^2/sqrt(Hz)
|
|
|
|
# VO measurement noise
|
|
vo_position_noise: float = 0.3 # meters (cuVSLAM at 600m altitude)
|
|
|
|
# Satellite measurement noise range
|
|
sat_noise_min: float = 5.0 # meters (high-confidence RANSAC)
|
|
sat_noise_max: float = 20.0 # meters (low-confidence RANSAC)
|
|
|
|
# Confidence tier thresholds
|
|
satellite_max_age: float = 30.0 # seconds
|
|
covariance_high_threshold: float = 400.0 # m^2 (trace of position covariance)
|
|
|
|
# Initial covariance diagonals
|
|
init_pos_var: float = 100.0 # m^2
|
|
init_vel_var: float = 1.0 # (m/s)^2
|
|
init_att_var: float = 0.01 # rad^2
|
|
init_accel_bias_var: float = 0.01 # (m/s^2)^2
|
|
init_gyro_bias_var: float = 1e-6 # (rad/s)^2
|
|
|
|
# Mahalanobis outlier rejection (chi-squared threshold for 3-DOF at 5-sigma)
|
|
mahalanobis_threshold: float = 16.27 # chi2(3, 0.99999) ≈ 5-sigma gate
|
|
|
|
|
|
class ESKFState(BaseModel):
|
|
"""Full ESKF nominal state snapshot."""
|
|
model_config = {"arbitrary_types_allowed": True}
|
|
|
|
position: np.ndarray # (3,) ENU meters from origin (East, North, Up)
|
|
velocity: np.ndarray # (3,) ENU m/s
|
|
quaternion: np.ndarray # (4,) [w, x, y, z] body-to-ENU
|
|
accel_bias: np.ndarray # (3,) m/s^2
|
|
gyro_bias: np.ndarray # (3,) rad/s
|
|
covariance: np.ndarray # (15, 15)
|
|
timestamp: float # seconds since epoch
|
|
confidence: ConfidenceTier
|
|
last_satellite_time: Optional[float] = None
|
|
last_vo_time: Optional[float] = None
|