diff --git a/config/ci.yaml b/config/ci.yaml new file mode 100644 index 0000000..38e00c9 --- /dev/null +++ b/config/ci.yaml @@ -0,0 +1 @@ +env: ci diff --git a/config/jetson.yaml b/config/jetson.yaml new file mode 100644 index 0000000..824e9cb --- /dev/null +++ b/config/jetson.yaml @@ -0,0 +1 @@ +env: jetson diff --git a/config/sitl.yaml b/config/sitl.yaml new file mode 100644 index 0000000..15801a3 --- /dev/null +++ b/config/sitl.yaml @@ -0,0 +1 @@ +env: sitl diff --git a/config/x86_dev.yaml b/config/x86_dev.yaml new file mode 100644 index 0000000..1d411a9 --- /dev/null +++ b/config/x86_dev.yaml @@ -0,0 +1 @@ +env: x86_dev diff --git a/pyproject.toml b/pyproject.toml index 49b7e33..221a019 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "opencv-python-headless>=4.9,<4.11", # 4.11+ requires numpy>=2.0 (incompatible with GTSAM) "gtsam>=4.3a0", "pymavlink>=2.4", + "pyyaml>=6.0", ] [project.optional-dependencies] diff --git a/src/gps_denied/config.py b/src/gps_denied/config.py index 678889f..e5e5741 100644 --- a/src/gps_denied/config.py +++ b/src/gps_denied/config.py @@ -3,9 +3,10 @@ from __future__ import annotations from pathlib import Path +from typing import Literal from pydantic import Field -from pydantic_settings import BaseSettings, SettingsConfigDict +from pydantic_settings import BaseSettings, SettingsConfigDict, YamlConfigSettingsSource class DatabaseConfig(BaseSettings): @@ -162,6 +163,8 @@ class AppSettings(BaseSettings): extra="ignore", ) + env: Literal["jetson", "x86_dev", "ci", "sitl"] = "x86_dev" + db: DatabaseConfig = Field(default_factory=DatabaseConfig) api: APIConfig = Field(default_factory=APIConfig) tiles: TileProviderConfig = Field(default_factory=TileProviderConfig) @@ -174,6 +177,36 @@ class AppSettings(BaseSettings): satellite: SatelliteConfig = Field(default_factory=SatelliteConfig) eskf: ESKFSettings = Field(default_factory=ESKFSettings) + @classmethod + def settings_customise_sources(cls, settings_cls, **kwargs): + """Load YAML overlay from config/{env}.yaml when present.""" + import os + import yaml + + init_kwargs = kwargs.get("init_settings") + env_settings = kwargs.get("env_settings") + dotenv = kwargs.get("dotenv_settings") + file_secret = kwargs.get("file_secret_settings") + + # Determine which env we're in (check ENV env-var before loading YAML) + current_env = os.environ.get("ENV", "x86_dev") + yaml_path = Path(f"config/{current_env}.yaml") + + yaml_source = None + if yaml_path.exists(): + try: + yaml_source = YamlConfigSettingsSource(settings_cls, yaml_file=yaml_path) + except Exception: + yaml_source = None + + sources = [s for s in [init_kwargs, env_settings, dotenv, file_secret] if s is not None] + if yaml_source is not None: + sources.append(yaml_source) + return tuple(sources) + + +# Alias for external consumers that expect RuntimeConfig +RuntimeConfig = AppSettings _settings: AppSettings | None = None