client.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. # -*- coding: utf-8 -*-
  2. """
  3. Client/Satellite-Konfiguration.
  4. """
  5. from dataclasses import dataclass, field
  6. @dataclass
  7. class InputMicrophone:
  8. """Ein einzelnes Mikrofon im Multi-Mic-Mix.
  9. `device` ist der PulseAudio/PipeWire-Source-Name, z.B.
  10. "alsa_input.usb-Generic-00.mono-fallback".
  11. """
  12. device: str = ""
  13. enabled: bool = True
  14. label: str = ""
  15. @dataclass
  16. class OutputSpeaker:
  17. """Ein einzelner Lautsprecher im Multi-Speaker-Verbund.
  18. `device` ist der PulseAudio/PipeWire-Sink-Name, z.B.
  19. "alsa_output.usb-Generic-00.analog-stereo".
  20. """
  21. device: str = ""
  22. enabled: bool = True
  23. volume: int = 80
  24. @dataclass
  25. class ServerConnectionConfig:
  26. """Server-Verbindungs-Konfiguration."""
  27. host: str = "localhost"
  28. port: int = 2101
  29. reconnect_interval_seconds: float = 5.0
  30. connection_timeout_seconds: float = 10.0
  31. @dataclass
  32. class ClientAudioConfig:
  33. """Audio-Konfiguration für den Client."""
  34. # Sprach-Audio (Wakeword-Erkennung)
  35. sample_rate: int = 16000
  36. channels: int = 1
  37. bit_depth: int = 16
  38. # Musik-Wiedergabe (sollte mit Server übereinstimmen)
  39. music_sample_rate: int = 44100 # 44.1kHz - Standard für die meisten Geräte
  40. music_channels: int = 2
  41. music_bit_depth: int = 16
  42. # Geräte
  43. input_device: str | None = None
  44. output_device: str | None = None
  45. buffer_size: int = 1024
  46. # System-Lautstärke (ALSA/PulseAudio, 0-100)
  47. output_volume: int = 80
  48. output_muted: bool = False
  49. input_volume: int = 80
  50. input_muted: bool = False
  51. # ALSA Mixer Controls (leer = Auto-Detect)
  52. output_mixer_control: str = ""
  53. input_mixer_control: str = ""
  54. # Multi-Speaker (PulseAudio Combined Sink)
  55. # Wenn ≥2 Eintraege enabled sind, wird ein Combined-Sink erstellt
  56. # und der Audio-Player auf diesen umgelenkt. Bei 0 oder 1 Eintrag
  57. # bleibt der klassische Single-Output-Pfad aktiv.
  58. output_speakers: list[OutputSpeaker] = field(default_factory=list)
  59. output_combine_sink_name: str = "trixy_combined"
  60. # Multi-Mic (PulseAudio null-sink + loopback-Mix)
  61. # Wenn ≥2 Eintraege enabled sind, wird ein virtueller gemischter
  62. # Input erstellt und der Default-Source darauf gesetzt. Bei 0 oder
  63. # 1 Eintrag bleibt der klassische Single-Input-Pfad aktiv.
  64. input_microphones: list[InputMicrophone] = field(default_factory=list)
  65. input_mix_sink_name: str = "trixy_mics_combined"
  66. @dataclass
  67. class ClientWakewordConfig:
  68. """Wakeword-Konfiguration für den Client."""
  69. models: list[str] = field(default_factory=lambda: ["custom", "system_command"])
  70. threshold: float = 0.5
  71. model_directory: str = "models/wakeword"
  72. use_onnx: bool = True
  73. enabled: bool = True
  74. silence_timeout_seconds: float = 3.0
  75. max_recording_seconds: float = 60.0
  76. no_speech_timeout_seconds: float = 5.0
  77. pre_buffer_seconds: float = 0.5
  78. wakeword_cooldown_seconds: float = 3.0
  79. @dataclass
  80. class SatelliteIdentityConfig:
  81. """Satelliten-Identität."""
  82. room: str = ""
  83. alias: str = ""
  84. mac_address: str = "" # Wird automatisch ermittelt wenn leer
  85. @dataclass
  86. class ClientLoggingConfig:
  87. """Logging-Konfiguration für den Client."""
  88. level: str = "INFO"
  89. log_directory: str = "logs"
  90. @dataclass
  91. class LEDConfig:
  92. """WS2812/NeoPixel LED-Konfiguration fuer Satellites."""
  93. enabled: bool = False
  94. gpio_pin: int = 18 # BCM Pin (18 = PWM0, empfohlen)
  95. led_count: int = 1 # 1, 7, 12
  96. type: str = "single" # "single" | "jewel_7" | "ring_12"
  97. brightness: int = 80 # 0-255 globale Helligkeit
  98. states: dict = field(default_factory=dict) # Pro Zustand: {color, animation, speed}
  99. @dataclass
  100. class ClientConfig:
  101. """Haupt-Client-Konfiguration."""
  102. server: ServerConnectionConfig = field(default_factory=ServerConnectionConfig)
  103. audio: ClientAudioConfig = field(default_factory=ClientAudioConfig)
  104. wakeword: ClientWakewordConfig = field(default_factory=ClientWakewordConfig)
  105. identity: SatelliteIdentityConfig = field(default_factory=SatelliteIdentityConfig)
  106. logging: ClientLoggingConfig = field(default_factory=ClientLoggingConfig)
  107. led: LEDConfig = field(default_factory=LEDConfig)
  108. config_port: int = 2105
  109. config_listener_enabled: bool = True
  110. assets_directory: str = "assets"
  111. profile: str = "default"