| | |
| | | from core.utils.voiceprint_provider import VoiceprintProvider |
| | | from core.utils.util import get_system_error_response |
| | | from core.utils import textUtils |
| | | from core.websocket_handshake import is_client_hello, is_priority_listen_detect |
| | | |
| | | |
| | | TAG = __name__ |
| | |
| | | self.loop = None # 在 handle_connection 中获取运行中的事件循环 |
| | | self.stop_event = threading.Event() |
| | | self.executor = ThreadPoolExecutor(max_workers=5) |
| | | self._tts_channel_lock = threading.Lock() |
| | | |
| | | # 添加上报线程池 |
| | | self.report_queue = queue.Queue() |
| | |
| | | self.tool_allowlist = [] |
| | | self.original_system_prompt = None |
| | | self.awaiting_voice_app_choice = False |
| | | self.shadowing_index = 0 |
| | | self.shadowing_prompt = "" |
| | | self.shadowing_clips = [] |
| | | self.shadowing_awaiting_user = False |
| | | self.shadowing_clip_gen = 0 |
| | | self.shadowing_timeout_task = None |
| | | self.shadowing_assess_index = 0 |
| | | self.last_asr_language = None |
| | | self.task_session = None |
| | | self.last_asr_audio_snapshot = None |
| | | self.shadowing_listen_pcm = [] |
| | | self.shadowing_audio_saved_index = -1 |
| | | |
| | | async def handle_connection(self, ws: websockets.ServerConnection): |
| | | try: |
| | |
| | | |
| | | # 认证通过,继续处理 |
| | | self.websocket = ws |
| | | if self.server is not None: |
| | | await self.server.register_device_handler(self.device_id, self) |
| | | |
| | | # 检查是否来自MQTT连接 |
| | | request_path = ws.request.path |
| | |
| | | # 启动AEC缓存清理任务 |
| | | self._aec_cache_cleanup_task = asyncio.create_task(self._check_aec_cache_expiry()) |
| | | |
| | | self.welcome_msg = self.config["xiaozhi"] |
| | | self.welcome_msg = copy.deepcopy(self.config.get("xiaozhi") or {}) |
| | | self.welcome_msg["session_id"] = self.session_id |
| | | |
| | | # 从配置中读取采样率 |
| | |
| | | |
| | | async def _route_message(self, message): |
| | | """消息路由""" |
| | | # hello 必须立刻应答。绑定状态还在拉配置时如果丢掉 hello, |
| | | # 设备会卡在 handshake/hello 等待,表现为连不上语音服务。 |
| | | if is_client_hello(message) or is_priority_listen_detect(message): |
| | | await handleTextMessage(self, message) |
| | | return |
| | | |
| | | # 检查是否已经获取到真实的绑定状态 |
| | | if not self.bind_completed_event.is_set(): |
| | | # 还没有获取到真实状态,等待直到获取到真实状态或超时 |
| | |
| | | ) |
| | | ) |
| | | |
| | | def _initialize_components(self): |
| | | try: |
| | | def _tts_config_ready(self) -> bool: |
| | | selected = (self.config.get("selected_module") or {}).get("TTS") |
| | | providers = self.config.get("TTS") or {} |
| | | return bool(selected) and selected in providers |
| | | |
| | | async def _open_tts_channels(self): |
| | | """Open TTS worker threads. Must run on the connection event loop after TTS config exists.""" |
| | | if self.tts is None and not self._tts_config_ready(): |
| | | self.logger.bind(tag=TAG).info("TTS配置尚未从智控台加载,延后打开通道") |
| | | return |
| | | with self._tts_channel_lock: |
| | | if self.tts is None: |
| | | self.tts = self._initialize_tts() |
| | | # 打开语音合成通道 |
| | | asyncio.run_coroutine_threadsafe( |
| | | self.tts.open_audio_channels(self), self.loop |
| | | ) |
| | | tts = self.tts |
| | | if tts is None: |
| | | self.logger.bind(tag=TAG).warning("TTS实例不可用,无法打开通道") |
| | | return |
| | | await tts.open_audio_channels(self) |
| | | play = getattr(tts, "audio_play_priority_thread", None) |
| | | text_th = getattr(tts, "tts_priority_thread", None) |
| | | if play and play.is_alive() and text_th and text_th.is_alive(): |
| | | self.logger.bind(tag=TAG).info("TTS通道已打开") |
| | | else: |
| | | self.logger.bind(tag=TAG).error("TTS通道打开后工作线程未运行") |
| | | |
| | | def _ensure_tts_channels(self): |
| | | """Start TTS worker threads once; used after private-config init.""" |
| | | try: |
| | | if self.loop is None: |
| | | return |
| | | fut = asyncio.run_coroutine_threadsafe(self._open_tts_channels(), self.loop) |
| | | fut.result(timeout=15) |
| | | except Exception as e: |
| | | self.logger.bind(tag=TAG).error(f"打开TTS通道失败: {e}") |
| | | |
| | | def _initialize_components(self): |
| | | try: |
| | | if self.tts is not None: |
| | | self._ensure_tts_channels() |
| | | if self.need_bind: |
| | | self.bind_completed_event.set() |
| | | return |
| | |
| | | def _initialize_tts(self): |
| | | """初始化TTS""" |
| | | tts = None |
| | | if not self.need_bind: |
| | | if not self.need_bind and self._tts_config_ready(): |
| | | tts = initialize_tts(self.config) |
| | | elif not self._tts_config_ready(): |
| | | self.logger.bind(tag=TAG).info("跳过TTS初始化:配置中没有可用的TTS模块") |
| | | return None |
| | | |
| | | if tts is None: |
| | | tts = DefaultTTS(self.config, delete_audio_file=True) |
| | |
| | | modules = {} |
| | | if modules.get("tts", None) is not None: |
| | | self.tts = modules["tts"] |
| | | if self.tts is not None: |
| | | try: |
| | | await self._open_tts_channels() |
| | | except Exception as e: |
| | | self.logger.bind(tag=TAG).error(f"打开TTS通道失败: {e}") |
| | | if modules.get("vad", None) is not None: |
| | | self.vad = modules["vad"] |
| | | if modules.get("asr", None) is not None: |
| | |
| | | |
| | | self.logger.bind(tag=TAG).info("聊天记录上报线程已退出") |
| | | |
| | | def _process_report(self, type, text, audio_data, report_time): |
| | | def _process_report(self, type, text, audio_data, report_time, task_instance_id=None, step_index=None): |
| | | """处理上报任务""" |
| | | try: |
| | | # 执行异步上报(在事件循环中运行) |
| | | asyncio.run(report(self, type, text, audio_data, report_time)) |
| | | asyncio.run(report(self, type, text, audio_data, report_time, task_instance_id, step_index)) |
| | | except Exception as e: |
| | | self.logger.bind(tag=TAG).error(f"上报处理异常: {e}") |
| | | finally: |
| | |
| | | async def close(self, ws=None): |
| | | """资源清理方法""" |
| | | try: |
| | | if self.server is not None: |
| | | await self.server.unregister_device_handler(self.device_id, self) |
| | | try: |
| | | from core.app_runtime.session import end_session |
| | | |
| | | end_session(self, "disconnected") |
| | | except Exception as session_error: |
| | | self.logger.bind(tag=TAG).warning(f"断线 flush TaskSession 失败: {session_error}") |
| | | |
| | | # 清理 VAD 连接资源 |
| | | if ( |
| | | hasattr(self, "vad") |