# -*- coding: utf-8 -*- """ Client/Satellite-Konfiguration. """ from dataclasses import dataclass, field @dataclass class InputMicrophone: """Ein einzelnes Mikrofon im Multi-Mic-Mix. `device` ist der PulseAudio/PipeWire-Source-Name, z.B. "alsa_input.usb-Generic-00.mono-fallback". """ device: str = "" enabled: bool = True label: str = "" @dataclass class OutputSpeaker: """Ein einzelner Lautsprecher im Multi-Speaker-Verbund. `device` ist der PulseAudio/PipeWire-Sink-Name, z.B. "alsa_output.usb-Generic-00.analog-stereo". """ device: str = "" enabled: bool = True volume: int = 80 @dataclass class ServerConnectionConfig: """Server-Verbindungs-Konfiguration.""" host: str = "localhost" port: int = 2101 reconnect_interval_seconds: float = 5.0 connection_timeout_seconds: float = 10.0 @dataclass class ClientAudioConfig: """Audio-Konfiguration für den Client.""" # Sprach-Audio (Wakeword-Erkennung) sample_rate: int = 16000 channels: int = 1 bit_depth: int = 16 # Musik-Wiedergabe (sollte mit Server übereinstimmen) music_sample_rate: int = 44100 # 44.1kHz - Standard für die meisten Geräte music_channels: int = 2 music_bit_depth: int = 16 # Geräte input_device: str | None = None output_device: str | None = None buffer_size: int = 1024 # System-Lautstärke (ALSA/PulseAudio, 0-100) output_volume: int = 80 output_muted: bool = False input_volume: int = 80 input_muted: bool = False # ALSA Mixer Controls (leer = Auto-Detect) output_mixer_control: str = "" input_mixer_control: str = "" # Multi-Speaker (PulseAudio Combined Sink) # Wenn ≥2 Eintraege enabled sind, wird ein Combined-Sink erstellt # und der Audio-Player auf diesen umgelenkt. Bei 0 oder 1 Eintrag # bleibt der klassische Single-Output-Pfad aktiv. output_speakers: list[OutputSpeaker] = field(default_factory=list) output_combine_sink_name: str = "trixy_combined" # Multi-Mic (PulseAudio null-sink + loopback-Mix) # Wenn ≥2 Eintraege enabled sind, wird ein virtueller gemischter # Input erstellt und der Default-Source darauf gesetzt. Bei 0 oder # 1 Eintrag bleibt der klassische Single-Input-Pfad aktiv. input_microphones: list[InputMicrophone] = field(default_factory=list) input_mix_sink_name: str = "trixy_mics_combined" @dataclass class ClientWakewordConfig: """Wakeword-Konfiguration für den Client.""" models: list[str] = field(default_factory=lambda: ["custom", "system_command"]) threshold: float = 0.5 model_directory: str = "models/wakeword" use_onnx: bool = True enabled: bool = True silence_timeout_seconds: float = 3.0 max_recording_seconds: float = 60.0 no_speech_timeout_seconds: float = 5.0 pre_buffer_seconds: float = 0.5 wakeword_cooldown_seconds: float = 3.0 @dataclass class SatelliteIdentityConfig: """Satelliten-Identität.""" room: str = "" alias: str = "" mac_address: str = "" # Wird automatisch ermittelt wenn leer @dataclass class ClientLoggingConfig: """Logging-Konfiguration für den Client.""" level: str = "INFO" log_directory: str = "logs" @dataclass class LEDConfig: """WS2812/NeoPixel LED-Konfiguration fuer Satellites.""" enabled: bool = False gpio_pin: int = 18 # BCM Pin (18 = PWM0, empfohlen) led_count: int = 1 # 1, 7, 12 type: str = "single" # "single" | "jewel_7" | "ring_12" brightness: int = 80 # 0-255 globale Helligkeit states: dict = field(default_factory=dict) # Pro Zustand: {color, animation, speed} @dataclass class ClientConfig: """Haupt-Client-Konfiguration.""" server: ServerConnectionConfig = field(default_factory=ServerConnectionConfig) audio: ClientAudioConfig = field(default_factory=ClientAudioConfig) wakeword: ClientWakewordConfig = field(default_factory=ClientWakewordConfig) identity: SatelliteIdentityConfig = field(default_factory=SatelliteIdentityConfig) logging: ClientLoggingConfig = field(default_factory=ClientLoggingConfig) led: LEDConfig = field(default_factory=LEDConfig) config_port: int = 2105 config_listener_enabled: bool = True assets_directory: str = "assets" profile: str = "default"