2025-03-05 23:13:24 +08:00
|
|
|
|
import asyncio
|
2025-03-12 10:57:25 +08:00
|
|
|
|
import sys
|
|
|
|
|
|
import signal
|
2025-04-30 15:05:42 +08:00
|
|
|
|
from config.settings import load_config
|
2025-03-05 23:13:24 +08:00
|
|
|
|
from core.websocket_server import WebSocketServer
|
2025-04-26 21:56:41 +08:00
|
|
|
|
from core.ota_server import SimpleOtaServer
|
2025-03-05 23:13:24 +08:00
|
|
|
|
from core.utils.util import check_ffmpeg_installed
|
2025-04-27 13:07:14 +08:00
|
|
|
|
from config.logger import setup_logging
|
|
|
|
|
|
from core.utils.util import get_local_ip
|
2025-05-07 16:20:43 +08:00
|
|
|
|
from aioconsole import ainput
|
2025-03-05 23:13:24 +08:00
|
|
|
|
|
|
|
|
|
|
TAG = __name__
|
2025-04-27 13:07:14 +08:00
|
|
|
|
logger = setup_logging()
|
2025-03-05 23:13:24 +08:00
|
|
|
|
|
2025-04-12 17:36:04 +08:00
|
|
|
|
|
2025-05-04 22:58:43 +08:00
|
|
|
|
async def wait_for_exit() -> None:
|
|
|
|
|
|
"""
|
|
|
|
|
|
阻塞直到收到 Ctrl‑C / SIGTERM。
|
|
|
|
|
|
- Unix: 使用 add_signal_handler
|
|
|
|
|
|
- Windows: 依赖 KeyboardInterrupt
|
|
|
|
|
|
"""
|
2025-03-12 10:57:25 +08:00
|
|
|
|
loop = asyncio.get_running_loop()
|
|
|
|
|
|
stop_event = asyncio.Event()
|
|
|
|
|
|
|
2025-05-04 22:58:43 +08:00
|
|
|
|
if sys.platform != "win32": # Unix / macOS
|
|
|
|
|
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
|
|
|
|
loop.add_signal_handler(sig, stop_event.set)
|
2025-03-12 10:57:25 +08:00
|
|
|
|
await stop_event.wait()
|
2025-05-04 22:58:43 +08:00
|
|
|
|
else:
|
|
|
|
|
|
# Windows:await一个永远pending的fut,
|
|
|
|
|
|
# 让 KeyboardInterrupt 冒泡到 asyncio.run,以此消除遗留普通线程导致进程退出阻塞的问题
|
|
|
|
|
|
try:
|
|
|
|
|
|
await asyncio.Future()
|
|
|
|
|
|
except KeyboardInterrupt: # Ctrl‑C
|
|
|
|
|
|
pass
|
2025-03-05 23:13:24 +08:00
|
|
|
|
|
2025-04-12 17:36:04 +08:00
|
|
|
|
|
2025-05-07 16:20:43 +08:00
|
|
|
|
async def monitor_stdin():
|
|
|
|
|
|
"""监控标准输入,消费回车键"""
|
|
|
|
|
|
while True:
|
|
|
|
|
|
await ainput() # 异步等待输入,消费回车
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-05 23:13:24 +08:00
|
|
|
|
async def main():
|
|
|
|
|
|
check_ffmpeg_installed()
|
|
|
|
|
|
config = load_config()
|
|
|
|
|
|
|
2025-05-07 16:20:43 +08:00
|
|
|
|
# 添加 stdin 监控任务
|
|
|
|
|
|
stdin_task = asyncio.create_task(monitor_stdin())
|
|
|
|
|
|
|
2025-03-05 23:13:24 +08:00
|
|
|
|
# 启动 WebSocket 服务器
|
|
|
|
|
|
ws_server = WebSocketServer(config)
|
|
|
|
|
|
ws_task = asyncio.create_task(ws_server.start())
|
2025-04-27 13:07:14 +08:00
|
|
|
|
ota_task = None
|
2025-03-05 23:13:24 +08:00
|
|
|
|
|
2025-04-27 13:07:14 +08:00
|
|
|
|
read_config_from_api = config.get("read_config_from_api", False)
|
|
|
|
|
|
if not read_config_from_api:
|
2025-04-27 13:55:04 +08:00
|
|
|
|
# 启动 Simple OTA 服务器
|
2025-04-27 13:07:14 +08:00
|
|
|
|
ota_server = SimpleOtaServer(config)
|
|
|
|
|
|
ota_task = asyncio.create_task(ota_server.start())
|
|
|
|
|
|
|
|
|
|
|
|
logger.bind(tag=TAG).info(
|
|
|
|
|
|
"OTA接口是\t\thttp://{}:{}/xiaozhi/ota/",
|
|
|
|
|
|
get_local_ip(),
|
|
|
|
|
|
config["server"]["ota_port"],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 获取WebSocket配置,使用安全的默认值
|
|
|
|
|
|
websocket_port = 8000
|
|
|
|
|
|
server_config = config.get("server", {})
|
|
|
|
|
|
if isinstance(server_config, dict):
|
|
|
|
|
|
websocket_port = int(server_config.get("port", 8000))
|
|
|
|
|
|
|
|
|
|
|
|
logger.bind(tag=TAG).info(
|
|
|
|
|
|
"Websocket地址是\tws://{}:{}/xiaozhi/v1/",
|
|
|
|
|
|
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"
|
|
|
|
|
|
)
|
2025-04-26 21:56:41 +08:00
|
|
|
|
|
2025-03-05 23:13:24 +08:00
|
|
|
|
try:
|
2025-05-07 16:20:43 +08:00
|
|
|
|
await wait_for_exit() # 阻塞直到收到退出信号
|
2025-03-12 10:57:25 +08:00
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
|
|
print("任务被取消,清理资源中...")
|
2025-03-05 23:13:24 +08:00
|
|
|
|
finally:
|
2025-05-07 16:20:43 +08:00
|
|
|
|
# 取消所有任务(关键修复点)
|
|
|
|
|
|
stdin_task.cancel()
|
2025-03-05 23:13:24 +08:00
|
|
|
|
ws_task.cancel()
|
2025-04-27 13:07:14 +08:00
|
|
|
|
if ota_task:
|
|
|
|
|
|
ota_task.cancel()
|
2025-05-07 16:20:43 +08:00
|
|
|
|
|
|
|
|
|
|
# 等待任务终止(必须加超时)
|
|
|
|
|
|
await asyncio.wait(
|
|
|
|
|
|
[stdin_task, ws_task, ota_task] if ota_task else [stdin_task, ws_task],
|
|
|
|
|
|
timeout=3.0,
|
|
|
|
|
|
return_when=asyncio.ALL_COMPLETED
|
|
|
|
|
|
)
|
2025-03-12 10:57:25 +08:00
|
|
|
|
print("服务器已关闭,程序退出。")
|
2025-03-05 23:13:24 +08:00
|
|
|
|
|
2025-04-12 17:36:04 +08:00
|
|
|
|
|
2025-03-05 23:13:24 +08:00
|
|
|
|
if __name__ == "__main__":
|
2025-03-12 10:57:25 +08:00
|
|
|
|
try:
|
|
|
|
|
|
asyncio.run(main())
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
|
print("手动中断,程序终止。")
|