100 lines
2.4 KiB
Python
100 lines
2.4 KiB
Python
"""
|
|
Demeter Server — Configuration
|
|
|
|
Loads settings from environment variables and the device registry
|
|
from config/devices.yaml.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import yaml
|
|
from pydantic import BaseModel, Field
|
|
from pydantic_settings import BaseSettings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# ── Paths ──
|
|
|
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
CONFIG_DIR = BASE_DIR / "config"
|
|
TEMPLATES_DIR = BASE_DIR / "templates"
|
|
|
|
|
|
# ── Device Config Models ──
|
|
|
|
class ResourceConfig(BaseModel):
|
|
"""A single CoAP resource on a device."""
|
|
uri: str
|
|
name: str
|
|
type: str = "periodic" # "periodic" or "event"
|
|
|
|
|
|
class DeviceConfig(BaseModel):
|
|
"""A single ESP sensor node."""
|
|
id: str
|
|
name: str
|
|
ip: str
|
|
port: int = 5683
|
|
enabled: bool = True
|
|
resources: list[ResourceConfig] = Field(default_factory=list)
|
|
|
|
|
|
class DevicesConfig(BaseModel):
|
|
"""Top-level device registry loaded from YAML."""
|
|
devices: list[DeviceConfig] = Field(default_factory=list)
|
|
|
|
|
|
# ── Application Settings ──
|
|
|
|
class Settings(BaseSettings):
|
|
"""Server settings loaded from environment variables."""
|
|
|
|
# FastAPI
|
|
app_title: str = "Demeter IoT Server"
|
|
app_version: str = "0.1.0"
|
|
debug: bool = False
|
|
|
|
# Server
|
|
host: str = "0.0.0.0"
|
|
port: int = 8000
|
|
|
|
# Loki
|
|
loki_url: Optional[str] = None # e.g. "http://localhost:3100/loki/api/v1/push"
|
|
|
|
# CoAP
|
|
coap_request_timeout: float = 10.0 # seconds
|
|
coap_reconnect_base: float = 5.0 # base backoff seconds
|
|
coap_reconnect_max: float = 60.0 # max backoff seconds
|
|
|
|
# Device config path
|
|
devices_config_path: str = str(CONFIG_DIR / "devices.yaml")
|
|
|
|
model_config = {"env_prefix": "DEMETER_", "env_file": ".env"}
|
|
|
|
|
|
def load_devices_config(path: str | Path | None = None) -> DevicesConfig:
|
|
"""Load and validate the device registry from YAML."""
|
|
if path is None:
|
|
path = CONFIG_DIR / "devices.yaml"
|
|
path = Path(path)
|
|
|
|
if not path.exists():
|
|
logger.warning("Device config not found at %s, using empty registry", path)
|
|
return DevicesConfig(devices=[])
|
|
|
|
with open(path) as f:
|
|
raw = yaml.safe_load(f)
|
|
|
|
if raw is None:
|
|
return DevicesConfig(devices=[])
|
|
|
|
return DevicesConfig.model_validate(raw)
|
|
|
|
|
|
# Singleton settings instance
|
|
settings = Settings()
|