from __future__ import annotations import os import socket from dataclasses import dataclass from pathlib import Path from typing import Any import yaml @dataclass(slots=True) class ServerConfig: url: str api_token: str reconnect_seconds: float = 3.0 verify_tls: bool = True @dataclass(slots=True) class MidiConfig: device_name: str channel_filter: int | None = None ignore_active_sensing: bool = True @dataclass(slots=True) class PerformanceConfig: control_change_interval_ms: int = 25 suppress_duplicate_values: bool = True @dataclass(slots=True) class LoggingConfig: level: str = "INFO" show_midi_events: bool = True @dataclass(slots=True) class BridgeConfig: bridge_id: str server: ServerConfig midi: MidiConfig performance: PerformanceConfig logging: LoggingConfig def load_config(path: str | Path) -> BridgeConfig: config_path = Path(path) raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {} if not isinstance(raw, dict): raise ValueError("Konfigurationsfilen skal være YAML med topniveau som objekt.") server_data = raw.get("server") or {} midi_data = raw.get("midi") or {} performance_data = raw.get("performance") or {} logging_data = raw.get("logging") or {} env_token = os.getenv("TUXDMX_API_TOKEN", "").strip() api_token = env_token or str(server_data.get("api_token", "")).strip() if not api_token: raise ValueError("API-token mangler. Angiv det i YAML eller via TUXDMX_API_TOKEN.") return BridgeConfig( bridge_id=str(raw.get("bridge_id") or socket.gethostname()).strip(), server=ServerConfig( url=str(server_data.get("url", "")).rstrip("/"), api_token=api_token, reconnect_seconds=float(server_data.get("reconnect_seconds", 3)), verify_tls=bool(server_data.get("verify_tls", True)), ), midi=MidiConfig( device_name=str(midi_data.get("device_name", "")).strip(), channel_filter=_optional_int(midi_data.get("channel_filter")), ignore_active_sensing=bool(midi_data.get("ignore_active_sensing", True)), ), performance=PerformanceConfig( control_change_interval_ms=int(performance_data.get("control_change_interval_ms", 25)), suppress_duplicate_values=bool(performance_data.get("suppress_duplicate_values", True)), ), logging=LoggingConfig( level=str(logging_data.get("level", "INFO")).upper(), show_midi_events=bool(logging_data.get("show_midi_events", True)), ), ) def _optional_int(value: Any) -> int | None: if value in {None, "", "null"}: return None return int(value)