main.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. Trixy Voice Assistant - Haupteinstiegspunkt.
  5. Unterstützt folgende Modi:
  6. - server: Zentraler Server für Satellite-Management und ML-Inferenz
  7. - client: Leichtgewichtiger Satellite mit Wakeword-Erkennung
  8. - standalone: All-in-One-Modus ohne Server
  9. - config: Remote-Verwaltung per TUI (verbindet sich mit laufender Instanz)
  10. - train: ML-Trainer für Wakeword- und Sprechererkennungsmodelle
  11. Verwendung:
  12. python3 main.py server [--debug] [--config <pfad>]
  13. python3 main.py client [--host <ip>] [--port <port>] [--room <raum>] [--alias <name>] [--debug]
  14. python3 main.py standalone [--debug] [--config <pfad>]
  15. python3 main.py config [--host <host>] [--port <port>] [--key <pfad>]
  16. python3 main.py train [wakeword|voice-recognition|all] [--debug] [--config <pfad>]
  17. """
  18. import argparse
  19. import asyncio
  20. import os
  21. import sys
  22. from pathlib import Path
  23. # Füge das aktuelle Verzeichnis zum Pfad hinzu
  24. sys.path.insert(0, str(Path(__file__).parent))
  25. # CUDA-Bibliotheken aus den nvidia Pip-Packages fuer onnxruntime-gpu verfuegbar machen
  26. _venv_root = Path(__file__).resolve().parents[1]
  27. _nvidia_base = _venv_root / "lib" / f"python{sys.version_info.major}.{sys.version_info.minor}" / "site-packages" / "nvidia"
  28. if _nvidia_base.is_dir():
  29. _nvidia_lib_dirs = [str(p) for p in _nvidia_base.glob("*/lib") if p.is_dir()]
  30. if _nvidia_lib_dirs:
  31. # LD_LIBRARY_PATH fuer Subprocesses (spawn)
  32. _existing = os.environ.get("LD_LIBRARY_PATH", "")
  33. os.environ["LD_LIBRARY_PATH"] = ":".join(_nvidia_lib_dirs) + (":" + _existing if _existing else "")
  34. # Libs vorab laden fuer den aktuellen Prozess (onnxruntime-gpu)
  35. import ctypes
  36. for _lib_dir in _nvidia_lib_dirs:
  37. for _so in sorted(Path(_lib_dir).glob("*.so.*")):
  38. try:
  39. ctypes.cdll.LoadLibrary(str(_so))
  40. except OSError:
  41. pass
  42. from trixy_core.utils.version import VERSION_STRING
  43. from trixy_core.utils.debug import pinfo, perror
  44. def create_parser() -> argparse.ArgumentParser:
  45. """Erstellt den Argument-Parser."""
  46. parser = argparse.ArgumentParser(
  47. prog="trixy",
  48. description="Trixy Voice Assistant System",
  49. formatter_class=argparse.RawDescriptionHelpFormatter,
  50. epilog="""
  51. Beispiele:
  52. %(prog)s server --debug
  53. %(prog)s client --host 192.168.1.100 --port 2101 --room wohnzimmer --alias "Echo 1"
  54. %(prog)s standalone --debug
  55. %(prog)s train wakeword
  56. """
  57. )
  58. parser.add_argument(
  59. "--version",
  60. action="version",
  61. version=f"Trixy v{VERSION_STRING}"
  62. )
  63. # Subparser für Modi
  64. subparsers = parser.add_subparsers(
  65. dest="mode",
  66. title="Modi",
  67. description="Verfügbare Betriebsmodi",
  68. required=True
  69. )
  70. # Server-Modus
  71. server_parser = subparsers.add_parser(
  72. "server",
  73. help="Server-Modus starten"
  74. )
  75. server_parser.add_argument(
  76. "--debug",
  77. action="store_true",
  78. help="Debug-Modus aktivieren (stdout statt TUI)"
  79. )
  80. server_parser.add_argument(
  81. "--config",
  82. type=str,
  83. default="config/server_config.json",
  84. help="Pfad zur Konfigurationsdatei"
  85. )
  86. server_parser.add_argument(
  87. "--auto-regist",
  88. action="store_true",
  89. help="Dauerhafter Auto-Registrierungsmodus für neue Satellites"
  90. )
  91. # Client-Modus
  92. client_parser = subparsers.add_parser(
  93. "client",
  94. help="Client/Satellite-Modus starten"
  95. )
  96. client_parser.add_argument(
  97. "--host",
  98. type=str,
  99. default=None,
  100. help="Server-IP-Adresse (Standard: aus Konfigurationsdatei)"
  101. )
  102. client_parser.add_argument(
  103. "--port",
  104. type=int,
  105. default=None,
  106. help="Server-Port (Standard: aus Konfigurationsdatei, fallback 2101)"
  107. )
  108. client_parser.add_argument(
  109. "--room",
  110. type=str,
  111. default=None,
  112. help="Raum-Kennung (Standard: aus Konfigurationsdatei)"
  113. )
  114. client_parser.add_argument(
  115. "--alias",
  116. type=str,
  117. default=None,
  118. help="Satellite-Name (Standard: aus Konfigurationsdatei)"
  119. )
  120. client_parser.add_argument(
  121. "--debug",
  122. action="store_true",
  123. help="Debug-Modus aktivieren"
  124. )
  125. client_parser.add_argument(
  126. "--config",
  127. type=str,
  128. default="config/client_config.json",
  129. help="Pfad zur Konfigurationsdatei"
  130. )
  131. client_parser.add_argument(
  132. "-i", "--keyboard-input",
  133. action="store_true",
  134. help="Keyboard-Eingabe aktivieren (Text statt Sprache)"
  135. )
  136. # Standalone-Modus
  137. standalone_parser = subparsers.add_parser(
  138. "standalone",
  139. help="Standalone-Modus starten (Server + Client kombiniert)"
  140. )
  141. standalone_parser.add_argument(
  142. "--debug",
  143. action="store_true",
  144. help="Debug-Modus aktivieren"
  145. )
  146. standalone_parser.add_argument(
  147. "--config",
  148. type=str,
  149. default="config/standalone_config.json",
  150. help="Pfad zur Konfigurationsdatei"
  151. )
  152. standalone_parser.add_argument(
  153. "-i", "--keyboard-input",
  154. action="store_true",
  155. help="Keyboard-Eingabe aktivieren (Text statt Sprache)"
  156. )
  157. # Config-Tool-Modus
  158. config_parser = subparsers.add_parser(
  159. "config",
  160. help="Config-Tool starten (Remote-Verwaltung per TUI)"
  161. )
  162. config_parser.add_argument(
  163. "--host",
  164. type=str,
  165. default="localhost",
  166. help="Host der Trixy-Instanz (Standard: localhost)"
  167. )
  168. config_parser.add_argument(
  169. "--port",
  170. type=int,
  171. default=2105,
  172. help="Config-Port der Trixy-Instanz (Standard: 2105)"
  173. )
  174. config_parser.add_argument(
  175. "--key",
  176. type=str,
  177. default="certs/encryption.key",
  178. help="Pfad zum Verschluesselungsschluessel"
  179. )
  180. # Trainer-Modus
  181. train_parser = subparsers.add_parser(
  182. "train",
  183. help="ML-Trainer starten"
  184. )
  185. train_parser.add_argument(
  186. "target",
  187. nargs="?",
  188. choices=["wakeword", "voice-recognition", "all"],
  189. default="all",
  190. help="Zu trainierendes Modell (Standard: all)"
  191. )
  192. train_parser.add_argument(
  193. "--debug",
  194. action="store_true",
  195. help="Debug-Modus aktivieren"
  196. )
  197. train_parser.add_argument(
  198. "--config",
  199. type=str,
  200. default="config/trainer_config.json",
  201. help="Pfad zur Konfigurationsdatei"
  202. )
  203. return parser
  204. async def run_server(args: argparse.Namespace) -> int:
  205. """Startet den Server-Modus."""
  206. from trixy_core.server import ServerApplication
  207. app = ServerApplication(
  208. config_path=args.config,
  209. debug=args.debug,
  210. auto_regist=getattr(args, "auto_regist", False)
  211. )
  212. try:
  213. await app.run()
  214. return 0
  215. except KeyboardInterrupt:
  216. pinfo("Beende auf Benutzeranfrage...")
  217. return 0
  218. except Exception as e:
  219. perror(f"Server-Fehler: {e}")
  220. return 1
  221. async def run_client(args: argparse.Namespace) -> int:
  222. """Startet den Client-Modus."""
  223. from trixy_core.client import ClientApplication
  224. app = ClientApplication(
  225. host=args.host,
  226. port=args.port,
  227. room=args.room,
  228. alias=args.alias,
  229. config_path=args.config,
  230. debug=args.debug,
  231. keyboard_input=getattr(args, "keyboard_input", False),
  232. )
  233. try:
  234. await app.run()
  235. return 0
  236. except KeyboardInterrupt:
  237. pinfo("Beende auf Benutzeranfrage...")
  238. return 0
  239. except Exception as e:
  240. perror(f"Client-Fehler: {e}")
  241. return 1
  242. async def run_standalone(args: argparse.Namespace) -> int:
  243. """Startet den Standalone-Modus."""
  244. from trixy_core.standalone import StandaloneApplication
  245. app = StandaloneApplication(
  246. config_path=args.config,
  247. debug=args.debug,
  248. keyboard_input=getattr(args, "keyboard_input", False),
  249. )
  250. try:
  251. await app.run()
  252. return 0
  253. except KeyboardInterrupt:
  254. pinfo("Beende auf Benutzeranfrage...")
  255. return 0
  256. except Exception as e:
  257. perror(f"Standalone-Fehler: {e}")
  258. return 1
  259. async def run_config(args: argparse.Namespace) -> int:
  260. """Startet das Config-Tool."""
  261. from trixy_core.config_app import ConfigApplication
  262. app = ConfigApplication(
  263. host=args.host,
  264. port=args.port,
  265. encryption_key_path=args.key,
  266. )
  267. try:
  268. await app.run()
  269. return 0
  270. except ConnectionError as e:
  271. perror(f"Verbindungsfehler: {e}")
  272. return 1
  273. except KeyboardInterrupt:
  274. pinfo("Config-Tool beendet")
  275. return 0
  276. except Exception as e:
  277. perror(f"Config-Tool-Fehler: {e}")
  278. return 1
  279. async def run_trainer(args: argparse.Namespace) -> int:
  280. """Startet den Trainer-Modus."""
  281. from trixy_core.trainer import TrainerApplication, TrainingTarget
  282. # Ziel-Mapping
  283. target_map = {
  284. "wakeword": TrainingTarget.WAKEWORD,
  285. "voice-recognition": TrainingTarget.VOICE_RECOGNITION,
  286. "all": TrainingTarget.ALL,
  287. }
  288. target = target_map.get(args.target, TrainingTarget.ALL)
  289. app = TrainerApplication(
  290. target=target,
  291. config_path=args.config,
  292. debug=args.debug
  293. )
  294. try:
  295. await app.run()
  296. return 0
  297. except KeyboardInterrupt:
  298. pinfo("Training abgebrochen...")
  299. return 0
  300. except Exception as e:
  301. perror(f"Trainer-Fehler: {e}")
  302. return 1
  303. def main() -> int:
  304. """Haupteinstiegspunkt."""
  305. parser = create_parser()
  306. args = parser.parse_args()
  307. # Modus-Dispatcher
  308. mode_runners = {
  309. "server": run_server,
  310. "client": run_client,
  311. "standalone": run_standalone,
  312. "config": run_config,
  313. "train": run_trainer,
  314. }
  315. runner = mode_runners.get(args.mode)
  316. if runner is None:
  317. parser.print_help()
  318. return 1
  319. # Asyncio-Loop ausführen
  320. try:
  321. return asyncio.run(runner(args))
  322. except KeyboardInterrupt:
  323. return 0
  324. if __name__ == "__main__":
  325. sys.exit(main())