fix(xiaozhi): 网络异常后 ws 重新连接

This commit is contained in:
Del Wang
2025-06-04 22:25:18 +08:00
parent 96d69ec8a6
commit 0075f13ffd
2 changed files with 40 additions and 33 deletions
@@ -3,7 +3,9 @@ import json
import websockets import websockets
from xiaozhi.ref import get_xiaozhi
from xiaozhi.services.protocols.protocol import Protocol from xiaozhi.services.protocols.protocol import Protocol
from xiaozhi.services.protocols.typing import DeviceState
from xiaozhi.utils.config import ConfigManager from xiaozhi.utils.config import ConfigManager
@@ -27,9 +29,20 @@ class WebsocketProtocol(Protocol):
self.CLIENT_ID = self.config.get_client_id() self.CLIENT_ID = self.config.get_client_id()
self.DEVICE_ID = self.config.get_device_id() self.DEVICE_ID = self.config.get_device_id()
async def _close_websocket(self):
if self.websocket:
try:
await self.websocket.close()
self.websocket = None
self.connected = False
except Exception:
pass
async def connect(self) -> bool: async def connect(self) -> bool:
"""连接到WebSocket服务器""" """连接到WebSocket服务器"""
try: try:
await self._close_websocket()
# 在连接时创建 Event,确保在正确的事件循环中 # 在连接时创建 Event,确保在正确的事件循环中
self.hello_received = asyncio.Event() self.hello_received = asyncio.Event()
@@ -72,7 +85,6 @@ class WebsocketProtocol(Protocol):
if self.on_network_error: if self.on_network_error:
self.on_network_error("等待响应超时") self.on_network_error("等待响应超时")
return False return False
except Exception as e: except Exception as e:
if self.on_network_error: if self.on_network_error:
self.on_network_error(f"无法连接服务: {str(e)}") self.on_network_error(f"无法连接服务: {str(e)}")
@@ -95,14 +107,10 @@ class WebsocketProtocol(Protocol):
pass pass
elif self.on_incoming_audio: elif self.on_incoming_audio:
self.on_incoming_audio(message) self.on_incoming_audio(message)
except websockets.ConnectionClosed: except Exception:
self.connected = False self.connected = False
if self.on_audio_channel_closed: if self.on_audio_channel_closed:
await self.on_audio_channel_closed() await self.on_audio_channel_closed()
except Exception as e:
self.connected = False
if self.on_network_error:
self.on_network_error(f"连接错误: {str(e)}")
async def send_audio(self, frames: list[bytes]): async def send_audio(self, frames: list[bytes]):
"""发送音频数据""" """发送音频数据"""
@@ -112,9 +120,8 @@ class WebsocketProtocol(Protocol):
try: try:
for frame in frames: for frame in frames:
await self.websocket.send(frame) await self.websocket.send(frame)
except Exception as e: except Exception:
if self.on_network_error: pass
self.on_network_error(f"发送音频失败: {str(e)}")
async def send_text(self, message: str): async def send_text(self, message: str):
"""发送文本消息""" """发送文本消息"""
@@ -122,24 +129,18 @@ class WebsocketProtocol(Protocol):
try: try:
await self.websocket.send(message) await self.websocket.send(message)
except Exception as e: except Exception as e:
await self.close_audio_channel() raise e
if self.on_network_error:
self.on_network_error(f"发送消息失败: {str(e)}")
def is_audio_channel_opened(self) -> bool: def is_audio_channel_opened(self) -> bool:
"""检查音频通道是否打开""" """检查音频通道是否打开"""
return self.websocket is not None and self.connected return self.websocket is not None and self.connected
async def open_audio_channel(self) -> bool: _is_heartbeat_running = False
"""建立 WebSocket 连接
如果尚未连接,则创建新的 WebSocket 连接 async def open_audio_channel(self):
Returns: if not self._is_heartbeat_running:
bool: 连接是否成功 self._is_heartbeat_running = True
""" asyncio.create_task(self.heartbeat())
if not self.connected: await self.connect()
return await self.connect()
return True
async def _handle_server_hello(self, data: dict): async def _handle_server_hello(self, data: dict):
"""处理服务器的 hello 消息 """处理服务器的 hello 消息
@@ -194,3 +195,15 @@ class WebsocketProtocol(Protocol):
await self.on_audio_channel_closed() await self.on_audio_channel_closed()
except Exception: except Exception:
pass pass
async def heartbeat(self):
while True:
if self.websocket and get_xiaozhi().device_state == DeviceState.IDLE:
try:
await self.send_text(
json.dumps({"session_id": "", "type": "abort"})
)
except Exception:
# 发送心跳失败,重新连接
await self.open_audio_channel()
await asyncio.sleep(1)
+4 -10
View File
@@ -134,6 +134,8 @@ class XiaoZhi:
except Exception as e: except Exception as e:
self.alert("错误", f"初始化音频设备失败: {e}") self.alert("错误", f"初始化音频设备失败: {e}")
threading.Thread(target=self._audio_input_event_trigger, daemon=True).start()
def _initialize_display(self): def _initialize_display(self):
"""初始化显示界面""" """初始化显示界面"""
if get_env("CLI"): if get_env("CLI"):
@@ -300,20 +302,12 @@ class XiaoZhi:
async def _on_audio_channel_opened(self): async def _on_audio_channel_opened(self):
"""音频通道打开回调""" """音频通道打开回调"""
self.set_device_state(DeviceState.IDLE) self.set_device_state(DeviceState.IDLE)
threading.Thread(target=self._audio_input_event_trigger, daemon=True).start()
def _audio_input_event_trigger(self): def _audio_input_event_trigger(self):
"""音频输入事件触发器""" """音频输入事件触发器"""
while self.running: while self.running:
try: if self.audio_codec.input_stream.is_active():
if self.audio_codec.input_stream.is_active(): self.events[EventType.AUDIO_INPUT_READY_EVENT].set()
self.events[EventType.AUDIO_INPUT_READY_EVENT].set()
except OSError as e:
if "Stream not open" in str(e):
break
except Exception:
pass
time.sleep(0.01) time.sleep(0.01)
async def _on_audio_channel_closed(self): async def _on_audio_channel_closed(self):