"""Configuration centralisée de Monitorink, chargée depuis l'environnement. Toutes les valeurs sensibles (token Claude, token Home Assistant) viennent de variables d'environnement / `.env` et ne sont jamais versionnées. """ from __future__ import annotations import os from dataclasses import dataclass, field from dotenv import load_dotenv load_dotenv() def _get(name: str, default: str = "") -> str: return os.environ.get(name, default).strip() def _get_list(name: str) -> list[str]: raw = _get(name) return [item.strip() for item in raw.split(",") if item.strip()] @dataclass(frozen=True) class HAEntity: """Une entité Home Assistant à afficher. Format env: `entity_id|Libellé|unité`.""" entity_id: str label: str unit: str = "" @classmethod def parse(cls, spec: str) -> "HAEntity": parts = [p.strip() for p in spec.split("|")] entity_id = parts[0] label = parts[1] if len(parts) > 1 and parts[1] else entity_id unit = parts[2] if len(parts) > 2 else "" return cls(entity_id=entity_id, label=label, unit=unit) @dataclass(frozen=True) class Config: # --- Affichage --- timezone: str = field(default_factory=lambda: _get("MONITORINK_TZ", "Europe/Paris")) locale: str = field(default_factory=lambda: _get("MONITORINK_LOCALE", "fr_FR")) # Canevas de RENDU en paysage (1680x1264). Le PNG est ensuite pivoté de 90° dans # render.py pour le panneau e-ink physiquement en portrait (1264x1680). width: int = field(default_factory=lambda: int(_get("MONITORINK_WIDTH", "1680"))) height: int = field(default_factory=lambda: int(_get("MONITORINK_HEIGHT", "1264"))) # Sens de rotation pour l'affichage Kobo : "cw" (bouton à droite) ou "ccw". rotate: str = field(default_factory=lambda: _get("MONITORINK_ROTATE", "cw").lower()) # --- Claude --- # Chemin du fichier .credentials.json d'un login Claude ISOLÉ dédié à Monitorink # (CLAUDE_CONFIG_DIR séparé). Le backend y lit/écrit (refresh) sans toucher le # ~/.claude partagé. L'endpoint /usage exige le scope user:profile -> login complet # requis (le token `claude setup-token` ne suffit pas, scope insuffisant). claude_creds_path: str = field( default_factory=lambda: _get("MONITORINK_CLAUDE_CREDS", "/creds/.credentials.json") ) claude_ua: str = field( default_factory=lambda: _get("MONITORINK_CLAUDE_UA", "claude-code/2.1.172") ) ccusage_enabled: bool = field( default_factory=lambda: _get("MONITORINK_CCUSAGE", "0") in ("1", "true", "yes") ) # --- Météo (Open-Meteo, sans clé) --- weather_lat: float = field(default_factory=lambda: float(_get("MONITORINK_LAT", "48.8566"))) weather_lon: float = field(default_factory=lambda: float(_get("MONITORINK_LON", "2.3522"))) # --- Home Assistant --- ha_base_url: str = field(default_factory=lambda: _get("MONITORINK_HA_URL").rstrip("/")) ha_token: str = field(default_factory=lambda: _get("MONITORINK_HA_TOKEN")) # --- NAS (moniteur maison nas_monitor, endpoint /api/status) --- nas_url: str = field(default_factory=lambda: _get("MONITORINK_NAS_URL")) # --- Cache / rafraîchissement serveur --- cache_ttl_seconds: int = field( default_factory=lambda: int(_get("MONITORINK_CACHE_TTL", "120")) ) @property def ha_entities(self) -> list[HAEntity]: return [HAEntity.parse(s) for s in _get_list("MONITORINK_HA_ENTITIES")] @property def ha_enabled(self) -> bool: return bool(self.ha_base_url and self.ha_token) config = Config()