|
@@ -0,0 +1,136 @@
|
|
|
+import sys
|
|
|
+import uuid
|
|
|
+import signal
|
|
|
+import asyncio
|
|
|
+from aioconsole import ainput
|
|
|
+from config.settings import load_config
|
|
|
+from config.logger import setup_logging
|
|
|
+from core.utils.util import check_ffmpeg_installed, get_local_ip, validate_mcp_endpoint
|
|
|
+from core.websocket.server import WebSocketServer
|
|
|
+from core.simple_http.server import SimpleHttpServer
|
|
|
+
|
|
|
+TAG = __name__
|
|
|
+logger = setup_logging()
|
|
|
+
|
|
|
+async def wait_for_exit() -> None:
|
|
|
+ """
|
|
|
+ 阻塞直到收到 Ctrl-C / SIGINT / SIGTERM 信号
|
|
|
+ - Unix: 使用 add_signal_handler 注册信号处理
|
|
|
+ - Windows: 使用 signal.pause() 等待信号
|
|
|
+ """
|
|
|
+ loop = asyncio.get_running_loop()
|
|
|
+ stop_event = asyncio.Event()
|
|
|
+
|
|
|
+ if sys.platform != "win32": # Unix / macOS
|
|
|
+ for sig in (signal.SIGINT, signal.SIGTERM):
|
|
|
+ loop.add_signal_handler(sig, stop_event.set)
|
|
|
+ await stop_event.wait()
|
|
|
+ else:
|
|
|
+ # Windows:await一个永远pending的fut,
|
|
|
+ # 让 KeyboardInterrupt 冒泡到 asyncio.run,以此消除遗留普通线程导致进程退出阻塞的问题
|
|
|
+ try:
|
|
|
+ await asyncio.Future()
|
|
|
+ except KeyboardInterrupt: # Ctrl‑C
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+async def monitor_stdin():
|
|
|
+ """监控标准输入,消费回车键"""
|
|
|
+ while True:
|
|
|
+ await ainput() # 异步等待输入, 消费回车
|
|
|
+
|
|
|
+async def main():
|
|
|
+ check_ffmpeg_installed()
|
|
|
+ config = load_config()
|
|
|
+
|
|
|
+ # 默认使用manager-api的secret作为auth_key
|
|
|
+ # 如果secret为空,则生成随机密钥
|
|
|
+ # auth_key用于jwt认证,比如视觉分析接口的jwt认证
|
|
|
+ auth_key = config.get("manager-api",{}).get("secret","")
|
|
|
+ if not auth_key or len(auth_key) == 0 or "你" in auth_key:
|
|
|
+ auth_key = str(uuid.uuid4().hex)
|
|
|
+ config["server"]["auth_key"] = auth_key
|
|
|
+
|
|
|
+ # 添加 stdin 监控任务
|
|
|
+ stdin_task = asyncio.create_task(monitor_stdin())
|
|
|
+
|
|
|
+ # 启动WebSocket 服务器
|
|
|
+ ws_server = WebSocketServer(config)
|
|
|
+ ws_task = asyncio.create_task(ws_server.start())
|
|
|
+
|
|
|
+ # 启动simple http 服务器
|
|
|
+ ota_server = SimpleHttpServer(config)
|
|
|
+ ota_task = asyncio.create_task(ota_server.start())
|
|
|
+
|
|
|
+ read_config_from_api = config.get("read_config_from_api",False)
|
|
|
+ port = int(config.get("server",{}).get("port",8003))
|
|
|
+ if not read_config_from_api:
|
|
|
+ logger.bind(tag=TAG).info(
|
|
|
+ "OTA接口是\t\thttp://{}:{}/xiaozhi/ota/",
|
|
|
+ get_local_ip(),
|
|
|
+ port,
|
|
|
+ )
|
|
|
+ logger.bind(tag=TAG).info(
|
|
|
+ "视觉分析接口是\thttp://{}:{}/mcp/vision/explain",
|
|
|
+ get_local_ip(),
|
|
|
+ port,
|
|
|
+ )
|
|
|
+ mcp_endpoint = config.get("mcp_endpoint",None)
|
|
|
+ if mcp_endpoint is not None and "你" not in mcp_endpoint:
|
|
|
+ if validate_mcp_endpoint(mcp_endpoint):
|
|
|
+ logger.bind(tag=TAG).info("MCP端点是\t\t{}",mcp_endpoint)
|
|
|
+ # 将mcp计入点地址转为调用点
|
|
|
+ mcp_endpoint = mcp_endpoint.replace("/mcp/","/mcp/call/")
|
|
|
+ config["mcp_endpoint"] = mcp_endpoint
|
|
|
+ else:
|
|
|
+ logger.bind(tag=TAG).error("MCP端点无效,请检查配置")
|
|
|
+ config["mcp_endpoint"] = "你的接入点 websocket地址"
|
|
|
+
|
|
|
+ # 获取websocket配置
|
|
|
+ websocket_port = 8000
|
|
|
+ sever_config = config.get("server",{})
|
|
|
+ if isinstance(sever_config,dict):
|
|
|
+ websocket_port = int(sever_config.get("websocket_port",8000))
|
|
|
+
|
|
|
+ logger.bind(tag=TAG).info(
|
|
|
+ "WebSocket地址是\t\tws://{}:{}",
|
|
|
+ get_local_ip(),
|
|
|
+ websocket_port,
|
|
|
+ )
|
|
|
+
|
|
|
+ logger.bind(tag=TAG).info(
|
|
|
+ "=======上面的地址是websocket协议地址,请勿用浏览器访问======="
|
|
|
+ )
|
|
|
+ logger.bind(tag=TAG).info(
|
|
|
+ "如想测试websocket请用谷歌浏览器打开test目录下的test_page.html"
|
|
|
+ )
|
|
|
+ logger.bind(tag=TAG).info(
|
|
|
+ "=============================================================\n"
|
|
|
+ )
|
|
|
+
|
|
|
+ try:
|
|
|
+ await wait_for_exit() # 阻塞直到收到退出信号
|
|
|
+ except asyncio.CancelledError:
|
|
|
+ print("收到退出信号,准备退出")
|
|
|
+ finally:
|
|
|
+ # 取消所有任务
|
|
|
+ stdin_task.cancel()
|
|
|
+ ws_task.cancel()
|
|
|
+ if ota_task:
|
|
|
+ ota_task.cancel()
|
|
|
+
|
|
|
+ # 等待所有任务完成
|
|
|
+ await asyncio.wait(
|
|
|
+ [stdin_task, ws_task, ota_task] if ota_task else [stdin_task, ws_task],
|
|
|
+ timeout = 3,
|
|
|
+ return_when = asyncio.ALL_COMPLETED,
|
|
|
+ )
|
|
|
+ print("服务器已关闭,程序退出。")
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ try:
|
|
|
+ asyncio.run(main())
|
|
|
+ except KeyboardInterrupt:
|
|
|
+ print("手动中断,程序终止。")
|