docs: add project description and server setup instructions to README
This commit is contained in:
99
server/app/config.py
Normal file
99
server/app/config.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user