server.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. # -*- coding: utf-8 -*-
  2. """
  3. Server-Anwendung für Trixy.
  4. Der Server ist die zentrale Komponente, die:
  5. - Satellites verwaltet
  6. - Plugins lädt und ausführt
  7. - ML-Inferenz durchführt
  8. - Events koordiniert
  9. """
  10. import asyncio
  11. from pathlib import Path
  12. from trixy_core.application import IApplication
  13. from trixy_core.utils.version import VERSION_STRING
  14. from trixy_core.config.datasets.server import ServerConfig
  15. from trixy_core.satellite.satellite_manager import SatelliteManager
  16. from trixy_core.satellite.registration_manager import RegistrationManager
  17. from trixy_core.satellite.connection_notification import (
  18. ConnectionNotificationHandler,
  19. setup_connection_notification,
  20. )
  21. from trixy_core.plugins.plugin_manager import PluginManager
  22. from trixy_core.plugins.extensions import get_global_registry
  23. from trixy_core.plugins.extensions.points import (
  24. setup_music_extension_points,
  25. setup_network_extension_points,
  26. setup_conversation_extension_points,
  27. )
  28. from trixy_core.events.event_data.basic import SystemShutdown
  29. from trixy_core.network.service import NetworkService
  30. from trixy_core.network.message_handler import setup_default_event_bridge
  31. from trixy_core.audio.config import init_audio_settings_from_config
  32. from trixy_core.input.text_input_handler import setup_text_input_handler, TextInputHandler
  33. from trixy_core.nlp.intent_dispatcher import IntentDispatcherService
  34. from trixy_core.stt.corrector import STTCorrectorService
  35. from trixy_core.conversation.profiler import ConversationProfiler
  36. from trixy_core.arbitration.arbitrator import WakewordArbitrator, ArbitrationConfig
  37. from trixy_core.audio.accumulator import AudioAccumulatorService
  38. from trixy_core.network.config_listener import ConfigListener
  39. from trixy_core.email.service import EmailService
  40. from trixy_core.scheduler.service import SchedulerService
  41. from trixy_core.trainer.service import TrainerService
  42. from trixy_core.activity.tracker import ActivityTracker
  43. from trixy_core.utils.debug import pinfo, pdebug, perror
  44. class ServerApplication(IApplication):
  45. """
  46. Server-Anwendung für Trixy.
  47. Verwaltet alle zentralen Funktionen und koordiniert
  48. die Kommunikation mit verbundenen Satellites.
  49. """
  50. def __init__(
  51. self,
  52. config_path: str | Path = "config/server_config.json",
  53. debug: bool = False,
  54. auto_regist: bool = False
  55. ) -> None:
  56. """
  57. Initialisiert die Server-Anwendung.
  58. Args:
  59. config_path: Pfad zur Konfigurationsdatei
  60. debug: Debug-Modus aktivieren
  61. auto_regist: Dauerhafter Auto-Registrierungsmodus
  62. """
  63. super().__init__(debug)
  64. self._config_path = Path(config_path)
  65. self._auto_regist = auto_regist
  66. self._server_config: ServerConfig | None = None
  67. # Server-spezifische Manager
  68. self._satellite_manager: SatelliteManager | None = None
  69. self._registration_manager: RegistrationManager | None = None
  70. self._plugin_manager: PluginManager | None = None
  71. self._network_service: NetworkService | None = None
  72. self._connection_notification: ConnectionNotificationHandler | None = None
  73. self._text_input_handler: TextInputHandler | None = None
  74. self._wakeword_arbitrator: WakewordArbitrator | None = None
  75. self._audio_accumulator: AudioAccumulatorService | None = None
  76. self._config_listener: ConfigListener | None = None
  77. @property
  78. def server_config(self) -> ServerConfig:
  79. """Server-Konfiguration."""
  80. if self._server_config is None:
  81. raise RuntimeError("Server nicht initialisiert")
  82. return self._server_config
  83. @property
  84. def satellites(self) -> SatelliteManager:
  85. """Satellite-Manager."""
  86. if self._satellite_manager is None:
  87. raise RuntimeError("Server nicht initialisiert")
  88. return self._satellite_manager
  89. @property
  90. def registration(self) -> RegistrationManager:
  91. """Registrierungs-Manager."""
  92. if self._registration_manager is None:
  93. raise RuntimeError("Server nicht initialisiert")
  94. return self._registration_manager
  95. @property
  96. def plugins(self) -> PluginManager:
  97. """Plugin-Manager."""
  98. if self._plugin_manager is None:
  99. raise RuntimeError("Server nicht initialisiert")
  100. return self._plugin_manager
  101. @property
  102. def network(self) -> NetworkService:
  103. """Network-Service."""
  104. if self._network_service is None:
  105. raise RuntimeError("Server nicht initialisiert")
  106. return self._network_service
  107. @property
  108. def extensions(self):
  109. """Extension-Registry."""
  110. return get_global_registry()
  111. def _setup_extension_points(self) -> None:
  112. """
  113. Richtet alle Extension Points ein.
  114. Wird vor dem Plugin-Laden aufgerufen, damit Plugins
  115. ihre Extensions registrieren können.
  116. """
  117. pdebug("Initialisiere Extension Points...")
  118. # Music Extension Points
  119. setup_music_extension_points()
  120. # Network Extension Points
  121. setup_network_extension_points()
  122. # Conversation Extension Points
  123. setup_conversation_extension_points()
  124. registry = get_global_registry()
  125. pdebug(f"Extension Points initialisiert: {len(registry)} Points")
  126. async def initialize(self) -> None:
  127. """Initialisiert den Server."""
  128. pinfo("Initialisiere Server...")
  129. # Konfiguration laden
  130. self._server_config = self.config_manager.load(
  131. self._config_path,
  132. ServerConfig,
  133. name="server",
  134. auto_reload=True
  135. )
  136. # File-Logging initialisieren
  137. self._init_file_logging(self._server_config.logging)
  138. # Audio-Einstellungen initialisieren (für Plugin-Zugriff)
  139. audio_settings = init_audio_settings_from_config(self._server_config)
  140. pdebug(f"Audio-Einstellungen: TTS={audio_settings.tts_sample_rate}Hz, Musik={audio_settings.music_sample_rate}Hz")
  141. # Manager initialisieren
  142. self._satellite_manager = SatelliteManager(self)
  143. self._registration_manager = RegistrationManager(
  144. self,
  145. directory=self._server_config.satellites_directory
  146. )
  147. self._plugin_manager = PluginManager(
  148. self,
  149. directory=self._server_config.plugins.plugin_directory,
  150. timeout_seconds=self._server_config.logging.plugin_timeout_seconds,
  151. max_failures=self._server_config.logging.plugin_max_failures,
  152. )
  153. # Extension Points initialisieren (vor Plugin-Laden)
  154. self._setup_extension_points()
  155. # Registrierte Satellites laden
  156. registered = self._registration_manager.load_all()
  157. for satellite in registered:
  158. self._satellite_manager.add(satellite)
  159. # Auto-Registrierungsmodus aktivieren falls gewünscht
  160. if self._auto_regist:
  161. self._registration_manager.enable_permanent_registration_mode()
  162. else:
  163. # File-basierte Registrierung aktivieren
  164. self._registration_manager.enable_file_trigger()
  165. # NetworkService erstellen und registrieren
  166. self._network_service = NetworkService(self)
  167. self.services.register_instance(self._network_service)
  168. # Event-Bridge für Netzwerk-Nachrichten einrichten
  169. event_bridge = setup_default_event_bridge(self.events)
  170. self._network_service.register_message_handler(event_bridge.on_message)
  171. # ConfigListener erstellen und registrieren (Remote-Config-Tool Zugriff)
  172. self._config_listener = ConfigListener(
  173. self,
  174. port=self._server_config.network.config_port,
  175. bind_address=self._server_config.network.bind_address,
  176. encryption_key_path=self._server_config.security.encryption_key_path,
  177. instance_type="server",
  178. )
  179. self.services.register_instance(self._config_listener)
  180. # Wakeword-Arbitrator erstellen (koordiniert Multi-Satellite-Erkennung)
  181. ww_config = self._server_config.wakeword
  182. self._wakeword_arbitrator = WakewordArbitrator(
  183. config=ArbitrationConfig(
  184. window_duration_ms=int(ww_config.arbitration_timeout_seconds * 1000),
  185. min_audio_level=ww_config.min_audio_level,
  186. min_confidence=ww_config.min_confidence,
  187. ),
  188. application=self,
  189. )
  190. # Audio-Akkumulator erstellen (sammelt gestreamte Frames pro Conversation)
  191. ww_trim = self._server_config.wakeword.wakeword_trim_seconds
  192. self._audio_accumulator = AudioAccumulatorService(self, wakeword_trim_seconds=ww_trim)
  193. # SyncStore und SyncHandler erstellen
  194. from trixy_core.sync.store import SyncStore
  195. from trixy_core.sync.handler import SyncHandler
  196. self._sync_store = SyncStore(base_directory="./sync_data")
  197. self._sync_handler = SyncHandler(self)
  198. self._network_service.register_message_handler(self._sync_handler.on_message)
  199. pdebug("SyncStore und SyncHandler initialisiert")
  200. # SchedulerService erstellen und registrieren (Job-Scheduling)
  201. self._scheduler_service = SchedulerService(self)
  202. self.services.register_instance(self._scheduler_service)
  203. # TrainerService erstellen und registrieren (ML-Trainer-Framework)
  204. self._trainer_service = TrainerService(self)
  205. self.services.register_instance(self._trainer_service)
  206. # ActivityTracker erstellen und registrieren (zentrales Activity-Logging)
  207. self._activity_tracker = ActivityTracker(self)
  208. self.services.register_instance(self._activity_tracker)
  209. # EmailService erstellen und registrieren (SMTP-Versand fuer Plugins)
  210. from dataclasses import asdict
  211. email_cfg = asdict(self._server_config.email)
  212. self._email_service = EmailService(self, email_cfg)
  213. self.services.register_instance(self._email_service)
  214. # MusicPlayerService erstellen und registrieren
  215. from trixy_core.music import MusicPlayerService
  216. self._music_player_service = MusicPlayerService(self)
  217. self.services.register_instance(self._music_player_service)
  218. # Connection Notification Handler einrichten (für TTS-Notifications)
  219. # Musik-Wiedergabe läuft über MusicPlayerService
  220. self._connection_notification = setup_connection_notification(self)
  221. # IntentDispatcher-Service registrieren (verarbeitet intent_received Events)
  222. self._intent_dispatcher = IntentDispatcherService(self)
  223. self.services.register_instance(self._intent_dispatcher)
  224. self.events.register_object(self._intent_dispatcher)
  225. # STT-Korrektur-Service registrieren (korrigiert Text vor NLP mit HIGH-Prioritaet)
  226. self._stt_corrector = STTCorrectorService(self)
  227. self.services.register_instance(self._stt_corrector)
  228. self.events.register_object(self._stt_corrector)
  229. # Conversation-Profiler registrieren (Debug-Modus: misst Phasen-Dauern)
  230. self._conversation_profiler = ConversationProfiler(self)
  231. self.services.register_instance(self._conversation_profiler)
  232. self.events.register_object(self._conversation_profiler)
  233. # Text-Input-Handler für Keyboard-Eingabe einrichten
  234. self._text_input_handler = setup_text_input_handler(self)
  235. pdebug(f"Server initialisiert mit {len(registered)} registrierten Satellites")
  236. async def start(self) -> None:
  237. """Startet den Server."""
  238. pinfo("Starte Server...")
  239. await self.events.emit("application_starting", {
  240. "mode": "server",
  241. "version": VERSION_STRING,
  242. })
  243. # Services starten
  244. await self.services.start_all()
  245. # Wakeword-Arbitrator starten (Event-Handler registrieren)
  246. if self._wakeword_arbitrator:
  247. await self._wakeword_arbitrator.start()
  248. # Audio-Akkumulator starten
  249. if self._audio_accumulator:
  250. await self._audio_accumulator.start()
  251. # Plugins laden
  252. if self._server_config and self._server_config.plugins.enabled:
  253. # Debug: Extension Points Status
  254. registry = get_global_registry()
  255. pdebug(f"Vor Plugin-Laden: {len(registry)} Extension Points, IDs: {[p.id for p in registry.points]}")
  256. await self._plugin_manager.load_all()
  257. pinfo(f"Server läuft auf Ports {self._server_config.network.command_port}-{self._server_config.network.music_port}")
  258. # Server ist jetzt vollstaendig bereit — Satellites duerfen sich verbinden
  259. self.services.set_ready(True)
  260. await self.events.emit("application_ready", {
  261. "mode": "server",
  262. "version": VERSION_STRING,
  263. "services_count": len(self.services.services),
  264. })
  265. async def stop(self) -> None:
  266. """Stoppt den Server."""
  267. pinfo("Stoppe Server...")
  268. # Markiere Server als nicht mehr bereit — neue HELO werden abgelehnt
  269. self.services.set_ready(False)
  270. await self.events.emit("application_stopping", {"reason": "shutdown"})
  271. # Shutdown-Event auslösen
  272. await self.events.trigger(
  273. "system_shutdown",
  274. SystemShutdown(reason="Server shutdown", source="server")
  275. )
  276. # Audio-Akkumulator stoppen
  277. if self._audio_accumulator:
  278. await self._audio_accumulator.stop()
  279. # Wakeword-Arbitrator stoppen
  280. if self._wakeword_arbitrator:
  281. await self._wakeword_arbitrator.stop()
  282. # Satellites trennen
  283. if self._satellite_manager:
  284. await self._satellite_manager.disconnect_all("Server shutdown")
  285. # Plugins entladen
  286. if self._plugin_manager:
  287. await self._plugin_manager.unload_all()
  288. # Services stoppen
  289. await self.services.stop_all()
  290. # ConfigManager aufräumen
  291. self.config_manager.stop_watching()
  292. # File-Logging herunterfahren
  293. self._shutdown_file_logging()
  294. pinfo("Server gestoppt")