拆分vc_session ,io 为ws数据收发,media为录音播放。
authorsnickersun <snickersun@163.com>
Wed, 20 May 2026 08:03:46 +0000 (16:03 +0800)
committersnickersun <snickersun@163.com>
Wed, 20 May 2026 08:03:46 +0000 (16:03 +0800)
1.doc/VC_SESSION_ARCH.md [new file with mode: 0644]
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/Makefile
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/ai_VC.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_fsm.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_session.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_session_imports.py [new file with mode: 0644]
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_session_io.py [new file with mode: 0644]
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_session_media.py [new file with mode: 0644]

diff --git a/1.doc/VC_SESSION_ARCH.md b/1.doc/VC_SESSION_ARCH.md
new file mode 100644 (file)
index 0000000..7f1f900
--- /dev/null
@@ -0,0 +1,129 @@
+# VC 会话模块:目录与依赖规则
+
+> 原则:**适度拆分 + 编排集中 + 子模块不互调**  
+> 适用:`7PIN_1_5` / `Helios_SDK` 下 `quectel/core`,MicroPython frozen(`.mpy`)。
+
+**当前形态**:`vc_session.py`(编排)+ `vc_session_io.py`(WS)+ `vc_session_media.py`(采播)+ `vc_session_imports.py`(共享 import)。
+
+---
+
+## 1. 目录结构
+
+```
+core/
+├── ai_VC.py                 # re-export;frozen 名不变
+├── vc_session.py            # 【编排】按键/PTT、AI_VC_start/Close、定时器、FSM、__init__
+├── vc_session_io.py         # 【IO】WS 建链/断链、send/recv 线程、协议 JSON 分发
+├── vc_session_media.py      # 【媒体】ASR 上行/录音、TTS/播放队列、本地采播
+├── vc_session_imports.py    # 子模块共享 import(仅 session/io/media)
+├── vc_fsm.py                # 纯 FSM
+├── vc_auth / vc_buttons / vc_led / …   # 外围,不 import vc_session
+├── protocol.py              # IOT 心跳 WS(≠ VC WS)
+└── main_init.py             # import ai_VC
+```
+
+| 文件 | 约行数 | 方法数(参考) |
+|------|--------|----------------|
+| `vc_session.py` | ~2250 | 编排 ~97 |
+| `vc_session_io.py` | ~1730 | IO ~31 |
+| `vc_session_media.py` | ~1660 | 媒体 ~76 |
+
+重新拆分:在 `core/` 下执行 `python _resplit_vc_session.py`(会按 `FORCE_IO` / `FORCE_MEDIA` / `FORCE_CORE` 从单体 `vc_session.py` 生成;**运行前请备份**)。
+
+---
+
+## 2. 依赖方向
+
+```mermaid
+flowchart TD
+    main_init --> ai_VC
+    ai_VC --> vc_session
+    vc_session --> vc_session_io
+    vc_session --> vc_session_media
+    vc_session_io --> vc_session_imports
+    vc_session_media --> vc_session_imports
+    vc_session --> vc_session_imports
+    vc_session_imports --> vc_fsm
+    vc_session_imports --> vc_common
+```
+
+| 规则 | 说明 |
+|------|------|
+| L2 编排 | 唯一 `class AI_VC` 定义处:`AI_VC(VCSessionIoMixin, VCSessionMediaMixin, Abstract)` |
+| L3 io / media | **禁止互相 import**;**禁止 import vc_session** |
+| 外围 `vc_*` | **禁止 import vc_session**;只 EventMesh 订阅 |
+
+---
+
+## 3. 禁止依赖(硬约束)
+
+1. **`vc_session_io` ↔ `vc_session_media` 禁止互 import**
+2. **io / media 禁止 import `vc_session`**
+3. **io / media 禁止 `publish AI_VC_start` / `Close` 或改 `_vc_session_started`**(可 `publish` 细粒度事件如 `set_led_state`)
+4. **建链、recv/send 线程仅在 io**;**录音、播放队列仅在 media**
+5. **按键不得直接 `websocket_connect_VC()`** → btn 队列 → `AI_key_press` → `publish_AI_btnInfo(AI_VC_start)`
+6. **线程启动失败**:`[VC] ws thread start failed`,`isconnect=0`,禁止仅打印 `ok` 无 `end`
+7. **Makefile frozen**:须含 `vc_session_imports.mpy`、`vc_session_io.mpy`、`vc_session_media.mpy`、`vc_session.mpy`
+
+### 3.1 `import *` 与 `_thread`(易踩坑)
+
+`from vc_session_imports import *` **不会**导入 `vc_session_imports` 里**以下划线开头**的绑定(例如 `import _thread` 在 imports 里存在,也不会进 io/media 的全局名)。凡 mixin 内调用 `_thread.start_new_thread`,必须在 **`vc_session_io.py` / `vc_session_media.py` / `vc_session.py` 各写一行 `import _thread`**。重跑 `_resplit_vc_session.py` 后若丢行,按此补回。
+
+---
+
+## 4. 职责划分
+
+### `vc_session.py`(编排)
+
+- `__init__`、EventMesh 订阅、模式/留言/会议/翻译
+- `AI_VC_start` / `Close` / `VC_init`、`check_vc_start`、idle 定时器
+- `AI_key_press` / `AI_key_release` / PTT / `ptt_begin`
+- `_fsm_apply_actions`、`_vc_publish_led`、`_vc_dispatch` 的**编排侧**(实现在 io 的 recv 里调用 `self._fsm_apply_actions`)
+
+### `vc_session_io.py`(WS only)
+
+- `websocket_connect_VC` / `websocket_deconnect_VC` / `websocket_error`
+- `AI_client_VC_recv`、send 队列线程、`AI_client_VC_send`、ping、Start 请求
+- `check_text` / `_vc_dispatch_server_event`(收包分发)
+- `request_send_*`(经 btn 队列发 WS)
+
+### `vc_session_media.py`(无 connect)
+
+- `VC_Speech_*` / `VC_record_*`、`_uplink_*`、`request_uplink_*`
+- `VC_Play_*`、`AI_audio_queue_*`、`play_tts`、留言/音乐播放回调
+- `_vc_tts_*`、语速/音色
+
+---
+
+## 5. 状态单写者
+
+| 状态 | 写入模块 |
+|------|----------|
+| `AI_client_VC_isconnect`、WS 线程 id | io |
+| `VC_record_state_flag`、TTS 队列 | media |
+| `_vc_session_started`、`Close_flag`、`VC_satrt_flag` | 编排(`vc_session`) |
+| `fsm.session` / `fsm.turn` | `vc_fsm` + `_fsm_apply_actions` |
+
+---
+
+## 6. 发布前检查
+
+- [ ] `py_compile` 通过:`vc_session.py` / `vc_session_io.py` / `vc_session_media.py`
+- [ ] `vc_session_io` / `vc_session_media` / `vc_session` 顶部均有 **`import _thread`**(`import *` 不导入 `_thread`)
+- [ ] io↔media 无互 import(`rg "from vc_session_" vc_session_io.py vc_session_media.py`)
+- [ ] Makefile 含四个 `vc_session*.mpy`
+- [ ] 开机:`websocket_connect_VC end` → `AI_VC_start3` → `rx event=started`
+- [ ] 短按:`[KEY] long wake` 或 `fallback`,无 `unhandled mode branch`
+- [ ] 改 `vc_auth` / `vc_buttons`:显式 import 冒烟
+
+---
+
+## 7. 不建议
+
+- 再拆 `vc_session_ws/asr/tts` 三文件或 `key/uplink/play` 横向切片
+- io 里做 TTS 播完判定;media 里做 WS 建链
+- 合并 `protocol` IOT WS 与 VC WS
+
+---
+
+*维护:`_resplit_vc_session.py` 中 `FORCE_IO` / `FORCE_MEDIA` / `FORCE_CORE` 调整归属后重跑脚本。*
index 295ff72..152fe30 100644 (file)
@@ -978,6 +978,9 @@ frozen_mpy_list =   $(BUILD)/_boot.mpy \
                                        $(BUILD)/vc_auth.mpy \
                                        $(BUILD)/vc_ota.mpy \
                                        $(BUILD)/vc_fsm.mpy \
+                                       $(BUILD)/vc_session_imports.mpy \
+                                       $(BUILD)/vc_session_io.mpy \
+                                       $(BUILD)/vc_session_media.mpy \
                                        $(BUILD)/vc_session.mpy \
                                        $(BUILD)/ai_VC.mpy \
                                        $(BUILD)/ai_alarm.mpy \
index d1cfe0f..3f41e1e 100644 (file)
@@ -1,50 +1,50 @@
-# -*- coding: utf-8 -*-\r
-"""\r
-语音对话入口(兼容 frozen 模块名 ai_VC)。\r
-实现已拆分至 vc_* 子模块,本文件仅 re-export。\r
-"""\r
-from vc_led import AI_led\r
-from vc_hw import AI_hw_status, set_ipv4_datacall\r
-from vc_uart import AI_UART\r
-from vc_buttons import AI_dev, audio_buf\r
-from vc_storage import getStorageDataManager\r
-from vc_auth import device_res_author\r
-from vc_ota import OTA\r
-from vc_fsm import (\r
-    VCSessionState,\r
-    VCTurnState,\r
-    VC_TtsRxPhase,\r
-    VCMode,\r
-    VCStateMachine,\r
-)\r
-from vc_session import AI_VC\r
-from vc_utils import num_str_to_chinese\r
-from vc_common import (\r
-    dev_type,\r
-    VC_HEARTBEAT_LOG,\r
-    VC_PROTO_GUARDS,\r
-    VC_WS_IO_LOG,\r
-    _VC_KEEPALIVE_RX_EVENTS,\r
-    _vc_out_json_event_meta,\r
-    PRESS_AUDIO_MODE,\r
-)\r
-\r
-__all__ = [\r
-    "AI_led",\r
-    "AI_hw_status",\r
-    "set_ipv4_datacall",\r
-    "AI_UART",\r
-    "AI_dev",\r
-    "audio_buf",\r
-    "getStorageDataManager",\r
-    "device_res_author",\r
-    "OTA",\r
-    "VCSessionState",\r
-    "VCTurnState",\r
-    "VC_TtsRxPhase",\r
-    "VCMode",\r
-    "VCStateMachine",\r
-    "AI_VC",\r
-    "num_str_to_chinese",\r
-    "dev_type",\r
-]\r
+# -*- coding: utf-8 -*-
+"""
+语音对话入口(兼容 frozen 模块名 ai_VC)。
+会话:vc_session(编排)+ vc_session_io + vc_session_media;其它 vc_* 为外围。本文件仅 re-export。
+"""
+from vc_led import AI_led
+from vc_hw import AI_hw_status, set_ipv4_datacall
+from vc_uart import AI_UART
+from vc_buttons import AI_dev, audio_buf
+from vc_storage import getStorageDataManager
+from vc_auth import device_res_author
+from vc_ota import OTA
+from vc_fsm import (
+    VCSessionState,
+    VCTurnState,
+    VC_TtsRxPhase,
+    VCMode,
+    VCStateMachine,
+)
+from vc_session import AI_VC
+from vc_utils import num_str_to_chinese
+from vc_common import (
+    dev_type,
+    VC_HEARTBEAT_LOG,
+    VC_PROTO_GUARDS,
+    VC_WS_IO_LOG,
+    _VC_KEEPALIVE_RX_EVENTS,
+    _vc_out_json_event_meta,
+    PRESS_AUDIO_MODE,
+)
+
+__all__ = [
+    "AI_led",
+    "AI_hw_status",
+    "set_ipv4_datacall",
+    "AI_UART",
+    "AI_dev",
+    "audio_buf",
+    "getStorageDataManager",
+    "device_res_author",
+    "OTA",
+    "VCSessionState",
+    "VCTurnState",
+    "VC_TtsRxPhase",
+    "VCMode",
+    "VCStateMachine",
+    "AI_VC",
+    "num_str_to_chinese",
+    "dev_type",
+]
index 80c6228..4ac2468 100644 (file)
-# -*- coding: utf-8 -*-\r
-"""VC 会话状态机。"""\r
-try:\r
-    from qp_log import qp_print as print\r
-except Exception:\r
-    pass\r
-\r
-VC_record_TYPE_PCM = 0\r
-VC_record_TYPE_AMR = 1\r
-VC_play_TYPE_PCM = 0\r
-VC_play_TYPE_MP3 = 1\r
-\r
-#websocket发送数据编号\r
-# AI_client_asr_num = 0\r
-# AI_client_chat_num = 1\r
-# AI_client_tts_num = 2\r
-AI_client_VC_num = 3\r
-AI_client_asr_num = 4\r
-\r
-\r
-class VCSessionState:\r
-    Disconnected = 0\r
-    Connecting = 1\r
-    Starting = 2\r
-    Started = 3\r
-\r
-\r
-class VCTurnState:\r
-    Idle = 0\r
-    Listening = 1\r
-    SpeechStreaming = 2\r
-    SpeechEnded = 3\r
-    Thinking = 4\r
-    TtsReceiving = 5\r
-    Playing = 6\r
-    AwaitPlayedAck = 7\r
-\r
-\r
-class VC_TtsRxPhase:\r
-    Idle = 0\r
-    Started = 1\r
-    ServerCompleted = 2\r
-\r
-\r
-class VCMode:\r
-    Realtime = 0       # VAD on(长对话,协议:无需 audio.create/completed)\r
-    PushToTalk = 1     # VAD off(单对话,协议:必须 audio.create/completed 包裹音频帧)\r
-\r
-\r
-class VCStateMachine:\r
-    # 轻量状态机骨架(先落地骨架与动作接口,后续逐步把旧 if/flag 逻辑收敛进来)。\r
-    #\r
-    # 约束来自《金三服务-语音对话V2.5》:\r
-    # - 收到 started 后才允许发音频帧(否则 session not initialized)\r
-    # - VAD 开启:不需要 audio.create/completed\r
-    # - VAD 关闭:必须 audio.create → frames → audio.completed\r
-    # - 播放结束 audio.played 是进入下一轮的门控信号之一(尤其关键词打断场景)\r
-\r
-    def __init__(self, vc):\r
-        self.vc = vc\r
-        self.session = VCSessionState.Disconnected\r
-        self.turn = VCTurnState.Idle\r
-        self.last_llm_ms = 0\r
-        self.last_tts_pending_ms = 0\r
-        self.await_played = False\r
-        self.last_tts_completed_ms = 0\r
-        self.tts_rx_phase = VC_TtsRxPhase.Idle\r
-        self.speech_uplink_open = 0\r
-\r
-    def reset_rx_phases(self):\r
-        self.tts_rx_phase = VC_TtsRxPhase.Idle\r
-        self.speech_uplink_open = 0\r
-\r
-    def mark_tts_rx_idle(self):\r
-        self.tts_rx_phase = VC_TtsRxPhase.Idle\r
-\r
-    def on_server_asr_started(self):\r
-        if self.session == VCSessionState.Started:\r
-            self.speech_uplink_open = 1\r
-        self.log("server.asr.started")\r
-\r
-    def _turn_name(self, t):\r
-        try:\r
-            return {\r
-                VCTurnState.Idle: "Idle",\r
-                VCTurnState.Listening: "Listening",\r
-                VCTurnState.SpeechStreaming: "SpeechStreaming",\r
-                VCTurnState.SpeechEnded: "SpeechEnded",\r
-                VCTurnState.Thinking: "Thinking",\r
-                VCTurnState.TtsReceiving: "TtsReceiving",\r
-                VCTurnState.Playing: "Playing",\r
-                VCTurnState.AwaitPlayedAck: "AwaitPlayedAck",\r
-            }.get(t, str(t))\r
-        except Exception:\r
-            return str(t)\r
-\r
-    def _session_name(self, s):\r
-        try:\r
-            return {\r
-                VCSessionState.Disconnected: "Disconnected",\r
-                VCSessionState.Connecting: "Connecting",\r
-                VCSessionState.Starting: "Starting",\r
-                VCSessionState.Started: "Started",\r
-            }.get(s, str(s))\r
-        except Exception:\r
-            return str(s)\r
-\r
-    def log(self, reason=""):\r
-        # 只用于关键节点的单行状态日志,便于串口快速定位状态漂移\r
-        try:\r
-            print("[FSM] {} session={} turn={} await_played={} tts_rx={} speech_up={}".format(\r
-                reason,\r
-                self._session_name(self.session),\r
-                self._turn_name(self.turn),\r
-                1 if self.await_played else 0,\r
-                int(self.tts_rx_phase),\r
-                1 if self.speech_uplink_open else 0,\r
-            ))\r
-        except Exception:\r
-            pass\r
-\r
-    def mode(self):\r
-        try:\r
-            return VCMode.Realtime if self.vc.VC_mode == 0 else VCMode.PushToTalk\r
-        except Exception:\r
-            return VCMode.Realtime\r
-\r
-    def on_session_start_request(self):\r
-        self.session = VCSessionState.Starting\r
-        self.turn = VCTurnState.Idle\r
-        # 新会话开始时必须清空上一轮“待播完”状态,否则会出现:\r
-        # - 切换模式/重连后 await_played 仍为 True → play_end 逻辑抢跑/不触发 → 无法自动开麦\r
-        self.await_played = False\r
-        self.last_tts_completed_ms = 0\r
-        self.last_llm_ms = 0\r
-        self.reset_rx_phases()\r
-\r
-    def on_server_started(self):\r
-        self.reset_rx_phases()\r
-        self.session = VCSessionState.Started\r
-        # 新会话 started:确保进入 Listening,且清理上轮播放门控\r
-        self.await_played = False\r
-        self.last_tts_completed_ms = 0\r
-        if self.turn in (VCTurnState.Idle, VCTurnState.AwaitPlayedAck):\r
-            self.turn = VCTurnState.Listening\r
-        self.log("server.started")\r
-\r
-    def on_server_vad_silence(self):\r
-        if self.session == VCSessionState.Started:\r
-            self.turn = VCTurnState.SpeechEnded\r
-        self.log("server.vad.silence")\r
-\r
-    def on_server_llm_created(self):\r
-        if self.session == VCSessionState.Started:\r
-            self.turn = VCTurnState.Thinking\r
-            try:\r
-                self.last_llm_ms = utime.ticks_ms()\r
-            except Exception:\r
-                self.last_llm_ms = 1\r
-        self.log("server.llm.created")\r
-\r
-    def on_server_tts_started(self):\r
-        if self.session == VCSessionState.Started:\r
-            self.turn = VCTurnState.TtsReceiving\r
-            self.tts_rx_phase = VC_TtsRxPhase.Started\r
-        self.log("server.tts.started")\r
-\r
-    def on_server_tts_completed(self):\r
-        if self.session == VCSessionState.Started:\r
-            # tts.completed 只表示服务端音频下发结束,本地可能仍在播放;保持 Playing,由 await_played 门控播完上报\r
-            self.await_played = True\r
-            self.tts_rx_phase = VC_TtsRxPhase.ServerCompleted\r
-            try:\r
-                self.last_tts_completed_ms = utime.ticks_ms()\r
-            except Exception:\r
-                self.last_tts_completed_ms = 1\r
-        self.log("server.tts.completed")\r
-\r
-    def on_server_canceled(self):\r
-        if self.session == VCSessionState.Started:\r
-            self.turn = VCTurnState.Listening\r
-        self.reset_rx_phases()\r
-        self.log("server.canceled")\r
-\r
-    def on_server_completed(self):\r
-        if self.session == VCSessionState.Started:\r
-            # 本轮结束:默认回到 Listening;是否自动开麦由 action 门控决定\r
-            # 若仍在等待本地播完(await_played),completed 不应把 turn 拉回 Listening,\r
-            # 否则会触发 completed→uplink_start 抢跑,打断播放链路。\r
-            if not self.await_played and self.turn != VCTurnState.AwaitPlayedAck:\r
-                self.turn = VCTurnState.Listening\r
-        self.log("server.completed")\r
-\r
-    def on_server_event(self, event, jsload_data=None):\r
-        # 事件→动作(action list)适配层。\r
-        # 现阶段只产出“可幂等的控制动作”(uplink start/stop),避免和旧逻辑重复触发播放等副作用。\r
-        # 后续会逐步把更多行为(play_start/audio.played/ptt create/completed)迁移到这里。\r
-        actions = []\r
-        try:\r
-            # LLM 阶段必须驱动 FSM.turn,否则 turn 会停在 SpeechEnded,导致:\r
-            # - _fsm_timeout_table_tick 里「Thinking 超时 / synthesis 超时」永不命中\r
-            # - 与注释和 force_tts 前置条件不一致\r
-            if event == "llm.created":\r
-                self.on_server_llm_created()\r
-                return actions\r
-            if event == "llm.completed":\r
-                # 若对端偶发跳过 llm.created,用 completed 把 turn 补到 Thinking,避免兜底全失效\r
-                try:\r
-                    if self.session == VCSessionState.Started:\r
-                        if self.turn in (VCTurnState.SpeechEnded, VCTurnState.Listening):\r
-                            self.turn = VCTurnState.Thinking\r
-                except Exception:\r
-                    pass\r
-                return actions\r
-            if event == "started":\r
-                self.on_server_started()\r
-                return actions\r
-            if event == "vad.silence":\r
-                self.on_server_vad_silence()\r
-                actions.append(("uplink_stop", "vad.silence"))\r
-                return actions\r
-            if event == "tts.started":\r
-                self.on_server_tts_started()\r
-                actions.append(("uplink_stop", "tts.started"))\r
-                actions.append(("play_start", "tts.started"))\r
-                return actions\r
-            if event == "tts.completed":\r
-                self.on_server_tts_completed()\r
-                # 播放可能已在 started 时开启,这里不再强制 play_start,避免反复 open PCM\r
-                return actions\r
-            if event == "canceled":\r
-                self.on_server_canceled()\r
-                # canceled:仅长对话允许“立刻回到聆听并尝试开麦”(由 _uplink_request_start 门控保证安全幂等)\r
-                try:\r
-                    if self.mode() == VCMode.Realtime and getattr(self.vc, "Close_flag", 0) == 0:\r
-                        actions.append(("uplink_start", "canceled"))\r
-                except Exception:\r
-                    pass\r
-                return actions\r
-            if event == "completed":\r
-                self.on_server_completed()\r
-                # completed:若不在等待 audio.played(说明本轮无 TTS),仅长对话可直接进入下一轮聆听\r
-                try:\r
-                    if self.mode() == VCMode.Realtime and getattr(self.vc, "Close_flag", 0) == 0:\r
-                        if (not getattr(self, "await_played", False)) and self.turn != VCTurnState.AwaitPlayedAck:\r
-                            actions.append(("uplink_start", "completed"))\r
-                except Exception:\r
-                    pass\r
-                return actions\r
-        except Exception:\r
-            pass\r
-        return actions\r
-\r
-    def on_local_audio_played(self):\r
-        # 本地播报完成后:长对话回到 Listening,等待下一轮输入\r
-        if self.session == VCSessionState.Started:\r
-            self.turn = VCTurnState.Listening\r
-            self.await_played = False\r
-            self.last_tts_completed_ms = 0\r
-        self.mark_tts_rx_idle()\r
-        self.log("local.audio_played")\r
-\r
-    def on_local_play_start(self):\r
-        if self.session == VCSessionState.Started:\r
-            self.turn = VCTurnState.Playing\r
-        self.log("local.play_start")\r
-\r
-    def on_local_synthesis_sent(self):\r
-        # synthesis 发出后到 tts.started 之间:视为 TTS 接收阶段,禁止开麦\r
-        if self.session == VCSessionState.Started:\r
-            self.turn = VCTurnState.TtsReceiving\r
-        self.log("local.synthesis_sent")\r
-\r
-    def on_local_play_end(self):\r
-        actions = []\r
-        try:\r
-            if self.session != VCSessionState.Started:\r
-                return actions\r
-            if self.await_played:\r
-                actions.append(("send_audio_played", "local.play_end"))\r
-                # 长对话模式:对话过程中的 TTS 播完后,自动开麦继续当前对话。\r
-                # 例外:\r
-                # - 刚切到长对话模式的提示音(mode_switch_long)不应自动开麦\r
-                # - 其它提示/提醒(prompt)不应自动开麦\r
-                # 实际开麦是否允许由 _uplink_request_start 的门控(FSM.turn / record_state 等)保证安全幂等。\r
-                try:\r
-                    if self.mode() == VCMode.Realtime and getattr(self.vc, "Close_flag", 0) == 0:\r
-                        k = getattr(self.vc, "_last_tts_kind", "") or ""\r
-                        if k == "dialog":\r
-                            # 用户短按打断(RequestToSpeak):等 canceled 再开麦,勿 play_end 抢跑\r
-                            if getattr(self.vc, "_vc_pending_interrupt_uplink", 0) != 1:\r
-                                actions.append(("uplink_start", "local.play_end"))\r
-                except Exception:\r
-                    pass\r
-            self.on_local_audio_played()\r
-        except Exception:\r
-            pass\r
-        self.log("local.play_end")\r
-        return actions\r
-\r
+# -*- coding: utf-8 -*-
+"""VC 会话状态机。"""
+try:
+    from qp_log import qp_print as print
+except Exception:
+    pass
+
+VC_record_TYPE_PCM = 0
+VC_record_TYPE_AMR = 1
+VC_play_TYPE_PCM = 0
+VC_play_TYPE_MP3 = 1
+
+#websocket发送数据编号
+# AI_client_asr_num = 0
+# AI_client_chat_num = 1
+# AI_client_tts_num = 2
+AI_client_VC_num = 3
+AI_client_asr_num = 4
+
+
+class VCSessionState:
+    Disconnected = 0
+    Connecting = 1
+    Starting = 2
+    Started = 3
+
+
+class VCTurnState:
+    Idle = 0
+    Listening = 1
+    SpeechStreaming = 2
+    SpeechEnded = 3
+    Thinking = 4
+    TtsReceiving = 5
+    Playing = 6
+    AwaitPlayedAck = 7
+
+
+class VC_TtsRxPhase:
+    Idle = 0
+    Started = 1
+    ServerCompleted = 2
+
+
+class VCMode:
+    Realtime = 0       # VAD on(长对话,协议:无需 audio.create/completed)
+    PushToTalk = 1     # VAD off(单对话,协议:必须 audio.create/completed 包裹音频帧)
+
+
+class VCStateMachine:
+    # 轻量状态机骨架(先落地骨架与动作接口,后续逐步把旧 if/flag 逻辑收敛进来)。
+    #
+    # 约束来自《金三服务-语音对话V2.5》:
+    # - 收到 started 后才允许发音频帧(否则 session not initialized)
+    # - VAD 开启:不需要 audio.create/completed
+    # - VAD 关闭:必须 audio.create → frames → audio.completed
+    # - 播放结束 audio.played 是进入下一轮的门控信号之一(尤其关键词打断场景)
+
+    def __init__(self, vc):
+        self.vc = vc
+        self.session = VCSessionState.Disconnected
+        self.turn = VCTurnState.Idle
+        self.last_llm_ms = 0
+        self.last_tts_pending_ms = 0
+        self.await_played = False
+        self.last_tts_completed_ms = 0
+        self.tts_rx_phase = VC_TtsRxPhase.Idle
+        self.speech_uplink_open = 0
+
+    def reset_rx_phases(self):
+        self.tts_rx_phase = VC_TtsRxPhase.Idle
+        self.speech_uplink_open = 0
+
+    def mark_tts_rx_idle(self):
+        self.tts_rx_phase = VC_TtsRxPhase.Idle
+
+    def on_server_asr_started(self):
+        if self.session == VCSessionState.Started:
+            self.speech_uplink_open = 1
+        self.log("server.asr.started")
+
+    def _turn_name(self, t):
+        try:
+            return {
+                VCTurnState.Idle: "Idle",
+                VCTurnState.Listening: "Listening",
+                VCTurnState.SpeechStreaming: "SpeechStreaming",
+                VCTurnState.SpeechEnded: "SpeechEnded",
+                VCTurnState.Thinking: "Thinking",
+                VCTurnState.TtsReceiving: "TtsReceiving",
+                VCTurnState.Playing: "Playing",
+                VCTurnState.AwaitPlayedAck: "AwaitPlayedAck",
+            }.get(t, str(t))
+        except Exception:
+            return str(t)
+
+    def _session_name(self, s):
+        try:
+            return {
+                VCSessionState.Disconnected: "Disconnected",
+                VCSessionState.Connecting: "Connecting",
+                VCSessionState.Starting: "Starting",
+                VCSessionState.Started: "Started",
+            }.get(s, str(s))
+        except Exception:
+            return str(s)
+
+    def log(self, reason=""):
+        # 只用于关键节点的单行状态日志,便于串口快速定位状态漂移
+        try:
+            print("[FSM] {} session={} turn={} await_played={} tts_rx={} speech_up={}".format(
+                reason,
+                self._session_name(self.session),
+                self._turn_name(self.turn),
+                1 if self.await_played else 0,
+                int(self.tts_rx_phase),
+                1 if self.speech_uplink_open else 0,
+            ))
+        except Exception:
+            pass
+
+    def mode(self):
+        try:
+            return VCMode.Realtime if self.vc.VC_mode == 0 else VCMode.PushToTalk
+        except Exception:
+            return VCMode.Realtime
+
+    def on_session_start_request(self):
+        self.session = VCSessionState.Starting
+        self.turn = VCTurnState.Idle
+        # 新会话开始时必须清空上一轮“待播完”状态,否则会出现:
+        # - 切换模式/重连后 await_played 仍为 True → play_end 逻辑抢跑/不触发 → 无法自动开麦
+        self.await_played = False
+        self.last_tts_completed_ms = 0
+        self.last_llm_ms = 0
+        self.reset_rx_phases()
+
+    def on_server_started(self):
+        self.reset_rx_phases()
+        self.session = VCSessionState.Started
+        # 新会话 started:确保进入 Listening,且清理上轮播放门控
+        self.await_played = False
+        self.last_tts_completed_ms = 0
+        if self.turn in (VCTurnState.Idle, VCTurnState.AwaitPlayedAck):
+            self.turn = VCTurnState.Listening
+        self.log("server.started")
+
+    def on_server_vad_silence(self):
+        if self.session == VCSessionState.Started:
+            self.turn = VCTurnState.SpeechEnded
+        self.log("server.vad.silence")
+
+    def on_server_llm_created(self):
+        if self.session == VCSessionState.Started:
+            self.turn = VCTurnState.Thinking
+            try:
+                self.last_llm_ms = utime.ticks_ms()
+            except Exception:
+                self.last_llm_ms = 1
+        self.log("server.llm.created")
+
+    def on_server_tts_started(self):
+        if self.session == VCSessionState.Started:
+            self.turn = VCTurnState.TtsReceiving
+            self.tts_rx_phase = VC_TtsRxPhase.Started
+            # 新一轮 TTS:清上轮 completed 时间戳,避免 play_end 用陈旧 since_done 触发 too_long 截断
+            self.last_tts_completed_ms = 0
+        self.log("server.tts.started")
+
+    def on_server_tts_completed(self):
+        if self.session == VCSessionState.Started:
+            # tts.completed 只表示服务端音频下发结束,本地可能仍在播放;保持 Playing,由 await_played 门控播完上报
+            self.await_played = True
+            self.tts_rx_phase = VC_TtsRxPhase.ServerCompleted
+            try:
+                self.last_tts_completed_ms = utime.ticks_ms()
+            except Exception:
+                # 勿置 1:ticks_diff(now,1) 会误判为超时并过早 VC_Play_End
+                self.last_tts_completed_ms = 0
+        self.log("server.tts.completed")
+
+    def on_server_canceled(self):
+        if self.session == VCSessionState.Started:
+            self.turn = VCTurnState.Listening
+        self.reset_rx_phases()
+        self.log("server.canceled")
+
+    def on_server_completed(self):
+        if self.session == VCSessionState.Started:
+            # 本轮结束:默认回到 Listening;是否自动开麦由 action 门控决定
+            # 若仍在等待本地播完(await_played),completed 不应把 turn 拉回 Listening,
+            # 否则会触发 completed→uplink_start 抢跑,打断播放链路。
+            if not self.await_played and self.turn != VCTurnState.AwaitPlayedAck:
+                self.turn = VCTurnState.Listening
+        self.log("server.completed")
+
+    def on_server_event(self, event, jsload_data=None):
+        # 事件→动作(action list)适配层。
+        # 现阶段只产出“可幂等的控制动作”(uplink start/stop),避免和旧逻辑重复触发播放等副作用。
+        # 后续会逐步把更多行为(play_start/audio.played/ptt create/completed)迁移到这里。
+        actions = []
+        try:
+            # LLM 阶段必须驱动 FSM.turn,否则 turn 会停在 SpeechEnded,导致:
+            # - _fsm_timeout_table_tick 里「Thinking 超时 / synthesis 超时」永不命中
+            # - 与注释和 force_tts 前置条件不一致
+            if event == "llm.created":
+                self.on_server_llm_created()
+                return actions
+            if event == "llm.completed":
+                # 若对端偶发跳过 llm.created,用 completed 把 turn 补到 Thinking,避免兜底全失效
+                try:
+                    if self.session == VCSessionState.Started:
+                        if self.turn in (VCTurnState.SpeechEnded, VCTurnState.Listening):
+                            self.turn = VCTurnState.Thinking
+                except Exception:
+                    pass
+                return actions
+            if event == "started":
+                self.on_server_started()
+                return actions
+            if event == "vad.silence":
+                self.on_server_vad_silence()
+                actions.append(("uplink_stop", "vad.silence"))
+                return actions
+            if event == "tts.started":
+                self.on_server_tts_started()
+                actions.append(("uplink_stop", "tts.started"))
+                actions.append(("play_start", "tts.started"))
+                return actions
+            if event == "tts.completed":
+                self.on_server_tts_completed()
+                # 播放可能已在 started 时开启,这里不再强制 play_start,避免反复 open PCM
+                return actions
+            if event == "canceled":
+                self.on_server_canceled()
+                # canceled:仅长对话允许“立刻回到聆听并尝试开麦”(由 _uplink_request_start 门控保证安全幂等)
+                try:
+                    if self.mode() == VCMode.Realtime and getattr(self.vc, "Close_flag", 0) == 0:
+                        actions.append(("uplink_start", "canceled"))
+                except Exception:
+                    pass
+                return actions
+            if event == "completed":
+                self.on_server_completed()
+                # completed:若不在等待 audio.played(说明本轮无 TTS),仅长对话可直接进入下一轮聆听
+                try:
+                    if self.mode() == VCMode.Realtime and getattr(self.vc, "Close_flag", 0) == 0:
+                        if (not getattr(self, "await_played", False)) and self.turn != VCTurnState.AwaitPlayedAck:
+                            actions.append(("uplink_start", "completed"))
+                except Exception:
+                    pass
+                return actions
+        except Exception:
+            pass
+        return actions
+
+    def on_local_audio_played(self):
+        # 本地播报完成后:长对话回到 Listening,等待下一轮输入
+        if self.session == VCSessionState.Started:
+            self.turn = VCTurnState.Listening
+            self.await_played = False
+            self.last_tts_completed_ms = 0
+        self.mark_tts_rx_idle()
+        self.log("local.audio_played")
+
+    def on_local_play_start(self):
+        if self.session == VCSessionState.Started:
+            self.turn = VCTurnState.Playing
+        self.log("local.play_start")
+
+    def on_local_synthesis_sent(self):
+        # synthesis 发出后到 tts.started 之间:视为 TTS 接收阶段,禁止开麦
+        if self.session == VCSessionState.Started:
+            self.turn = VCTurnState.TtsReceiving
+        self.log("local.synthesis_sent")
+
+    def on_local_play_end(self):
+        actions = []
+        try:
+            if self.session != VCSessionState.Started:
+                return actions
+            if self.await_played:
+                actions.append(("send_audio_played", "local.play_end"))
+                # 长对话模式:对话过程中的 TTS 播完后,自动开麦继续当前对话。
+                # 例外:
+                # - 刚切到长对话模式的提示音(mode_switch_long)不应自动开麦
+                # - 其它提示/提醒(prompt)不应自动开麦
+                # - boot_prompt:started 阶段已跳过开麦,播完需自动进入首轮聆听(155536)
+                # 实际开麦是否允许由 _uplink_request_start 的门控(FSM.turn / record_state 等)保证安全幂等。
+                try:
+                    if self.mode() == VCMode.Realtime and getattr(self.vc, "Close_flag", 0) == 0:
+                        k = getattr(self.vc, "_last_tts_kind", "") or ""
+                        if k in ("dialog", "boot_prompt"):
+                            # 用户短按打断(RequestToSpeak):等 canceled 再开麦,勿 play_end 抢跑
+                            if getattr(self.vc, "_vc_pending_interrupt_uplink", 0) != 1:
+                                actions.append(("uplink_start", "local.play_end"))
+                except Exception:
+                    pass
+            self.on_local_audio_played()
+        except Exception:
+            pass
+        self.log("local.play_end")
+        return actions
+
index a9a3441..c8cf361 100644 (file)
-# -*- coding: utf-8 -*-\r
-"""语音对话 WebSocket / ASR / TTS 主业务。"""\r
-from common import Abstract\r
-try:\r
-    from qp_log import qp_print as print\r
-except Exception:\r
-    pass\r
-import EventMesh\r
-import uwebsocket\r
-import usys\r
-import gc\r
-import urandom\r
-import uhashlib\r
-import ubinascii\r
-import ujson\r
-import osTimer\r
-import _thread\r
-from queue import Queue\r
-import utime\r
-import audio\r
-import uos\r
-from machine import Pin\r
-import modem\r
-import sim\r
-import net\r
-import base64\r
-import request\r
-import ql_fs\r
-from misc import Power\r
-import ntptime\r
-import machine\r
-import dataCall\r
-import constants\r
-import versions\r
-import version_util\r
-try:\r
-    import vc_config\r
-except Exception:\r
-    vc_config = None\r
-try:\r
-    import vc_text_intent\r
-except Exception:\r
-    vc_text_intent = None\r
-try:\r
-    import VAD\r
-except Exception:\r
-    pass\r
-try:\r
-    import usr.ai_alarm as _ai_alarm\r
-except Exception:\r
-    import ai_alarm as _ai_alarm\r
-from vc_common import (\r
-    dev_type,\r
-    VC_HEARTBEAT_LOG,\r
-    VC_PROTO_GUARDS,\r
-    VC_WS_IO_LOG,\r
-    _VC_KEEPALIVE_RX_EVENTS,\r
-    _vc_out_json_event_meta,\r
-    PRESS_AUDIO_MODE,\r
-)\r
-from vc_fsm import (\r
-    VCSessionState,\r
-    VCTurnState,\r
-    VC_TtsRxPhase,\r
-    VCMode,\r
-    VCStateMachine,\r
-    VC_record_TYPE_PCM,\r
-    VC_record_TYPE_AMR,\r
-    VC_play_TYPE_PCM,\r
-    VC_play_TYPE_MP3,\r
-    AI_client_VC_num,\r
-    AI_client_asr_num,\r
-)\r
-from vc_ota import OTA\r
-from vc_utils import num_str_to_chinese\r
-\r
-class AI_VC(Abstract):\r
-    def get_vc_tts_playback_active(self, *args, **kwargs):\r
-        try:\r
-            return 1 if self._vc_tts_playback_active() else 0\r
-        except Exception:\r
-            return 0\r
-\r
-    def get_vc_stream_active(self, *args, **kwargs):\r
-        # 供本地提示音侧查询:TTS 流播放期间本地不可抢占。\r
-        try:\r
-            if self._vc_tts_playback_active():\r
-                return 1\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if getattr(self.fsm, "await_played", False):\r
-                return 1\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if getattr(self.fsm, "tts_rx_phase", VC_TtsRxPhase.Idle) != VC_TtsRxPhase.Idle:\r
-                return 1\r
-        except Exception:\r
-            pass\r
-        try:\r
-            t = getattr(self.fsm, "turn", VCTurnState.Idle)\r
-            if t in (VCTurnState.TtsReceiving, VCTurnState.Playing, VCTurnState.AwaitPlayedAck):\r
-                return 1\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if getattr(self, "_tts_pending", 0) == 1:\r
-                return 1\r
-        except Exception:\r
-            pass\r
-        return 0\r
-\r
-    def _vc_fsm_log_snip(self):\r
-        try:\r
-            f = self.fsm\r
-            return "sess={} turn={} tts_rx={} await={} sp_up={}".format(\r
-                int(getattr(f, "session", -1)),\r
-                int(getattr(f, "turn", -1)),\r
-                int(getattr(f, "tts_rx_phase", -1)),\r
-                1 if getattr(f, "await_played", False) else 0,\r
-                1 if getattr(f, "speech_uplink_open", 0) else 0,\r
-            )\r
-        except Exception:\r
-            return "fsm=?"\r
-\r
-    def _vc_tts_pipeline_busy(self):\r
-        try:\r
-            if getattr(self.fsm, "await_played", False):\r
-                return True\r
-            if getattr(self.fsm, "tts_rx_phase", VC_TtsRxPhase.Idle) != VC_TtsRxPhase.Idle:\r
-                return True\r
-            t = getattr(self.fsm, "turn", VCTurnState.Idle)\r
-            if t in (VCTurnState.TtsReceiving, VCTurnState.Playing, VCTurnState.AwaitPlayedAck):\r
-                return True\r
-            if getattr(self, "_tts_pending", 0) == 1:\r
-                return True\r
-            if getattr(self, "VC_Play_flag", 0) == 1:\r
-                return True\r
-            if self._vc_local_play_busy():\r
-                return True\r
-        except Exception:\r
-            pass\r
-        return False\r
-\r
-    def _vc_local_play_busy(self):\r
-        try:\r
-            if getattr(self, "_vc_local_play_active", 0) != 1:\r
-                return False\r
-            until = getattr(self, "_vc_local_play_until_ms", 0)\r
-            if until and utime.ticks_diff(until, utime.ticks_ms()) > 0:\r
-                return True\r
-            self._vc_local_play_active = 0\r
-            self._vc_local_play_until_ms = 0\r
-        except Exception:\r
-            pass\r
-        return False\r
-\r
-    def _vc_capture_allowed(self, reason=""):\r
-        """采集/上行开麦前:下行播放或本地短音未结束则禁止。"""\r
-        try:\r
-            if self._vc_tts_pipeline_busy():\r
-                return False\r
-            if self._vc_local_play_busy():\r
-                return False\r
-        except Exception:\r
-            return False\r
-        return True\r
-\r
-    def _vc_stop_downlink_for_interrupt(self, reason="interrupt"):\r
-        """详设 DES-SCN-003:打断/取消时先本地停播清队列,再允许开麦。"""\r
-        try:\r
-            if getattr(self, "AI_audio_queue", None) is not None:\r
-                if self.AI_audio_queue.empty() != True:\r
-                    self.AI_audio_queue.clear()\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if self._vc_tts_pipeline_busy() or getattr(self, "is_ruppt_tts", 0) == 1:\r
-                self._drop_tts_audio = 1\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self._ptt_hard_mute_local_playback()\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if getattr(self, "VC_Play_flag", 0) == 1 or getattr(self, "play_pcm", None) is not None:\r
-                self.VC_Play_End()\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self._vc_local_play_active = 0\r
-            self._vc_local_play_until_ms = 0\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self.fsm.await_played = False\r
-            self._need_audio_played_ack = 0\r
-            self._tts_pending = 0\r
-            self._tts_pending_ms = 0\r
-        except Exception:\r
-            pass\r
-        try:\r
-            pending_intr = getattr(self, "_vc_pending_interrupt_uplink", 0) == 1\r
-            if (\r
-                not pending_intr\r
-                and getattr(self, "VC_record_state_flag", 0) != 0\r
-                and getattr(self, "record_data_save_flag", 0) == 0\r
-            ):\r
-                self._uplink_request_stop("stop_downlink/" + str(reason))\r
-        except Exception:\r
-            pass\r
-\r
-    def _vc_local_play(self, path, play_mode=1, loop=1, hold_ms=650):\r
-        """本地 MP3 短音:播前停录/停流式 TTS,避免与麦克风采集叠用。"""\r
-        try:\r
-            if getattr(self, "record_data_save_flag", 0) == 0 and getattr(self, "VC_record_state_flag", 0) != 0:\r
-                self._uplink_request_stop("local_play")\r
-                utime.sleep_ms(30)\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if self._vc_tts_playback_active():\r
-                self._ptt_hard_mute_local_playback()\r
-                try:\r
-                    self.VC_Play_End()\r
-                except Exception:\r
-                    pass\r
-        except Exception:\r
-            pass\r
-        try:\r
-            hold = int(hold_ms)\r
-            if hold < 200:\r
-                hold = 200\r
-            self._vc_local_play_active = 1\r
-            self._vc_local_play_until_ms = utime.ticks_add(utime.ticks_ms(), hold)\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self.aud.play(play_mode, loop, path)\r
-        except Exception as e:\r
-            try:\r
-                self._vc_local_play_active = 0\r
-                self._vc_local_play_until_ms = 0\r
-            except Exception:\r
-                pass\r
-            usys.print_exception(e)\r
-\r
-    def _vc_tts_playback_active(self):\r
-        """仅服务端 TTS/PCM 流在播(VC_Play_Start);aud.play 本地短音不算。"""\r
-        try:\r
-            if getattr(self, "VC_Play_flag", 0) == 1:\r
-                return True\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if getattr(self, "play_pcm", None) is not None:\r
-                return True\r
-        except Exception:\r
-            pass\r
-        return False\r
-\r
-    def _vc_vad_uplink_stop_eligible(self):\r
-        # 对应旧 VC_state >= SpeechStart\r
-        try:\r
-            if getattr(self.fsm, "speech_uplink_open", 0):\r
-                return True\r
-            t = getattr(self.fsm, "turn", VCTurnState.Idle)\r
-            return t not in (VCTurnState.Idle, VCTurnState.Listening)\r
-        except Exception:\r
-            return False\r
-\r
-    def _vc_long_idle_active_speech_or_later(self):\r
-        # 对应旧 VC_state >= SpeechIng\r
-        try:\r
-            t = getattr(self.fsm, "turn", VCTurnState.Idle)\r
-            return t not in (VCTurnState.Idle, VCTurnState.Listening)\r
-        except Exception:\r
-            return False\r
-\r
-    def _vc_is_business_idle(self):\r
-        # Level2:LED ai_state 为 idle,且无录音/流播/音乐闹钟等(与 WS ping 无关)\r
-        try:\r
-            if getattr(self, "music_play_flag", 0) == 1:\r
-                return False\r
-            if getattr(self, "alarm_play_flag", 0) == 1:\r
-                return False\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if getattr(self, "VC_record_state_flag", 0) != 0:\r
-                return False\r
-            if getattr(self, "VC_Play_flag", 0) != 0:\r
-                return False\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if EventMesh.publish("get_vc_stream_active") == 1:\r
-                return False\r
-        except Exception:\r
-            pass\r
-        try:\r
-            st = EventMesh.publish("get_led_debug_state")\r
-            if st and isinstance(st, dict):\r
-                if (st.get("ai") or "") != "idle":\r
-                    return False\r
-        except Exception:\r
-            return False\r
-        return True\r
-\r
-    def _vc_idle_deep_sleep_impl(self):\r
-        try:\r
-            _need_close = (self.VC_satrt_flag == 1) or (getattr(self, "AI_client_VC_isconnect", 0) == 1)\r
-            print("[VC][idle_L2] 触发休眠流程 need_Close={} VC_mode={}".format(\r
-                1 if _need_close else 0, getattr(self, "VC_mode", -1)))\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if self.VC_satrt_flag == 1 or getattr(self, "AI_client_VC_isconnect", 0) == 1:\r
-                try:\r
-                    print("[VC][idle_L2] 调用 Close 断开会话/链路")\r
-                except Exception:\r
-                    pass\r
-                self.Close()\r
-        except Exception:\r
-            pass\r
-        try:\r
-            utime.sleep_ms(100)\r
-        except Exception:\r
-            pass\r
-        try:\r
-            print("[VC][idle_L2] 发布 Hsensor_set_sleep")\r
-            EventMesh.publish("Hsensor_set_sleep")\r
-        except Exception:\r
-            pass\r
-        try:\r
-            print("[VC][idle_L2] 发布 SYSTEM_SLEEP ({})".format(constants.EVENT.SYSTEM_SLEEP))\r
-            EventMesh.publish(constants.EVENT.SYSTEM_SLEEP)\r
-        except Exception:\r
-            pass\r
-\r
-    def _vc_rx_resets_long_idle_timer(self, recv_data):\r
-        """是否应因本帧下行清零 close_timerout(排除保活/心跳,含 V2.5 文本 ping 对端应答)。"""\r
-        try:\r
-            if recv_data is None:\r
-                return False\r
-            if isinstance(recv_data, (bytes, bytearray)):\r
-                return True\r
-            if not isinstance(recv_data, str):\r
-                return False\r
-            t = recv_data.strip()\r
-            if not t:\r
-                return False\r
-            tl = t.lower()\r
-            if tl in ("ping", "pong"):\r
-                return False\r
-            try:\r
-                jd = ujson.loads(t)\r
-            except Exception:\r
-                return True\r
-            ev = jd.get("event")\r
-            if ev is None or ev == "":\r
-                ev = jd.get("type")\r
-            if ev is None:\r
-                return True\r
-            if str(ev).strip().lower() in _VC_KEEPALIVE_RX_EVENTS:\r
-                return False\r
-            return True\r
-        except Exception:\r
-            return True\r
-\r
-    # -------------------------------------------------------------------------\r
-    # 电源键 / AI 键:按 VC_mode 拆函数,便于阅读与修改(长对话 vs 单对话 PTT)\r
-    # -------------------------------------------------------------------------\r
-    def _handle_pwk_short_release(self, event=None, msg=None, merged_ai_tail=False):\r
-        """电源键短按:读留言标志、退会议/翻译;否则 Close,或单键模式下走 AI_key_release。"""\r
-        print("pwk_key_release")\r
-        if EventMesh.publish("get_recv_voice") == 1:\r
-            EventMesh.publish("update_play_new_voice_info")\r
-            self.recv_voice = 0\r
-        EventMesh.publish("set_history_voice_flag", 0)\r
-        self.AI_new_voice_url = ""\r
-        if self.trans_flag == 1 or self.meet_flag == 1:\r
-            EventMesh.publish("exit_meet_mode")\r
-            EventMesh.publish("exit_trans_mode")\r
-            return\r
-        if merged_ai_tail:\r
-            self.AI_key_release(event, msg)\r
-        else:\r
-            EventMesh.publish("publish_AI_btnInfo", {"func": self.Close})\r
-        EventMesh.publish("stop_sos_call")\r
-\r
-    def _ai_key_press_ptt_mode(self):\r
-        """单对话 PTT:短按仅停 TTS(不换 sid);非播放态短按吞掉;长按由 AI_key_long_press→ptt_begin。"""\r
-        if self.VC_mode != 1:\r
-            return False\r
-        try:\r
-            playing = False\r
-            try:\r
-                t = getattr(self.fsm, "turn", VCTurnState.Idle)\r
-                playing = t in (VCTurnState.TtsReceiving, VCTurnState.Playing)\r
-            except Exception:\r
-                playing = False\r
-            if playing or self._vc_tts_pipeline_busy():\r
-                self._ptt_short_press_stop = 1\r
-                try:\r
-                    self._ptt_hard_mute_local_playback()\r
-                except Exception:\r
-                    pass\r
-                try:\r
-                    self.VC_Play_End()\r
-                except Exception:\r
-                    pass\r
-                try:\r
-                    self.fsm.await_played = False\r
-                except Exception:\r
-                    pass\r
-                try:\r
-                    self._ptt_led_lock_listening = 0\r
-                    self._ptt_interrupt_active = 0\r
-                    self.is_ruppt_tts = 0\r
-                except Exception:\r
-                    pass\r
-                try:\r
-                    self._vc_publish_led("idle", "ptt_short_press/stop_tts")\r
-                except Exception:\r
-                    pass\r
-                return True\r
-        except Exception:\r
-            pass\r
-        return True\r
-\r
-    def _ai_key_press_long_dialogue_interrupt(self):\r
-        """长对话:TTS/播放忙或 turn==Thinking 时短按 → RequestToSpeak(先 cancel,canceled 后再开麦)。"""\r
-        if self.VC_mode != 0:\r
-            return False\r
-        try:\r
-            if getattr(self, "AI_client_VC_isconnect", 0) != 1 or getattr(self, "_vc_session_started", 0) != 1:\r
-                return False\r
-        except Exception:\r
-            return False\r
-        need_cancel = False\r
-        if self._vc_tts_pipeline_busy():\r
-            need_cancel = True\r
-        else:\r
-            try:\r
-                if getattr(self.fsm, "turn", VCTurnState.Idle) == VCTurnState.Thinking:\r
-                    need_cancel = True\r
-            except Exception:\r
-                pass\r
-        if not need_cancel:\r
-            return False\r
-        print("[KEY] long interrupt -> RequestToSpeak (cancel then uplink)")\r
-        self.is_ruppt_tts = 1\r
-        try:\r
-            self._vc_pending_interrupt_uplink = 1\r
-        except Exception:\r
-            pass\r
-        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_RequestToSpeak})\r
-        return True\r
-\r
-    def _ai_key_press_long_dialogue_wake(self):\r
-        """长对话未连接或未 started:直接 AI_VC_start,勿 Close+sleep。"""\r
-        if self.VC_mode != 0:\r
-            return False\r
-        if getattr(self, "voice_flag", 0) == 1 or getattr(self, "meet_flag", 0) == 1 or getattr(self, "trans_flag", 0) == 1:\r
-            return False\r
-        try:\r
-            if getattr(self, "AI_client_VC_isconnect", 0) == 1 and getattr(self, "_vc_session_started", 0) == 1:\r
-                return False\r
-        except Exception:\r
-            pass\r
-        print("[KEY] long wake AI_VC_start (no Close)")\r
-        try:\r
-            self.Close_flag = 0\r
-        except Exception:\r
-            pass\r
-        self.get_config_timer_count = 0\r
-        self.key_state = 1\r
-        self.AI_TTS_str = ""\r
-        self.AI_ASR_str = ""\r
-        self.AI_ASR_str_tmp = ""\r
-        self.AI_VC_start()\r
-        self.AI_key_long_press_flag = 0\r
-        return True\r
-\r
-    def _vc_led_alert_rb_flash(self):\r
-        try:\r
-            EventMesh.publish("set_led_state", {"alert": "rb_flash_2"})\r
-        except Exception:\r
-            pass\r
-\r
-    def _vc_clear_stale_record_state(self, reason=""):\r
-        """record_state_flag 非 0 但可能无真实录音时先停录复位,避免 uplink 门控拒开麦。"""\r
-        try:\r
-            if getattr(self, "VC_record_state_flag", 0) != 0:\r
-                self.VC_Speech_End()\r
-        except Exception:\r
-            try:\r
-                self.VC_record_state_flag = 0\r
-            except Exception:\r
-                pass\r
-\r
-    def _ai_key_press_long_dialogue_when_session_ready(self):\r
-        """长对话已连接且 session 已 started:非播放态直接开麦续聊(勿 idle 时发 cancel)。"""\r
-        if self.VC_mode != 0:\r
-            return False\r
-        try:\r
-            if getattr(self, "AI_client_VC_isconnect", 0) == 1 and getattr(self, "_vc_session_started", 0) == 1:\r
-                # 模式切换提示音会置 Close_flag=1;用户再短按须恢复可开麦\r
-                try:\r
-                    self.Close_flag = 0\r
-                except Exception:\r
-                    pass\r
-                self._vc_clear_stale_record_state("key.press/continue")\r
-                self.request_uplink_start("key.press/continue")\r
-                return True\r
-        except Exception:\r
-            pass\r
-        return False\r
-\r
-    def _ai_key_release_handle_ptt(self):\r
-        """单对话 PTT 松手:仅在上行录音中时 end_speech;禁止误触发 AI_VC_start(否则新 sid / cancel 竞态)。"""\r
-        if self.AI_key_long_press_flag == 1:\r
-            if getattr(self, "VC_record_state_flag", 0) != 0:\r
-                try:\r
-                    gap = 200\r
-                    if "vc_config" in globals() and vc_config:\r
-                        gap = int(vc_config.get_int("PTT_RELEASE_END_SPEECH_DELAY_MS", 200))\r
-                    if gap > 0:\r
-                        utime.sleep_ms(gap)\r
-                except Exception:\r
-                    pass\r
-                print("[PTT] release end_speech asr_str={!r} rec={}".format(\r
-                    self.AI_ASR_str, getattr(self, "VC_record_state_flag", -1)))\r
-                self.AI_client_VC_end_speech()\r
-            else:\r
-                self.AI_key_long_press_flag = 0\r
-                try:\r
-                    if getattr(self, "_ptt_short_press_stop", 0) == 1:\r
-                        self._ptt_short_press_stop = 0\r
-                except Exception:\r
-                    pass\r
-                print("[PTT] release_no_uplink_skip")\r
-            return\r
-        try:\r
-            if getattr(self, "_ptt_short_press_stop", 0) == 1:\r
-                self._ptt_short_press_stop = 0\r
-        except Exception:\r
-            pass\r
-        print("[PTT] release_short_no_long")\r
-\r
-    def _ai_key_long_press_route(self, event=None, msg=None):\r
-        """长按:翻译先 Close 再开翻译录音;单对话→ptt_begin;长对话→与短按一致走 AI_key_press 链路。"""\r
-        if self.trans_flag == 1:\r
-            self.Close()\r
-            EventMesh.publish("start_voice_record", 4)\r
-        self.AI_key_long_press_flag = 1\r
-        if self.VC_mode == 1:\r
-            EventMesh.publish("publish_AI_btnInfo", {"func": self.ptt_begin})\r
-        else:\r
-            EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_key_press})\r
-\r
-    # ===== Push-To-Talk(单对话模式)=====\r
-    # 语义:\r
-    # - 长按:无论当前处于 TTS/Thinking/任何中间态,立即打断并进入新一轮录音上行\r
-    # - 松开:停止录音并等待服务端返回(由 AI_client_VC_end_speech 触发)\r
-    def _ptt_end_led_suppress(self):\r
-        # 保持 _vc_play_end_suppress_idle_led / _ptt_led_lock_listening 直至真开录(VC_Speech_Start)\r
-        pass\r
-\r
-    def _ptt_should_drop_inbound_tts(self):\r
-        """单对话打断窗口:丢弃迟到 TTS 帧 / 禁止起播。"""\r
-        try:\r
-            if getattr(self, "VC_mode", -1) != 1:\r
-                return False\r
-            if getattr(self, "_drop_tts_audio", 0) == 1:\r
-                return True\r
-            if getattr(self, "is_ruppt_tts", 0) == 1:\r
-                return True\r
-            if getattr(self, "_ptt_interrupt_active", 0) == 1:\r
-                return True\r
-            if getattr(self, "_vc_serial_canceling", 0) == 1:\r
-                return True\r
-            if getattr(self, "_ptt_pending_arm", 0) == 1:\r
-                return True\r
-        except Exception:\r
-            pass\r
-        return False\r
-\r
-    def _ptt_should_skip_remote_playback(self):\r
-        return self._ptt_should_drop_inbound_tts()\r
-\r
-    def _ptt_enter_interrupt_arm(self):\r
-        try:\r
-            self._ptt_arm_epoch = int(getattr(self, "_ptt_arm_epoch", 0)) + 1\r
-            self._ptt_interrupt_active = 1\r
-            self._ptt_led_lock_listening = 1\r
-            self._vc_play_end_suppress_idle_led = 1\r
-            self._drop_tts_audio = 1\r
-            self.is_ruppt_tts = 1\r
-        except Exception:\r
-            pass\r
-\r
-    def _ptt_abort_interrupt_arm(self):\r
-        try:\r
-            self._ptt_interrupt_active = 0\r
-            self._ptt_led_lock_listening = 0\r
-            self._vc_play_end_suppress_idle_led = 0\r
-            self._drop_tts_audio = 0\r
-            self.is_ruppt_tts = 0\r
-        except Exception:\r
-            pass\r
-\r
-    def _ptt_on_record_hardware_started(self):\r
-        """真开录后:允许新 TTS、放开灯锁;旧 TTS 已在打断期丢弃。"""\r
-        try:\r
-            self._ptt_interrupt_active = 0\r
-            self._ptt_led_lock_listening = 0\r
-            self._vc_play_end_suppress_idle_led = 0\r
-            self._drop_tts_audio = 0\r
-            self.is_ruppt_tts = 0\r
-        except Exception:\r
-            pass\r
-\r
-    def _ptt_hard_mute_local_playback(self):\r
-        try:\r
-            self.VC_Play_flag = 0\r
-            if getattr(self, "VC_play_TYPE", None) == VC_play_TYPE_PCM:\r
-                if getattr(self, "play_pcm", None) is not None:\r
-                    try:\r
-                        self.play_pcm.stream_stop()\r
-                        self.play_pcm.close()\r
-                    except Exception:\r
-                        pass\r
-                    self.play_pcm = None\r
-            try:\r
-                self.aud.stopPlayStream()\r
-                self.aud.stopAll()\r
-            except Exception:\r
-                pass\r
-            if getattr(self, "AI_audio_queue", None) is not None:\r
-                try:\r
-                    if self.AI_audio_queue.empty() != True:\r
-                        self.AI_audio_queue.clear()\r
-                except Exception:\r
-                    pass\r
-        except Exception:\r
-            pass\r
-        try:\r
-            gap = 0\r
-            if "vc_config" in globals() and vc_config:\r
-                gap = int(vc_config.get_int("PTT_POST_MUTE_MS", 30))\r
-            if gap > 0:\r
-                utime.sleep_ms(gap)\r
-        except Exception:\r
-            pass\r
-\r
-    def battery_priority_mute_vc(self, event=None, msg=None):\r
-        """低电/关机提示音:短暂停 VC TTS,避免被流式播放挡住。"""\r
-        try:\r
-            self._ptt_hard_mute_local_playback()\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if getattr(self, "VC_Play_flag", 0) == 1 or getattr(self, "play_pcm", None) is not None:\r
-                self.VC_Play_End()\r
-        except Exception:\r
-            pass\r
-\r
-    def _vc_enqueue_btn(self, func, data=None):\r
-        """统一经 publish_AI_btnInfo 入队,在 btnInfo_queue_thread 串行执行。"""\r
-        try:\r
-            if data is not None:\r
-                EventMesh.publish("publish_AI_btnInfo", {"func": func, "data": data})\r
-            else:\r
-                EventMesh.publish("publish_AI_btnInfo", {"func": func})\r
-        except Exception:\r
-            pass\r
-\r
-    def _vc_ptt_cancel_wait_ms(self):\r
-        try:\r
-            if "vc_config" in globals() and vc_config:\r
-                return int(vc_config.get_int("PTT_CANCEL_WAIT_MS", 2500))\r
-        except Exception:\r
-            pass\r
-        return 2500\r
-\r
-    def _vc_ptt_need_interrupt_wait(self):\r
-        """单对话:服务端仍在思考/收 TTS/播报时,开录前须先 cancel 并等 canceled。"""\r
-        try:\r
-            if getattr(self, "VC_mode", -1) != 1:\r
-                return False\r
-            turn = getattr(self.fsm, "turn", VCTurnState.Idle)\r
-            if self._vc_tts_pipeline_busy():\r
-                return True\r
-            if turn in (\r
-                VCTurnState.Thinking, VCTurnState.TtsReceiving,\r
-                VCTurnState.Playing, VCTurnState.AwaitPlayedAck,\r
-            ):\r
-                return True\r
-        except Exception:\r
-            pass\r
-        return False\r
-\r
-    def _vc_begin_ptt_cancel_wait(self, reason="ptt_interrupt"):\r
-        try:\r
-            self._ptt_pending_arm = 1\r
-            self._vc_serial_canceling = 1\r
-            try:\r
-                self._vc_cancel_deadline_ms = utime.ticks_add(\r
-                    utime.ticks_ms(), self._vc_ptt_cancel_wait_ms())\r
-            except Exception:\r
-                self._vc_cancel_deadline_ms = 0\r
-            json_data = {"event": "cancel", "payload": {}}\r
-            self.AI_client_VC_send(ujson.dumps(json_data))\r
-            try:\r
-                self.fsm.reset_rx_phases()\r
-            except Exception:\r
-                pass\r
-            self._drop_tts_audio = 1\r
-            print("[VC][serial] cancel_wait reason={} turn={}".format(\r
-                reason, getattr(getattr(self, "fsm", None), "turn", "?")))\r
-        except Exception:\r
-            pass\r
-\r
-    def _vc_on_ptt_cancel_settled(self, via="canceled"):\r
-        """btn 线程:cancel 已收尾,执行挂起的 PTT 开录。"""\r
-        try:\r
-            if not getattr(self, "_vc_serial_canceling", 0) and not getattr(self, "_ptt_pending_arm", 0):\r
-                return\r
-            self._vc_serial_canceling = 0\r
-            self._vc_cancel_deadline_ms = 0\r
-            if getattr(self, "_ptt_pending_arm", 0):\r
-                self._ptt_pending_arm = 0\r
-                print("[VC][serial] ptt_arm after {}".format(via))\r
-                self._ptt_arm_uplink()\r
-        except Exception:\r
-            pass\r
-\r
-    def _vc_tick_serial_cancel_timeout(self):\r
-        if not getattr(self, "_vc_serial_canceling", 0):\r
-            return\r
-        try:\r
-            dl = getattr(self, "_vc_cancel_deadline_ms", 0)\r
-            if dl and utime.ticks_diff(utime.ticks_ms(), dl) >= 0:\r
-                print("[VC][serial] cancel_wait timeout -> arm")\r
-                self._vc_on_ptt_cancel_settled(via="timeout")\r
-        except Exception:\r
-            pass\r
-\r
-    def _vc_fsm_dispatch_cb(self, data):\r
-        """btn 线程:处理服务端 FSM 事件(单对话串行)。"""\r
-        try:\r
-            ev, jsload = data\r
-        except Exception:\r
-            return\r
-        try:\r
-            actions = self.fsm.on_server_event(ev, jsload)\r
-            self._fsm_apply_actions(actions)\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-        if ev == "canceled":\r
-            self._vc_on_ptt_cancel_settled(via="canceled")\r
-\r
-    def _vc_dispatch_server_event(self, ev, jsload_data=None):\r
-        try:\r
-            if ev == "canceled":\r
-                self._vc_stop_downlink_for_interrupt("canceled")\r
-        except Exception:\r
-            pass\r
-        if getattr(self, "VC_mode", -1) == 1:\r
-            self._vc_enqueue_btn(self._vc_fsm_dispatch_cb, (ev, jsload_data))\r
-        else:\r
-            try:\r
-                self._fsm_apply_actions(self.fsm.on_server_event(ev, jsload_data))\r
-            except Exception:\r
-                pass\r
-\r
-    def _ptt_arm_uplink(self, t0=0):\r
-        """btn 线程:cancel 已结束或无需 cancel 时,发 audio.create 并开麦。"""\r
-        try:\r
-            self.is_ruppt_tts = 0\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self._ptt_audio_created = 0\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self.AI_ASR_flag = 0\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self.AI_client_VC_send_speech()\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self.VC_Speech_Start_now()\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self._vc_publish_led("listening", "ptt_begin/recording")\r
-        except Exception:\r
-            pass\r
-        self._ptt_end_led_suppress()\r
-        try:\r
-            if t0:\r
-                print("[PTT] record started (ms={})".format(utime.ticks_ms() - t0))\r
-        except Exception:\r
-            pass\r
-\r
-    def ptt_begin(self, reason="key.long_press"):\r
-        try:\r
-            if self.VC_mode != 1:\r
-                return\r
-        except Exception:\r
-            return\r
-\r
-        # PTT 新一轮开始:清理模式切换/提示音退出标志\r
-        try:\r
-            self.Close_flag = 0\r
-            self._ptt_await_server = 0\r
-        except Exception:\r
-            pass\r
-\r
-        t0 = 0\r
-        try:\r
-            t0 = utime.ticks_ms()\r
-        except Exception:\r
-            t0 = 0\r
-\r
-        self._ptt_enter_interrupt_arm()\r
-        try:\r
-            self._vc_publish_led("listening", "ptt_begin/pre")\r
-        except Exception:\r
-            pass\r
-\r
-        # 本地先硬静音再 VC_init,避免 DMA/队列尾包漏播\r
-        try:\r
-            self._ptt_hard_mute_local_playback()\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self.VC_init()\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if getattr(self, "VC_Play_flag", 0) == 1 or getattr(self, "play_pcm", None) is not None:\r
-                self.VC_Play_End()\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self._ptt_hard_mute_local_playback()\r
-        except Exception:\r
-            pass\r
-\r
-        # 若上一轮录音尚未结束,先强制停掉,避免 record_thread 复用出错\r
-        try:\r
-            if getattr(self, "VC_record_state_flag", 0) != 0:\r
-                self.VC_Speech_End()\r
-        except Exception:\r
-            pass\r
-\r
-        # 已在等 canceled:只刷新「待开录」,勿重复 audio.create\r
-        try:\r
-            if getattr(self, "_vc_serial_canceling", 0):\r
-                self._ptt_pending_arm = 1\r
-                print("[PTT] cancel in progress, refresh pending arm")\r
-                self._ptt_end_led_suppress()\r
-                return\r
-        except Exception:\r
-            pass\r
-\r
-        # 会话未连接:先建链并发 Start,等待 started 后再自动开麦\r
-        try:\r
-            if getattr(self, "AI_client_VC_isconnect", 0) != 1:\r
-                self._ptt_force_record_on_started = 1\r
-                self.AI_VC_start()\r
-                return\r
-        except Exception:\r
-            self._ptt_force_record_on_started = 1\r
-            try:\r
-                self.AI_VC_start()\r
-            except Exception:\r
-                pass\r
-            return\r
-\r
-        # 会话还没 initialized:先发 Start,请求服务端进入 started\r
-        if getattr(self, "_vc_session_started", 0) != 1:\r
-            self._ptt_force_record_on_started = 1\r
-            try:\r
-                try:\r
-                    if t0:\r
-                        print("[PTT] wait server.started (ms={})".format(utime.ticks_ms() - t0))\r
-                except Exception:\r
-                    pass\r
-                self.AI_client_VC_start_requset()\r
-            except Exception:\r
-                pass\r
-            return\r
-\r
-        # 服务端仍在思考/TTS:先 cancel,在 btn 队列收到 canceled 后再开录(方案甲串行)\r
-        if self._vc_ptt_need_interrupt_wait():\r
-            self._vc_begin_ptt_cancel_wait("ptt_begin")\r
-            return\r
-\r
-        self._ptt_arm_uplink(t0)\r
-\r
-    def _vc_effective_asr_text(self):\r
-        try:\r
-            t = (getattr(self, "AI_ASR_str", "") or "").strip()\r
-            if t:\r
-                return t\r
-            return (getattr(self, "_last_asr_result_text", "") or "").strip()\r
-        except Exception:\r
-            return ""\r
-\r
-    def _vc_long_dialogue_auto_uplink_ok(self, reason):\r
-        """长对话 canceled/completed 自动开麦:须有有效 ASR,且防抖/勿在 LLM 无 TTS 后抢开。"""\r
-        try:\r
-            if reason not in ("canceled", "completed", "local.play_end", "audio.played"):\r
-                return True\r
-            if getattr(self, "Close_flag", 0) == 1:\r
-                return False\r
-            if reason == "canceled" and getattr(self, "_vc_pending_interrupt_uplink", 0) == 1:\r
-                pass\r
-            elif not self._vc_effective_asr_text():\r
-                try:\r
-                    print("[VC][uplink_ctrl] Deny START by {} cause=empty_asr".format(reason))\r
-                except Exception:\r
-                    pass\r
-                return False\r
-            if reason == "completed":\r
-                if getattr(self, "_turn_has_tts_started", 0) == 0:\r
-                    try:\r
-                        if getattr(self, "_llm_completed_ms", 0) or getattr(self.fsm, "turn", None) == VCTurnState.Thinking:\r
-                            print("[VC][uplink_ctrl] Deny START by completed cause=llm_without_tts")\r
-                            return False\r
-                    except Exception:\r
-                        pass\r
-            try:\r
-                now = utime.ticks_ms()\r
-                last = getattr(self, "_vc_last_auto_uplink_ms", 0)\r
-                if last and utime.ticks_diff(now, last) < 1200:\r
-                    print("[VC][uplink_ctrl] Deny START by {} cause=auto_uplink_debounce".format(reason))\r
-                    return False\r
-                self._vc_last_auto_uplink_ms = now\r
-            except Exception:\r
-                pass\r
-        except Exception:\r
-            pass\r
-        return True\r
-\r
-    def _vc_led_state_debug_enabled(self):\r
-        try:\r
-            if "vc_config" in globals() and vc_config:\r
-                return vc_config.get_bool("LED_STATE_DEBUG", True)\r
-        except Exception:\r
-            pass\r
-        return True\r
-\r
-    def _vc_led_should_standby_idle(self):\r
-        """未录音且非 TTS 播中:待机/提示音/模式切换/单对话轮间应灭灯(DES-L3 idle),勿被 FSM.turn=Listening 误导。"""\r
-        try:\r
-            if getattr(self, "VC_record_state_flag", 0) != 0:\r
-                return False\r
-            if self._vc_tts_playback_active():\r
-                return False\r
-            if getattr(self, "_ptt_await_server", 0) == 1:\r
-                return False\r
-            t = getattr(self.fsm, "turn", VCTurnState.Idle)\r
-            if t in (\r
-                VCTurnState.Thinking,\r
-                VCTurnState.TtsReceiving,\r
-                VCTurnState.Playing,\r
-                VCTurnState.AwaitPlayedAck,\r
-            ):\r
-                return False\r
-            if getattr(self, "Close_flag", 0) == 1:\r
-                return True\r
-            if getattr(self, "VC_mode", -1) == 1:\r
-                return True\r
-            k = getattr(self, "_last_tts_kind", "") or ""\r
-            if k in ("prompt", "boot_prompt", "mode_switch_long", "mode_switch_ptt"):\r
-                return True\r
-        except Exception:\r
-            pass\r
-        return False\r
-\r
-    def _vc_publish_led(self, ai_state, reason=""):\r
-        try:\r
-            if getattr(self, "_ptt_led_lock_listening", 0) == 1:\r
-                if ai_state != "listening":\r
-                    if self._vc_led_state_debug_enabled():\r
-                        print("[LED] publish blocked ai={} reason={} (ptt_arm_lock)".format(\r
-                            ai_state, reason))\r
-                    return\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if self._vc_led_state_debug_enabled():\r
-                print("[LED] publish ai={} reason={} VC_mode={} rec={} turn={}".format(\r
-                    ai_state, reason, getattr(self, "VC_mode", -1),\r
-                    getattr(self, "VC_record_state_flag", -1),\r
-                    getattr(getattr(self, "fsm", None), "turn", "?")))\r
-        except Exception:\r
-            pass\r
-        try:\r
-            EventMesh.publish("set_led_state", {"ai_state": ai_state})\r
-        except Exception:\r
-            pass\r
-\r
-    def _vc_led_sync_from_fsm(self):\r
-        """按 FSM/真实 TTS 播放恢复 AI 灯;本地 aud.play 短音不参与。"""\r
-        try:\r
-            if getattr(self, "_ptt_led_lock_listening", 0) == 1:\r
-                if getattr(self, "VC_record_state_flag", 0) == 0:\r
-                    return\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if self._vc_led_should_standby_idle():\r
-                self._vc_publish_led("idle", "fsm_sync/standby_idle")\r
-                return\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if getattr(self, "VC_mode", -1) == 1 and getattr(self, "_ptt_await_server", 0) == 1:\r
-                self._vc_publish_led("thinking", "fsm_sync/ptt_await")\r
-                return\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if self._vc_tts_playback_active():\r
-                self._vc_publish_led("playing", "fsm_sync/tts")\r
-                return\r
-        except Exception:\r
-            pass\r
-        try:\r
-            t = getattr(self.fsm, "turn", VCTurnState.Idle)\r
-            if t in (VCTurnState.Thinking, VCTurnState.TtsReceiving):\r
-                self._vc_publish_led("thinking", "fsm_sync/thinking")\r
-            elif t in (VCTurnState.Playing, VCTurnState.AwaitPlayedAck):\r
-                # 服务端已标记播放态但本地 PCM 尚未起播:仍显示思考,勿误绿灯\r
-                self._vc_publish_led("thinking", "fsm_sync/await_tts")\r
-            elif t in (VCTurnState.Listening, VCTurnState.SpeechEnded, VCTurnState.SpeechStreaming):\r
-                self._vc_publish_led("listening", "fsm_sync/listening")\r
-            else:\r
-                self._vc_publish_led("idle", "fsm_sync/idle")\r
-        except Exception:\r
-            try:\r
-                self._vc_publish_led("idle", "fsm_sync/fallback")\r
-            except Exception:\r
-                pass\r
-\r
-    def _uplink_request_start(self, reason="", *args, **kwargs):\r
-        # 统一入口:避免多处同时 start 造成冲突(尤其是 TTS/播放阶段误开麦导致回声触发与状态紊乱)\r
-        try:\r
-            if self.Close_flag == 1:\r
-                try:\r
-                    if reason in ("canceled", "local.play_end", "audio.played", "key.press/continue"):\r
-                        print("[VC][uplink_ctrl] Deny START by {} cause=Close_flag".format(reason))\r
-                except Exception:\r
-                    pass\r
-                return\r
-        except Exception:\r
-            return\r
-\r
-        # 连续对话才允许“自动开新一轮上行”\r
-        try:\r
-            if self.VC_mode != 0:\r
-                try:\r
-                    if reason in ("canceled", "local.play_end", "audio.played"):\r
-                        print("[VC][uplink_ctrl] Deny START by {} cause=VC_mode({})".format(reason, self.VC_mode))\r
-                except Exception:\r
-                    pass\r
-                return\r
-        except Exception:\r
-            return\r
-\r
-        try:\r
-            if self.VC_mode == 0 and not self._vc_long_dialogue_auto_uplink_ok(reason):\r
-                return\r
-        except Exception:\r
-            pass\r
-\r
-        try:\r
-            if not self._vc_capture_allowed(reason):\r
-                try:\r
-                    print("[VC][uplink_ctrl] Deny START by {} cause=playback_busy".format(reason))\r
-                except Exception:\r
-                    pass\r
-                return\r
-        except Exception:\r
-            return\r
-\r
-        # ===== 业务门控(收敛到 FSM turn/session)=====\r
-        try:\r
-            if getattr(self.fsm, "session", VCSessionState.Disconnected) != VCSessionState.Started:\r
-                try:\r
-                    if reason in ("canceled", "local.play_end", "audio.played"):\r
-                        print("[VC][uplink_ctrl] Deny START by {} cause=fsm.session".format(reason))\r
-                except Exception:\r
-                    pass\r
-                return\r
-            turn = getattr(self.fsm, "turn", VCTurnState.Idle)\r
-            # 仅 Listening/SpeechEnded 允许开麦进入下一轮;其它阶段一律拒绝(避免抢跑打乱链路)\r
-            if turn not in (VCTurnState.Listening, VCTurnState.SpeechEnded):\r
-                try:\r
-                    if reason in ("canceled", "local.play_end", "audio.played"):\r
-                        print("[VC][uplink_ctrl] Deny START by {} cause=fsm.turn({})".format(reason, turn))\r
-                except Exception:\r
-                    pass\r
-                return\r
-        except Exception:\r
-            return\r
-\r
-        # 若当前不在录音上行,才允许开新一轮\r
-        if getattr(self, "VC_record_state_flag", 0) != 0:\r
-            # canceled 打断后:可能是僵死 flag=2(started 未真开麦),先停录复位再开麦\r
-            if reason == "canceled":\r
-                try:\r
-                    self._vc_clear_stale_record_state("uplink_start/canceled")\r
-                except Exception:\r
-                    pass\r
-            if getattr(self, "VC_record_state_flag", 0) != 0:\r
-                try:\r
-                    if reason in ("canceled", "local.play_end", "audio.played"):\r
-                        print("[VC][uplink_ctrl] Deny START by {} cause=VC_record_state_flag({})".format(\r
-                            reason, getattr(self, "VC_record_state_flag", 0)))\r
-                except Exception:\r
-                    pass\r
-                return\r
-\r
-        # 新一轮开始录音:允许上行(上一轮可能已置 silence 闸门)\r
-        if getattr(self, "record_data_save_flag", 0) == 0:\r
-            self._server_vad_silence = 0\r
-\r
-        print("[VC][uplink_ctrl] Request START by {}".format(reason))\r
-        try:\r
-            if reason == "canceled" and getattr(self, "_vc_pending_interrupt_uplink", 0) == 1:\r
-                self._vc_pending_interrupt_uplink = 0\r
-                print("[VC][uplink_ctrl] interrupt_uplink armed -> start (canceled)")\r
-        except Exception:\r
-            pass\r
-        self.VC_Speech_Start_now()\r
-\r
-    def _uplink_request_stop(self, reason="", *args, **kwargs):\r
-        # 统一入口:避免多处同时 stop 造成冲突\r
-        if getattr(self, "record_data_save_flag", 0) == 0:\r
-            self._server_vad_silence = 1\r
-\r
-        if getattr(self, "VC_record_state_flag", 0) == 0:\r
-            try:\r
-                if getattr(self, "VC_mode", 0) != 1:\r
-                    print("[VC][long_idle] _uplink_request_stop skip (record_state==0) reason={}".format(reason))\r
-            except Exception:\r
-                pass\r
-            return\r
-\r
-        try:\r
-            if getattr(self, "VC_mode", 0) != 1:\r
-                print("[VC][long_idle] step=uplink_stop_exec reason={} VC_record_state_flag={}".format(\r
-                    reason, getattr(self, "VC_record_state_flag", -1)))\r
-        except Exception:\r
-            pass\r
-        print("[VC][uplink_ctrl] Request STOP by {}".format(reason))\r
-        self.VC_Speech_End()\r
-\r
-    def _uplink_mark_start(self, first_len=0):\r
-        try:\r
-            if self._uplink_active == 1:\r
-                return\r
-            self._uplink_active = 1\r
-            self._uplink_bytes = 0\r
-            self._uplink_start_ms = utime.ticks_ms()\r
-            print("[VC][uplink] START len={} {} VC_record_state_flag={}".format(\r
-                first_len, self._vc_fsm_log_snip(), self.VC_record_state_flag))\r
-        except Exception:\r
-            pass\r
-\r
-    def _uplink_mark_end(self, reason=""):\r
-        try:\r
-            if self._uplink_active != 1:\r
-                return\r
-            dur = utime.ticks_diff(utime.ticks_ms(), self._uplink_start_ms) if self._uplink_start_ms else -1\r
-            print("[VC][uplink] END reason={} bytes={} dur_ms={} {} VC_record_state_flag={}".format(\r
-                reason, self._uplink_bytes, dur, self._vc_fsm_log_snip(), self.VC_record_state_flag))\r
-        except Exception:\r
-            pass\r
-        self._uplink_active = 0\r
-        self._uplink_bytes = 0\r
-        self._uplink_start_ms = 0\r
-\r
-        # 最近一次 TTS 的语义来源(dialog/prompt/mode_switch_long/mode_switch_ptt/...)\r
-        self._last_tts_kind = ""\r
-        # 下一个“无 dialog_id 的提示音 TTS”在 tts.started 时采用该 kind\r
-        self._pending_tts_kind = ""\r
-\r
-    # ===== 状态机动作接口(骨架)=====\r
-    # 说明:统一把“副作用动作”收敛到这些方法,便于后续把 recv/timer 的 if/flag 逻辑迁移到 VCStateMachine。\r
-    def request_send_start(self):\r
-        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_start_requset})\r
-\r
-    def request_send_audio_create(self):\r
-        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_send_speech})\r
-\r
-    def request_send_audio_completed(self):\r
-        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_end_speech})\r
-\r
-    def request_send_audio_played(self):\r
-        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_audio_played})\r
-\r
-    def request_send_synthesis(self, text):\r
-        try:\r
-            self.AI_TTS_str = text\r
-        except Exception:\r
-            pass\r
-        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_synthesis})\r
-\r
-    def request_uplink_start(self, reason=""):\r
-        EventMesh.publish("publish_AI_btnInfo", {"func": self._uplink_request_start, "data": reason})\r
-\r
-    def request_uplink_stop(self, reason=""):\r
-        EventMesh.publish("publish_AI_btnInfo", {"func": self._uplink_request_stop, "data": reason})\r
-\r
-    def _fsm_apply_actions(self, actions):\r
-        try:\r
-            for a in actions:\r
-                if not a:\r
-                    continue\r
-                if a[0] == "uplink_stop":\r
-                    self.request_uplink_stop(a[1] if len(a) > 1 else "")\r
-                elif a[0] == "uplink_start":\r
-                    self.request_uplink_start(a[1] if len(a) > 1 else "")\r
-                elif a[0] == "play_start":\r
-                    if getattr(self, "VC_mode", -1) == 1:\r
-                        if self._ptt_should_skip_remote_playback():\r
-                            if self._vc_led_state_debug_enabled():\r
-                                print("[VC][serial] skip play_start (ptt_interrupt)")\r
-                            continue\r
-                    try:\r
-                        self.fsm.on_local_play_start()\r
-                    except Exception:\r
-                        pass\r
-                    EventMesh.publish("publish_AI_btnInfo", {"func": self.VC_Play_Start})\r
-                elif a[0] == "send_audio_played":\r
-                    self.request_send_audio_played()\r
-        except Exception:\r
-            pass\r
-\r
-    # AI\r
-\r
-    def _print_hw_diag(self):\r
-        # 仅诊断使用:打印当前分支能看到的麦克/音频相关配置和运行时状态。\r
-        # JSWM2 老分支没有 device_profile/_PROFILE,这里宽容处理\r
-        try:\r
-            profile = _PROFILE if isinstance(_PROFILE, dict) else {}\r
-        except Exception:\r
-            profile = {}\r
-        dev_type_profile = profile.get("dev_type", None)\r
-        flags = profile.get("flags", {})\r
-        hardware = profile.get("hardware", {})\r
-        product_cfg = profile.get("product_cfg", {})\r
-        vc_cfg = {}\r
-        try:\r
-            vc_cfg = product_cfg.get("vc", {})\r
-        except Exception:\r
-            vc_cfg = {}\r
-\r
-        print("[hw-diag] ===== AI_VC hardware diag =====")\r
-        print("[hw-diag] profile.dev_type        =", dev_type_profile)\r
-        print("[hw-diag] profile.flags           =", flags)\r
-        print("[hw-diag] profile.hardware        =", hardware)\r
-        print("[hw-diag] product_cfg.vc          =", vc_cfg)\r
-\r
-        try:\r
-            print("[hw-diag] VC_record_samplerate   =", self.VC_record_samplerate)\r
-            print("[hw-diag] VC_play_samplerate     =", self.VC_play_samplerate)\r
-        except Exception:\r
-            pass\r
-        try:\r
-            print("[hw-diag] vad_silence_runtime    =", self.vad_silence)\r
-        except Exception:\r
-            pass\r
-        try:\r
-            print("[hw-diag] record_data_save_flag  =", self.record_data_save_flag)\r
-            print("[hw-diag] record_max_bytes       =", self.record_max_bytes)\r
-        except Exception:\r
-            pass\r
-\r
-        try:\r
-            g = self.record.gain_get()\r
-        except Exception as e:\r
-            g = "gain_get_error: {}".format(e)\r
-        print("[hw-diag] record.gain_get         =", g)\r
-        try:\r
-            print("[hw-diag] type(record)            =", type(self.record))\r
-            print("[hw-diag] type(aud)               =", type(self.aud))\r
-        except Exception:\r
-            pass\r
-        print("[hw-diag] ===== end of hw-diag =====")\r
-\r
-    def __init__(self):\r
-        # import voiceCall\r
-        # voiceCall.callStart("10086")\r
-        # return\r
-        self.http_get_config_flag = 0\r
-        if self.http_get_config_flag == 1:\r
-            try:\r
-                self.http_get_config()#获取/更新配置参数\r
-            except Exception as e:\r
-                usys.print_exception(e)\r
-        self.VC_30S_timer = osTimer()\r
-        self.ai_key_press_audio = osTimer()\r
-        self.VC_30S_timer.start(30000, 1, self.VC_30S_timer_cb)\r
-        self.online_kws = 0#是否开启线上关键词\r
-        self.get_config_timer_count = 0\r
-        self.record_data_save_flag = 0 #是否保存录音文件\r
-        self.play_data_save_flag = 0#是否保存tts数据到文件\r
-        self.record_fd = None\r
-        self.play_fd = None\r
-        self.is_VC_ACK = 0 #是否开启ack应答模式\r
-        self.is_long_record = 0 #是否开启长录音模式(语音打断模式)\r
-        self.ai_OTA = OTA()\r
-        self.is_auth = 1 #是否开启dmp鉴权\r
-        #self.VC_mode = 1 #0:短按连续问答模式 1:长按录音模式\r
-        self.VC_mode = EventMesh.publish("get_storage_config","VC_mode")#\r
-        if self.VC_mode == None :\r
-            self.VC_mode = 0\r
-        self.is_up_error= 0 #是否上传错误id\r
-        self.Close_flag =0 #=1时,连续对话播放完音频就退出\r
-        # 服务端 VAD 结束标志:收到 vad.silence 后置 1,用于硬闸门禁止继续上行音频\r
-        # (避免仍持续上行导致服务端推迟/抑制后续 TTS,从而一直停留在思考态蓝绿闪)\r
-        self._server_vad_silence = 0\r
-        # 单轮状态标志:用于判定 completed 是否应自动进入下一轮\r
-        self._turn_has_asr_completed = 0\r
-        self._turn_has_vad_silence = 0\r
-        self._turn_has_tts_started = 0\r
-        # VC 会话是否已进入 started(服务端 session initialized)\r
-        # 重要:在收到 started 前发送音频帧会触发服务端 "session not initialized" → failed → 端侧重连死循环\r
-        self._vc_session_started = 0\r
-        # 开机/提示音:本轮 started 不自动开麦(避免问候 TTS 前误进 listening)\r
-        self._skip_started_uplink_once = 0\r
-        self._last_tts_kind = ""\r
-        self._pending_tts_kind = ""\r
-        # Push-To-Talk(VAD关闭/单对话)是否已发送 audio.create\r
-        # 重要:协议要求 disable_vad=true 时必须 audio.create→音频帧→audio.completed\r
-        self._ptt_audio_created = 0\r
-        # PTT:松手等服务器期间保持 thinking 勿回蓝闪聆听\r
-        self._ptt_await_server = 0\r
-        # 方案甲 btnInfo 串行:打断时先 cancel,收到 canceled(或超时)后再 audio.create\r
-        self._vc_serial_canceling = 0\r
-        self._ptt_pending_arm = 0\r
-        self._vc_cancel_deadline_ms = 0\r
-        # PTT 打断:轮次号 + 开录前丢弃旧 TTS / 灯只显示聆听\r
-        self._ptt_arm_epoch = 0\r
-        self._ptt_interrupt_active = 0\r
-        self._ptt_led_lock_listening = 0\r
-        # 端侧主动发 synthesis 后到收到 tts.started 之间的窗口标志,用于阻止误开下一轮录音上行\r
-        self._tts_pending = 0\r
-        self._tts_pending_ms = 0\r
-        # 各类兜底的节流时间戳(避免“兜底过激/连续触发”)\r
-        self._fb_last_thinking_restart_ms = 0\r
-        self._fb_last_tts_pending_timeout_ms = 0\r
-        # thinking 兜底:部分情况下服务端只返回 llm.completed 而不触发 tts.started/completed,\r
-        # 会导致一直停留在 thinking。此时超时自动回到聆听。\r
-        self._llm_completed_ms = 0\r
-        # 语音上行统计(真实发送到 VC 发送队列的开始/结束)\r
-        self._uplink_active = 0\r
-        self._uplink_bytes = 0\r
-        self._uplink_start_ms = 0\r
-\r
-        # 状态机骨架(后续逐步把旧逻辑迁移进来)\r
-        self.fsm = VCStateMachine(self)\r
-\r
-        self.play_new_voice_flag =0 #播放文字留言flag\r
-\r
-        self.wait_first_flag = 0#是否进入未读判定(只播报一次)\r
-        self.wait_first_timer = osTimer()#如果播放未读留言的时候未读则不要播放了\r
-        self.wait_first_cnt = 0\r
-\r
-        self.wait_sort = 0#未读留言正在播第几条\r
-        EventMesh.subscribe("play_tts",self.play_tts)\r
-        EventMesh.subscribe("play_new_voice",self.play_new_voice)\r
-        EventMesh.subscribe("play_his_voice",self.play_his_voice)\r
-        EventMesh.subscribe("play_wait_voice",self.play_wait_voice)\r
-        EventMesh.subscribe("play_single_voice",self.play_single_voice)#不带tts的播放(闹钟)\r
-        EventMesh.subscribe("play_music",self.play_music)#播放音乐\r
-        EventMesh.subscribe("play_trans_tts",self.play_trans_tts)#接收翻译tts\r
-        EventMesh.subscribe("set_vad_silence",self.set_vad_silence)\r
-        EventMesh.subscribe("set_wait_first_flag",self.set_wait_first_flag)\r
-        EventMesh.subscribe("kws_even",self.kws_even)\r
-        EventMesh.subscribe("change_volume",self.change_volume)    \r
-        EventMesh.subscribe("find_cmd",self.find_cmd) #查询命令\r
-        EventMesh.subscribe("NLP_set_vad_silence",self.NLP_set_vad_silence) #设置聆听间隔\r
-        EventMesh.subscribe("change_voice",self.change_voice) #改变音色\r
-        EventMesh.subscribe("Close_VC",self.Close_VC) #外部结束对话\r
-        EventMesh.subscribe("get_recv_voice",self.get_recv_voice) #获取留言播放状态\r
-        EventMesh.subscribe("set_recv_voice",self.set_recv_voice) #设置留言播放状态\r
-        self.voice_timer = osTimer()#语音留言10S按下按键确定是否播放的判断定时器\r
-        self.voice_cnt = 0 #留言定时器计数\r
-        self.voicerupt_flag = 0#留言选择按键接听标志位\r
-        self.wait_voicerupt_flag = 0#未读留言选择按键接听标志位\r
-        self.recv_voice = 0 #是否收到留言\r
-        self.voice_notice = 0 #收到留言并播放则置为1,接下来要播放提示词\r
-        self.voice_choose_flag = 0#当tts和语音留言冲突时,为了防止语音地址被误清,做标志位在close做判断\r
-\r
-        self.revoice_timer = osTimer()#语音留言10S按下按键确定是否回复的判断定时器\r
-        self.revoice_cnt = 0 #回复留言定时器计数\r
-        self.revoicerupt_flag = 0#留言选择按键接听标志位\r
-\r
-        self.music_timer = osTimer()#音乐播放定时器,等待tts播完才放音乐\r
-        self.music_cnt = 0 #留言定时器计数\r
-\r
-        self.his_timer = osTimer()#历史留言播放定时器,等待tts播完再播留言\r
-        self.his_cnt = 0#历史留言定时器技术\r
-\r
-        self.wait_timer = osTimer()#未读留言播放定时器,等待tts播完再播留言\r
-        self.wait_cnt = 0#未读留言定时器技术\r
-\r
-        self.error_data = []\r
-        self.asr_taskid  = ""\r
-        self.VC_taskid  = ""\r
-        self.VC_server = 2 #0柒牌正式服务器,1:测试服务器1,2:测试服务器2\r
-        self.voice_flag = 0\r
-\r
-        #会议记录标志\r
-        self.meet_flag = 0\r
-        #AI翻译标志\r
-        self.trans_flag = 0\r
-\r
-        #历史留言查看判断标志\r
-        self.history_voice_flag = 0#公共变量判断 \r
-        #60S等待留言判断标志\r
-        self.wait_voice_flag = 0#公共变量判断 \r
-        EventMesh.publish("AI_alarm_open")#\r
-        self.server_ip = EventMesh.publish("get_storage_config","server_ip")#\r
-        if self.server_ip == None:\r
-            if self.VC_server == 1:# \r
-                self.server_ip="118.145.158.207:22083"\r
-            elif self.VC_server == 2:# \r
-                self.server_ip="118.145.158.207:22082"\r
-            elif self.VC_server == 4:# \r
-                self.server_ip="118.145.158.207:22084"\r
-            elif self.VC_server == 5:# \r
-                self.server_ip="amp-wss.raisound.com"\r
-            elif self.VC_server == 6:# \r
-                self.server_ip="118.145.158.207:22080"\r
-            elif self.VC_server == 7:# \r
-                self.server_ip="118.145.158.207:22080"\r
-            elif self.VC_server == 8:# \r
-                self.server_ip="115.190.94.208:22080"\r
-            elif self.VC_server == 86:# \r
-                self.server_ip="118.145.158.207:22086"    \r
-            else:\r
-                #self.server_ip="sevenpin.qipai.com.cn"\r
-                self.server_ip="118.145.158.207:22082"\r
-            # 默认走 constants.AMP_SERVER(由 default_srv.py 统一管理,可在 usr 覆盖)\r
-            try:\r
-                if constants.AMP_SERVER:\r
-                    self.server_ip = constants.AMP_SERVER\r
-            except Exception:\r
-                pass\r
-\r
-        #开机提示音\r
-        self.power_on_tone = EventMesh.publish("get_storage_config","power_on_tone")#\r
-        try:\r
-            if "vc_config" in globals() and vc_config:\r
-                if not vc_config.get_bool("POWER_ON_TONE_ENABLED", True):\r
-                    self.power_on_tone = None\r
-        except Exception:\r
-            pass\r
-        if self.power_on_tone == None:\r
-            try:\r
-                if "vc_config" in globals() and vc_config:\r
-                    self.power_on_tone = vc_config.get_attr("POWER_ON_TONE_DEFAULT", None)\r
-            except Exception:\r
-                pass\r
-\r
-        self.VC_record_TYPE = VC_record_TYPE_PCM #0:PCM 1:AMR\r
-        self.VC_play_TYPE = VC_play_TYPE_PCM #0:PCM 1:mp3 \r
-        self.VC_record_samplerate = 16000\r
-        self.VC_play_samplerate = 16000\r
-        self.VAD_data_len = 640 #10ms数据\r
-        self.is_VAD = 0#=1打开vad打断\r
-        # ARG_sample_rate,             // 采样率\r
-        # ARG_channel,                // 通道数\r
-        # ARG_frame_size,              // 每帧字节数\r
-        # ARG_energy_threshold,        // 能量阈值\r
-        # ARG_voice_time,              // 语音持续时间阈值(ms)\r
-        # ARG_silence_time,            // 静音持续时间阈值(ms)\r
-        # ARG_silence_start_threshold, // 静音开始阈值(ms)\r
-        self.vad = None\r
-        if self.is_VAD == 1:\r
-            self.vad = VAD.VAD(self.VC_record_samplerate ,1,self.VAD_data_len,600,100,300,100)\r
-        self.speech_rate = EventMesh.publish("get_storage_config","speech_rate")#语速配置\r
-        if self.speech_rate == None:\r
-            self.speech_rate = 0\r
-        \r
-        self.vad_silence = EventMesh.publish("get_storage_config","vad_silence")#语速配置\r
-        if self.vad_silence == None:\r
-            try:\r
-                if "vc_config" in globals() and vc_config:\r
-                    self.vad_silence = vc_config.get_int("VAD_SILENCE_DEFAULT", 300)\r
-                else:\r
-                    self.vad_silence = 300\r
-            except Exception:\r
-                self.vad_silence = 300\r
-\r
-        self.VC_TTS_voice_type = EventMesh.publish("get_storage_config","VC_TTS_voice_type")#音色配置\r
-        if self.VC_TTS_voice_type == None :\r
-            try:\r
-                if "vc_config" in globals() and vc_config:\r
-                    cfg = vc_config.get_attr("VC_TTS_VOICE_TYPE_DEFAULT", None)\r
-                    if isinstance(cfg, dict):\r
-                        self.VC_TTS_voice_type = cfg\r
-            except Exception:\r
-                pass\r
-        # 若配置未提供,沿用原逻辑内置兜底(保持兼容)\r
-        if self.VC_TTS_voice_type == None:\r
-            self.VC_TTS_voice_type = {\r
-                "女":{"bot_name":"柒妹妹","voice":"zh_female_wanwanxiaohe_moon_bigtts","lang":"zh-cn"},\r
-                "男":{"bot_name":"柒先生","voice":"zh_male_wennuanahu_moon_bigtts","lang":"zh-cn"},\r
-                "童":{"bot_name":"柒乐乐","voice":"ICL_zh_female_huoponvhai_tob","lang":"zh-cn"},\r
-            }\r
-            \r
-            # self.VC_TTS_voice_type = {0:"zh_female_xinlingjitang_moon_bigtts",1:"zh_male_wennuanahu_moon_bigtts",2:"ICL_zh_female_huoponvhai_tob",\r
-            #                           3:"aliyun::longjiaxin",4:"zh_female_daimengchuanmei_moon_bigtts"}\r
-            #self.VC_TTS_voice_type = ["aliyun::longxiaoxia","aliyun::longnan","aliyun::longling"]\r
-            \r
-            #EventMesh.publish("set_storage_config",["VC_TTS_voice_type",self.VC_TTS_voice_type])\r
-\r
-        try:\r
-            self.VC_TTS_voice_key = EventMesh.publish("get_storage_config","VC_TTS_voice_key")#音色配置\r
-            if self.VC_TTS_voice_key == None :\r
-                try:\r
-                    if "vc_config" in globals() and vc_config:\r
-                        self.VC_TTS_voice_key = vc_config.get_attr("VC_TTS_VOICE_KEY_DEFAULT", None)\r
-                except Exception:\r
-                    pass\r
-                if not self.VC_TTS_voice_key:\r
-                    self.VC_TTS_voice_key = "男"\r
-            if self.VC_TTS_voice_key in self.VC_TTS_voice_type:\r
-                pass\r
-            else:\r
-                self.VC_TTS_voice_key = list(self.VC_TTS_voice_type.keys())[0]\r
-            print("音色配置:{}:{}\r\n\r\n".format(self.VC_TTS_voice_key,self.VC_TTS_voice_type[self.VC_TTS_voice_key]))\r
-            self.VC_TTS_volume =  EventMesh.publish("get_storage_config","VC_TTS_volume")#音量配置\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-        #配置词\r
-        self.commands_mode = 2 #=1用大模型判断命令,=0用服务器本地插件判断命令 =2用设备本地判断方式\r
-        self.commands = []\r
-        if self.VC_TTS_volume == None:\r
-            self.VC_TTS_volume = 100\r
-        #可ota配置的参数变量\r
-        self.emotion = EventMesh.publish("get_storage_config","emotion")#情绪\r
-        if self.emotion == None:\r
-            self.emotion = ""\r
-        self.ota_update_notice = EventMesh.publish("get_storage_config","ota_update_notice")#OTA升级提示语\r
-        if self.ota_update_notice == None:\r
-            self.ota_update_notice = "" \r
-        self.AI_audio_queue_thread_id = 0\r
-        self.AI_audio_queue_thread_lock = 0\r
-        self.AI_VC_send_data_queue_thread_id = 0\r
-        self.AI_VC_send_data_queue_thread_lock = 0\r
-        self.AI_ASR_send_data_queue_thread_id = 0\r
-        self.AI_ASR_send_data_queue_thread_lock = 0\r
-        self.AI_record_tmp_queue = Queue(100)\r
-        self.AI_ASR_send_data_queue = Queue(200)\r
-        self.AI_VC_send_data_queue = Queue(100)\r
-        self.AI_audio_queue = Queue(100)\r
-        #self.AI_WS_connect_Queue = Queue(100) #释放用\r
-        self.AI_TTS_str = ""\r
-        self.AI_ASR_str = ""\r
-        self.AI_ASR_flag = 0\r
-        self.AI_ASR_str_tmp = ""\r
-        self.AI_ASR_send_buf_len = 0\r
-        self.AI_ASR_text_tmp = ""\r
-        self.AI_ASR_text_repet_count = 0\r
-        # #开始线程\r
-        # if self.AI_audio_queue_thread_id ==0:\r
-        #     self.AI_audio_queue_thread_id = _thread.start_new_thread(self.AI_audio_queue_thread, ())\r
-        # else:\r
-        #     print("AI_VC_start :AI_audio_queue_thread_id != 0 ")\r
-        # if self.AI_VC_send_data_queue_thread_id == 0:    \r
-        #     self.AI_VC_send_data_queue_thread_id = _thread.start_new_thread(self.AI_VC_send_data_queue_thread, ())\r
-            \r
-        self.VC_dialog_id = ""#\r
-        self.VC_message_id=None\r
-        self.VC_task_id =None\r
-        self.AI_client_VC = None\r
-        self.AI_client_VC_recv_id = 0\r
-        self.AI_client_VC_recv_lock = 0\r
-        self.AI_client_VC_isconnect =0\r
-        self.AI_client_VC_debug =0\r
-\r
-        self.asr_state = 0  # 预留:ASR 侧状态(当前未参与分支)\r
-        self.key_state = 0\r
-\r
-        self.AI_client_asr = None\r
-        self.AI_client_asr_recv_id = 0\r
-        self.AI_client_asr_recv_lock = 0\r
-        self.AI_client_asr_isconnect =0\r
-        self.AI_ASR_timer =  osTimer()\r
-        self.AI_ASR_timer_count = 0\r
-        self.AI_client_asr_debug =0\r
-        self.AI_ASR_check_timeout = 0\r
-        self.AI_ASR_error_timeout = 0\r
-        self.VC_Thinking_timerout = 0\r
-\r
-\r
-        self.record_pcm = None\r
-        self.play_pcm = None\r
-        self.VC_record_thread_id = 0\r
-        self.VC_record_thread_lock = 0\r
-        self.VC_pingping_timerout = 0\r
-        self.asr_pingping_timerout = 0\r
-        self.factory_timer = osTimer()\r
-        self._1S_timer = osTimer()\r
-        self.Audio_buf_size = 20*1024\r
-        self.record = audio.Record()\r
-        #self.record.end_callback(self.record_callback)\r
-        self.record.gain_set(4,10)\r
-        #self.record.gain_set(4,12)\r
-        self.aud = audio.Audio(0)\r
-        self.aud.setCallback(self.audio_cb)\r
-        # 确保 record/aud 已创建后再打印硬件/音频诊断信息(需要 gain_get/type(record))\r
-        try:\r
-            self._print_hw_diag()\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-        #self.aud.setVolume(self.VC_TTS_volume //10)\r
-        self.audio_play_state = 1 #音频播放完状态\r
-        self.close_timerout = 0\r
-        self._idle_deep_sec = 0\r
-        self._vc_idle_deep_armed_suppress = 0\r
-        self.LocalRespondingEnded_timerout = 0\r
-\r
-        self.VC_Play_flag = 0\r
-        self.play_flag = 0\r
-        self.VC_record_state_flag = 0\r
-        self.AI_key_long_press_flag = 0\r
-        self._asr_completed_tone_done = 0  # asr.completed 本地 think 提示每轮仅一次\r
-        self._last_asr_result_text = ""  # 保留最近 asr.result,防 VC_Speech_End 早于 asr.completed 清空\r
-        self._vc_last_auto_uplink_ms = 0  # 长对话自动开麦防抖\r
-        self._vc_local_play_active = 0  # aud.play 本地短音占用(与采集互斥)\r
-        self._vc_local_play_until_ms = 0\r
-        self._vc_pending_interrupt_uplink = 0  # 长对话短按打断:等 canceled 再开麦\r
-        self.websocket_error_count = 0\r
-        self.AI_new_voice_url = "" #未读语音地址/音乐播放地址/闹钟播放标志\r
-        self.music_play_flag = 0 #音乐播放标志\r
-        self.alarm_play_flag = 0 #闹钟播放标志\r
-        self.tts_in_data_len = 0\r
-        self.tts_out_data_len = 0\r
-        self._tts_first_write_ms = 0\r
-        self._tts_play_check_log_ms = 0\r
-        self._need_audio_played_ack = 0#tts.completed 后需在本地播报结束后上报 audio.played;勿用 tts_in/out:tts.completed 会再调 VC_Play_Start 把二者清零\r
-        self.VC_satrt_flag = 0\r
-        self.VC_stop_play_audio_flag = 0\r
-        self.VC_satrt_kws_flag = 0\r
-        self.session_id = ""\r
-        self.stop_voice_timer=osTimer()\r
-        self.pwk_cnt_voice = 0 #不听语音后,积攒的次数,需要一起播放\r
-        self.pwk_notice_flag = 0#是否处于积攒留言状态(用来实现单次提醒)\r
-        self.is_noread_voice = EventMesh.publish("get_storage_config","is_noread_voice")\r
-        if self.is_noread_voice == None:\r
-            self.is_noread_voice = 0#是否还有未读留言(开机判断)\r
-        self.is_ruppt_tts = 0#是否已经打断tts播放\r
-        EventMesh.subscribe("AI_key_press", self.AI_key_press)\r
-        EventMesh.subscribe("BT_AI_key_press", self.BT_AI_key_press)\r
-        EventMesh.subscribe("stop_AI_record", self.stop_AI_record)\r
-        EventMesh.subscribe("AI_key_release", self.AI_key_release)\r
-        EventMesh.subscribe("pwk_key_double_press", self.pwk_key_double_press)\r
-        EventMesh.subscribe("AI_key_long_press", self.AI_key_long_press)\r
-        EventMesh.subscribe("pwk_key_release", self.pwk_key_release)\r
-        EventMesh.subscribe("get_AI_audio_queue", self.get_AI_audio_queue)\r
-        EventMesh.subscribe("get_VC_satrt_flag", self.get_VC_satrt_flag)\r
-        EventMesh.subscribe("get_vc_stream_active", self.get_vc_stream_active)\r
-        EventMesh.subscribe("get_vc_tts_playback_active", self.get_vc_tts_playback_active)\r
-        EventMesh.subscribe("battery_priority_mute_vc", self.battery_priority_mute_vc)\r
-        EventMesh.subscribe("VC_error", self.VC_error)\r
-        EventMesh.subscribe("audio_test", self.audio_test)\r
-        EventMesh.subscribe("audio_test_close", self.audio_test_close)\r
-        EventMesh.subscribe("set_record_data_save_flag", self.set_record_data_save_flag)\r
-        EventMesh.subscribe("set_VC_TTS_voice_type", self.set_VC_TTS_voice_type)\r
-        EventMesh.subscribe("SET_TTS_VOLUME", self.set_tts_volume)\r
-        EventMesh.subscribe("set_VC_stop_play_audio_flag", self.set_VC_stop_play_audio_flag)\r
-        EventMesh.subscribe("set_yuyinaudio_flag",self.set_yuyinaudio_flag)\r
-        EventMesh.subscribe("get_yuyinaudio_flag",self.get_yuyinaudio_flag)\r
-        EventMesh.subscribe("set_pwk_cnt_voice",self.set_pwk_cnt_voice)\r
-        EventMesh.subscribe("get_pwk_cnt_voice",self.get_pwk_cnt_voice)\r
-        EventMesh.subscribe("exit_voice_message_mode",self.exit_voice_message_mode) #超时未退出语音留言模式\r
-        EventMesh.subscribe("come_meet_mode",self.come_meet_mode)#进入会议模式\r
-        EventMesh.subscribe("exit_meet_mode",self.exit_meet_mode)#退出会议模式\r
-        EventMesh.subscribe("come_trans_mode",self.come_trans_mode)#进入翻译模式\r
-        EventMesh.subscribe("exit_trans_mode",self.exit_trans_mode)#退出翻译模式\r
-        EventMesh.subscribe("set_trans_flag",self.set_trans_flag)#设置翻译状态\r
-        EventMesh.subscribe("get_audio_play_state",self.get_audio_play_state)\r
-        EventMesh.subscribe("set_voicerupt_flag",self.set_voicerupt_flag)#按键中断触发留言播放\r
-        EventMesh.subscribe("set_wait_voicerupt_flag",self.set_wait_voicerupt_flag)#按键中断触发未读留言播放 \r
-        EventMesh.subscribe("get_recv_voice",self.get_recv_voice)#获取是否收到留言\r
-        EventMesh.subscribe("rate_change",self.rate_change)#直接修改语速\r
-        EventMesh.subscribe("get_history_voice_flag",self.get_history_voice_flag)#获取查找历史留言状态\r
-        EventMesh.subscribe("set_history_voice_flag",self.set_history_voice_flag)#修改查找历史留言状态\r
-        EventMesh.subscribe("clean_unread_vocie",self.clean_unread_vocie)#修改查找历史留言状态\r
-        EventMesh.subscribe("get_wait_voice_flag",self.get_wait_voice_flag)#获取60S等待留言播放定时器状态\r
-        EventMesh.subscribe("set_wait_voice_flag",self.set_wait_voice_flag)#修改60S等待留言播放定时器状态\r
-        EventMesh.subscribe("rate_move",self.rate_move)#NLP修改语速\r
-        EventMesh.subscribe("find_rate",self.find_rate)#NLP查询语速\r
-        EventMesh.subscribe("set_pwk_notice_flag",self.set_pwk_notice_flag)#设置未读状态\r
-        # EventMesh.subscribe("get_voicerupt_flag",self.get_voicerupt_flag)#获取留言播放状态\r
-        EventMesh.subscribe("chenggong_tone",self.chenggong_tone)\r
-        \r
-        pass\r
-        EventMesh.publish("set_led_state", {"ai_state": "idle"})\r
-        self.dialog_attributes_provider =  EventMesh.publish("get_storage_config","dialog_attributes_provider")#智能体类型配置\r
-        if self.dialog_attributes_provider == None:\r
-            self.dialog_attributes_provider = "coze"\r
-        self.outbound_attributess_provider = EventMesh.publish("get_storage_config","outbound_attributess_provider")#tts服务选择\r
-        if self.outbound_attributess_provider == None:\r
-            self.outbound_attributess_provider = "volcengine"\r
-        self.inbound_attributess_provider = EventMesh.publish("get_storage_config","inbound_attributess_provider")#tts服务选择\r
-        if self.inbound_attributess_provider == None:\r
-            self.inbound_attributess_provider = "volcengine"\r
-        \r
-        self.bot_name = "柒先生"\r
-        try:\r
-            if "bot_name" in self.VC_TTS_voice_type[self.VC_TTS_voice_key]:\r
-                self.bot_name = self.VC_TTS_voice_type[self.VC_TTS_voice_key]["bot_name"]\r
-            else:\r
-                print("not bot_name!!!!!!!!!!!!!!!!!!!!!!")\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-        try:\r
-            bt_ota_flag=0\r
-            try:\r
-                if "vc_config" in globals() and vc_config and vc_config.get_bool("BT_OTA_ENABLED", False):\r
-                    bt_ota_flag = self.ai_OTA.bt_ota()#先检查蓝牙是否要升级\r
-            except Exception:\r
-                pass\r
-\r
-            cat1_ota_flag = self.ai_OTA.min_sys_ota()\r
-            if bt_ota_flag==1 or cat1_ota_flag == 1:\r
-            #if 0:    \r
-                if self.ota_update_notice != "":\r
-                    self.AI_TTS_str = self.ota_update_notice\r
-                else:\r
-                    self.AI_TTS_str = "检测到有新版本需要升级啦!升级时可千万别断电或乱按哦,升级完成后设备会自动重启,重启就代表升级成功"\r
-                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) \r
-                self.Close_flag = 1\r
-            # if self.ai_OTA.min_sys_ota()==2:\r
-            #     self.AI_TTS_str = "检测到有新版本需要升级啦!电量低于40%,无法保证升级正常执行,请充满电后重启设备再次升级!"\r
-            #     EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) \r
-            else:\r
-                try:\r
-                    self.ai_OTA.ota_result()\r
-                    self.ai_OTA.pots_ICCID()\r
-                except Exception as e:\r
-                    usys.print_exception(e)\r
-                #开机提示语,如果联网失败,直接提示信号弱\r
-                \r
-                \r
-                #EventMesh.publish("get_storage_config",["powerRestart",1])\r
-                powerRestart =  EventMesh.publish("get_storage_config","powerRestart")#=1非正常重启\r
-                if powerRestart == None:\r
-                    powerRestart = 0\r
-                if powerRestart == 1:\r
-                    EventMesh.publish("set_storage_config",["powerRestart",0])\r
-                    \r
-                else:\r
-                    pass\r
-                    self.AI_TTS_str = self.power_on_tone.replace("bot_name", self.bot_name)\r
-                    print("开机提示:{}".format(self.AI_TTS_str))\r
-                    try:\r
-                        self._pending_tts_kind = "boot_prompt"\r
-                        self._last_tts_kind = "boot_prompt"\r
-                        self._skip_started_uplink_once = 1\r
-                    except Exception:\r
-                        pass\r
-                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start})   \r
-\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-    def set_VC_stop_play_audio_flag(self,event = None, msg = None):\r
-        self.VC_stop_play_audio_flag = msg\r
-        print("self.VC_stop_play_audio_flag:{}".format(self.VC_stop_play_audio_flag))\r
-\r
-    def rate_change(self,event,msg):#直接设置语速(数字)\r
-        self.speech_rate = msg\r
-        EventMesh.publish("set_storage_config",["speech_rate",self.speech_rate])\r
-        print("set_speech_rate:{}".format(self.speech_rate))\r
-    def rate_move(self,event,msg):#设置语速(模糊(快/慢一点))\r
-        if msg == 1 and self.speech_rate<50:#增大\r
-            self.speech_rate += 10\r
-        else:#减小\r
-            if self.speech_rate >-50:\r
-                self.speech_rate -= 10\r
-        EventMesh.publish("set_storage_config",["speech_rate",self.speech_rate])\r
-        print("set_speech_rate:{}".format(self.speech_rate))\r
-    def find_rate(self,event,msg):#查询APP语速\r
-        print("查询语速处理:原始数据为:{}".format(self.speech_rate))\r
-        y = 1\r
-        if self.speech_rate >= -50 and self.speech_rate < 0:#负数\r
-            y = 1 - (-0.01 * self.speech_rate) \r
-        elif self.speech_rate <= 50 and self.speech_rate >= 0:#正数\r
-            y = 0.02 * self.speech_rate + 1.0\r
-        print("处理后数据为{}".format(y))\r
-        rate = str(y) + "倍"\r
-        return rate\r
-    def set_pwk_notice_flag(self,event,msg):#设置未读状态\r
-        self.pwk_notice_flag =  msg\r
-    #获取60S等待留言播放定时器状态\r
-    def get_wait_voice_flag(self,event,msg):\r
-        return self.wait_voice_flag\r
-    def set_wait_voice_flag(self,event,msg):\r
-        # print("未读留言标记修改为:{}".format(msg))\r
-        self.wait_voice_flag = msg\r
-\r
-    #查看历史留言状态修改\r
-    def get_history_voice_flag(self,event,msg):\r
-        return self.history_voice_flag\r
-    def set_history_voice_flag(self,event,msg):\r
-        # print("历史留言标记修改为:{}".format(msg))\r
-        self.history_voice_flag = msg\r
-    def clean_unread_vocie(self,event,msg):\r
-        self.pwk_cnt_voice = 0\r
-        self.pwk_notice_flag = 0\r
-        EventMesh.publish("set_app_new_voice_flag",0)\r
-    def audio_test(self,event = None, msg = None):\r
-        self._uplink_request_start("audio_test")\r
-        self.VC_Play_Start()      \r
-    def audio_test_close(self,event = None, msg = None):\r
-        self._uplink_request_stop("audio_test_close")\r
-        self.VC_Play_End()    \r
-        if self.record_data_save_flag == 1:\r
-            if self.record_fd :\r
-                self.record_fd.close()\r
-            self.record_fd = None\r
-    def set_record_data_save_flag(self,event = None, msg = None):\r
-        self.record_data_save_flag = msg\r
-    def post_processor_after_instantiation(self):\r
-        pass\r
-    def get_AI_audio_queue(self, event, msg):\r
-        return self.AI_audio_queue\r
-    def get_VC_satrt_flag(self, event, msg):\r
-        return self.VC_satrt_flag\r
-    #检查有没有意外打开ai部分功能\r
-    def check_vc_start(self):\r
-        print("check_vc_start:{} {} {} {}".format(self.VC_satrt_flag,self.VC_record_state_flag,self.AI_client_VC_isconnect,self.VC_Play_flag))\r
-        if self.VC_satrt_flag == 0:\r
-            if self.VC_record_state_flag != 0 or self.AI_client_VC_isconnect == 1 or  self.VC_Play_flag == 1:\r
-                self.Close()\r
-    def VC_30S_timer_cb(self, *args): #\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_30S_timer_cb_}) \r
-\r
-    def VC_30S_timer_cb_(self):    \r
-        self.get_config_timer_count += 1\r
-        if self.get_config_timer_count >= 2*60*2:#2小时更新配置参数\r
-            self.get_config_timer_count = 0\r
-            if self.http_get_config_flag == 1:\r
-                try:\r
-                    self.http_get_config()#获取/更新配置参数\r
-                except Exception as e:\r
-                    usys.print_exception(e)\r
-        self.check_vc_start()#30秒检查一下有没有关闭\r
-    #提交错误id           \r
-    def ud_error(self,error_data):\r
-        response = None\r
-        if self.is_up_error == 1:\r
-            try:\r
-                # currentVersion = versions.Build_Time\r
-                # currentVersion = "7PIN-"+currentVersion[:10]+"-"+currentVersion[11:-3]\r
-                url = "http://{}/health?task_id={}".format(self.server_ip,error_data[0]+error_data[1])\r
-                print(url)\r
-                response = request.get(url,timeout=5)\r
-                if response.status_code == 200:\r
-\r
-                    print("ud_error:ok")\r
-                else:\r
-                    print("ud_error  error!!!!")\r
-                    for i in response.text:\r
-                        print(i)\r
-                response.close()\r
-            except Exception as e:\r
-                usys.print_exception(e)\r
-                if response != None:\r
-                    response.close()\r
-    #获取配置参数\r
-    def http_get_config(self, event=None, msg=None):\r
-        print("http_get_config")\r
-\r
-        response = None\r
-        #http://180.184.206.142/bz-api/dev/config?deviceId=xxx\r
-        try:\r
-            url = (constants.DMP_BASE + "/dev/config/{}").format(modem.getDevImei())\r
-                \r
-                \r
-            print(url)\r
-            response = request.get(url,timeout=5)\r
-            if response.status_code == 200:\r
-                data = response.json()\r
-                print("http_get_config:{}".format(data))\r
-                \r
-                if data:\r
-                    try:\r
-                        if data["code"] == 20000:#有配置\r
-                            if data["data"] :\r
-                                if "version" in data["data"]:\r
-                                    config_version = EventMesh.publish("get_storage_config","config_version")#音量配置\r
-                                    if config_version != data["data"]["version"]:\r
-                                        config_version = data["data"]["version"]\r
-                                        EventMesh.publish("set_storage_config",["config_version",config_version])\r
-                                        if "VC_TTS_voice_type" in data["data"]:\r
-                                            self.VC_TTS_voice_type = data["data"]["VC_TTS_voice_type"]\r
-                                            EventMesh.publish("set_storage_config",["VC_TTS_voice_type",self.VC_TTS_voice_type])\r
-                                            print("配置VC_TTS_voice_type成功")\r
-                                        if "server_ip" in data["data"]:\r
-                                            self.server_ip = data["data"]["server_ip"]\r
-                                            EventMesh.publish("set_storage_config",["server_ip",self.server_ip])\r
-                                            print("配置server_ip成功")\r
-                                        if "power_on_tone" in data["data"]:\r
-                                            self.power_on_tone = data["data"]["power_on_tone"]\r
-                                            EventMesh.publish("set_storage_config",["power_on_tone",self.power_on_tone])\r
-                                            print("配置power_on_tone成功")\r
-                                        if "dialog_attributes_provider" in data["data"]:\r
-                                            self.dialog_attributes_provider = data["data"]["dialog_attributes_provider"]\r
-                                            EventMesh.publish("set_storage_config",["dialog_attributes_provider",self.dialog_attributes_provider])\r
-                                        if "outbound_attributess_provider" in data["data"]:\r
-                                            self.outbound_attributess_provider = data["data"]["outbound_attributess_provider"]\r
-                                            EventMesh.publish("set_storage_config",["outbound_attributess_provider",self.outbound_attributess_provider])\r
-                                            print("配置outbound_attributess_provider成功")\r
-                                        if "inbound_attributess_provider" in data["data"]:\r
-                                            self.inbound_attributess_provider = data["data"]["inbound_attributess_provider"]\r
-                                            EventMesh.publish("set_storage_config",["inbound_attributess_provider",self.inbound_attributess_provider])\r
-                                            print("配置inbound_attributess_provider成功")\r
-                                        if "speech_rate" in data["data"]:\r
-                                            self.speech_rate = EventMesh.publish("SET_SPEECH_RATE",float(data["data"]["speech_rate"]))\r
-                                            EventMesh.publish("set_storage_config",["speech_rate",self.speech_rate])\r
-                                            print("配置speech_rate成功")\r
-                                        if "emotion" in data["data"]:\r
-                                            self.emotion = data["data"]["emotion"]\r
-                                            EventMesh.publish("set_storage_config",["emotion",self.emotion])\r
-                                            print("配置emotion成功")\r
-                                        if "ota_update_notice" in data["data"]:\r
-                                            self.ota_update_notice =data["data"]["ota_update_notice"]\r
-                                            EventMesh.publish("set_storage_config",["ota_update_notice",self.ota_update_notice])\r
-                                            print("配置ota_update_notice成功")\r
-                                        if "VC_TTS_volume" in data["data"]:\r
-                                            self.VC_TTS_volume =int(data["data"]["VC_TTS_volume"])\r
-                                            EventMesh.publish("set_storage_config",["VC_TTS_volume",self.VC_TTS_volume])\r
-                                            print("配置VC_TTS_volume成功")\r
-                                            \r
-                    except Exception as e:\r
-                            usys.print_exception(e)\r
-            else:\r
-                print("status_code:{}".format(response.status_code))\r
-                print("http_get_config:error!!!!!!!!!!")\r
-            response.close()\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-            if response != None:\r
-                response.close()\r
-\r
-    def audio_cb(self,event):\r
-        if event == 0:\r
-            EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
-            self.audio_play_state =1\r
-            #EventMesh.publish("set_audio_state", 1)\r
-            print('audio-play start.')\r
-            # 本地 aud.play(开录/思考短音等)不改动 AI 灯,仅 TTS 流由 VC_Play_Start 控绿灯\r
-            if self._vc_led_state_debug_enabled():\r
-                print("[LED] audio_cb start skip (local_play)")\r
-            return\r
-        elif event == 7:\r
-            self.audio_play_state = 0\r
-            #EventMesh.publish("set_audio_state", 0)   \r
-            print('audio-play finish.!!!!!!')\r
-            try:\r
-                if getattr(self, "VC_mode", -1) == 1 and getattr(self, "_ptt_await_server", 0) == 1:\r
-                    self._vc_publish_led("thinking", "audio_cb/finish/ptt_await")\r
-                    return\r
-            except Exception:\r
-                pass\r
-            try:\r
-                if getattr(self, "VC_record_state_flag", 0) != 0:\r
-                    self._vc_publish_led("listening", "audio_cb/finish/recording")\r
-                    return\r
-            except Exception:\r
-                pass\r
-            try:\r
-                if self._vc_tts_playback_active():\r
-                    if self._vc_led_state_debug_enabled():\r
-                        print("[LED] audio_cb finish skip (tts_playback)")\r
-                    return\r
-            except Exception:\r
-                pass\r
-            try:\r
-                if getattr(self, "_ptt_led_lock_listening", 0) == 1:\r
-                    return\r
-            except Exception:\r
-                pass\r
-            self._vc_led_sync_from_fsm()\r
-    def set_tts_volume(self,event,msg):\r
-        EventMesh.publish("set_storage_config",["VC_TTS_volume",msg])#音量配置\r
-        self.VC_TTS_volume = msg\r
-        print("set_tts_volume:{} {}".format(self.VC_TTS_volume,msg))\r
-        if int(msg) <= 10:  #最小一定要设置1要不然没有声音\r
-            volume = 10 //10\r
-            self.aud.setVolume(volume)\r
-        else:\r
-            self.aud.setVolume(self.VC_TTS_volume //10)\r
-        pass\r
-    def get_yuyinaudio_flag(self, event=None, msg = None):\r
-        return self.voice_flag\r
-\r
-    def set_yuyinaudio_flag(self, event=None, msg = None):\r
-        self.voice_flag=0\r
-\r
-    #留言主动拒听\r
-    def get_pwk_cnt_voice(self, event=None, msg = None):\r
-        return self.pwk_cnt_voice\r
-\r
-    def set_pwk_cnt_voice(self, event=None, msg = None):\r
-        self.pwk_cnt_voice=msg\r
-        print("设置未读次数为:{}".format(self.pwk_cnt_voice))\r
-\r
-    #会议\r
-    def get_meetaudio_flag(self, event=None, msg = None):\r
-        return self.meet_flag\r
-    def set_meetaudio_flag(self, event=None, msg = None):\r
-        self.meet_flag = 0\r
-\r
-    def get_audio_play_state(self, event=None, msg = None):\r
-        return self.audio_play_state\r
-    \r
-    \r
-    \r
-    def come_meet_mode(self, event=None, msg = None):\r
-        if self.meet_flag == 0: #还是为0就提示进入会议纪要模式\r
-            EventMesh.publish("send_meet_open_flag_to_iot")#创建会议\r
-            self.meet_flag=1\r
-            exit_tts ="开启会议纪要,正在录音"\r
-            EventMesh.publish("play_tts",[exit_tts,1])\r
-            utime.sleep_ms(4000)\r
-            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_key_press})  \r
-        pass\r
-    def exit_meet_mode(self, event=None, msg = None):\r
-        if self.meet_flag == 1: #还是为1就提示已退出会议纪要模式\r
-            EventMesh.publish("send_meet_end_flag_to_iot")#结束会议\r
-            self.meet_flag=0\r
-            EventMesh.publish("stop_voice_record") #关闭语音录音\r
-            exit_tts ="录音结束,已退出会议纪要模式"\r
-            EventMesh.publish("play_tts",[exit_tts,1])\r
-        pass\r
-    def come_trans_mode(self, event=None, msg = None):\r
-        if self.trans_flag == 0: #还是为0就提示进入AI翻译模式\r
-            EventMesh.publish("send_trans_come_flag_to_iot")#进入翻译\r
-        pass\r
-    def exit_trans_mode(self, event=None, msg = None):\r
-        if self.trans_flag == 1: #还是为1就提示已退出AI翻译模式        \r
-            EventMesh.publish("stop_voice_record") #关闭语音录音\r
-            exit_tts ="已退出AI翻译模式"\r
-            EventMesh.publish("play_tts",[exit_tts,1])\r
-            self.trans_flag=0\r
-            EventMesh.publish("send_trans_end_flag_to_iot")#退出翻译\r
-        pass\r
-    def set_trans_flag(self,event,msg):\r
-        self.trans_flag = msg\r
-    def exit_voice_message_mode(self, event=None, msg = None):\r
-        if self.voice_flag == 1: #还是为1就提示已退出会议纪要模式\r
-            self.voice_flag=0\r
-            exit_tts ="已退出语音留言模式"\r
-            EventMesh.publish("play_tts",[exit_tts,1])\r
-        pass\r
-    def ai_key_press_audio_cb1(self, event=None, msg = None):\r
-        print("播放按键音!!!!!!!!!!")\r
-        # EventMesh.publish("audio_montage_play",["chenggong.mp3"])  \r
-        # self.chenggong_tone()\r
-        self.aud.aud_tone_play(10,400)#播放按键音\r
-    def ai_key_press_audio_cb2(self, event=None, msg = None):\r
-        EventMesh.publish("audio_montage_play",["chenggong.mp3"])  \r
-        # self.chenggong_tone() #播放按键音\r
-\r
-    def AI_key_press(self, event=None, msg = None):#按键按下\r
-        # 按键时打印 LED 聚合状态/最终模式(用于定位“绿灯不灭/闪一下”)\r
-        try:\r
-            if "vc_config" in globals() and vc_config and vc_config.get_bool("KEY_LED_DEBUG", False):\r
-                try:\r
-                    led = EventMesh.publish("get_led_debug_state")\r
-                except Exception:\r
-                    led = None\r
-                try:\r
-                    t = getattr(self.fsm, "turn", None)\r
-                except Exception:\r
-                    t = None\r
-                print("[KEY] AI_key_press VC_mode={} {} turn={} led={}".format(\r
-                    getattr(self, "VC_mode", -1), self._vc_fsm_log_snip(), t, led))\r
-        except Exception:\r
-            pass\r
-        # 单对话 PTT / 长对话分支(按键短按提示音已关,避免与开录等提示音叠在一起)\r
-        try:\r
-            if self._ai_key_press_ptt_mode():\r
-                return\r
-        except Exception:\r
-            pass\r
-        if self._ai_key_press_long_dialogue_interrupt():\r
-            return\r
-        try:\r
-            if self._ai_key_press_long_dialogue_when_session_ready():\r
-                return\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if self._ai_key_press_long_dialogue_wake():\r
-                EventMesh.publish(constants.EVENT.SYSTEM_UP)\r
-                return\r
-        except Exception:\r
-            pass\r
-\r
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
-        if EventMesh.publish("get_sos_flag")== 1:\r
-            return\r
-        if self.voice_flag == 1: #语音留言模式\r
-            self.stop_voice_timer.stop() #关闭语音留言模式定时器\r
-           # EventMesh.publish("stop_AI_record")\r
-            self.Close()\r
-            self.revoice_cnt = 0\r
-            self.revoice_timer.stop()#回复定时器关闭,计数器清零\r
-            EventMesh.publish("start_voice_record",2)#开始语音录音\r
-            return\r
-        elif self.meet_flag == 1:#会议纪要模式\r
-            self.Close()\r
-            EventMesh.publish("start_voice_record",3)#开始会议录音\r
-            return\r
-        elif self.trans_flag == 1:#AI翻译模式\r
-            return\r
-        \r
-        EventMesh.publish("stop_voice_record") #关闭语音录音\r
-        print("AI_key_press: unhandled mode branch")\r
-        self.AI_key_long_press_flag = 0\r
-    def BT_AI_key_press(self, event=None, msg = None):#按键按下\r
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
-        if EventMesh.publish("get_sos_flag")== 1:\r
-            return\r
-        if self.voice_flag == 1: #语音留言模式\r
-            tts_str  = "语音留言模式,长按按钮开始录音,松开按钮结束" \r
-            EventMesh.publish("play_tts",[tts_str,1])\r
-            return\r
-        \r
-        EventMesh.publish("stop_voice_record") #开启静默需要关闭\r
-        print("AI_key_press")\r
-        self.get_config_timer_count = 0\r
-        self.key_state = 1\r
-        # EventMesh.publish("audio_montage_play",["chenggong.mp3"])  \r
-        # self.chenggong_tone() #播放按键音\r
-        # 长对话:蓝牙按键同单按\r
-        # - 对话进行中:打断/续聊(复用 dialog_id)\r
-        # - 已停止/未 started:走新会话流程\r
-        try:\r
-            if self.VC_mode == 0:\r
-                if getattr(self, "AI_client_VC_isconnect", 0) == 1 and getattr(self, "_vc_session_started", 0) == 1:\r
-                    EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_RequestToSpeak})\r
-                    return\r
-        except Exception:\r
-            pass\r
-\r
-        self.Close()\r
-        \r
-        if self.VC_mode == 1: #长按对话模式 接收到蓝牙耳机按键事件\r
-            \r
-            tts_str = "请您按住圆形的按钮开始讲话,讲完松开即可 "\r
-            EventMesh.publish("play_tts",[tts_str,1])\r
-            return\r
-        # if dev_type == "7PIN":\r
-        #     self.chenggong_tone()\r
-        #self.aud.aud_tone_play(10,200)#播放按键音\r
-        self.AI_TTS_str = ""#"你好,有什么吩咐"\r
-        self.AI_ASR_str = ""\r
-        self.AI_ASR_str_tmp = ""\r
-        # if self.VC_state != VC_State_None:\r
-        #     EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) \r
-        #     EventMesh.publish("publish_AI_btnInfo",{"func":utime.sleep_ms,'data':(10)})\r
-        self._uplink_request_stop("BT_AI_key_press/compat2")\r
-        self._uplink_request_start("BT_AI_key_press/compat2")\r
-        \r
-        self.AI_VC_start() \r
-        \r
-        \r
-        # self.Close()\r
-        # self.AI_VC_start()\r
-        self.AI_key_long_press_flag = 0\r
-    def stop_AI_record(self, event=None, msg=None):#停止AI对话\r
-        if self.VC_satrt_flag == 1:\r
-            self.get_config_timer_count = 0\r
-            self.AI_TTS_str = ""#"你好,有什么吩咐"\r
-            self.AI_ASR_str = ""\r
-            self.AI_ASR_str_tmp = ""\r
-            self.AI_key_long_press_flag = 0\r
-            EventMesh.publish("publish_AI_btnInfo",{"func":self.Close})\r
-            self.request_uplink_stop("stop_AI_record")\r
-            count = 0\r
-            while True:\r
-                if count >= 20 or self.VC_satrt_flag == 0:\r
-                    break\r
-                count+=1\r
-                utime.sleep_ms(500)\r
-\r
-    def exit_voice_message_timer(self,*argc):\r
-        EventMesh.publish("exit_voice_message_mode")\r
-\r
-    def AI_key_release(self, event=None, msg=None):#按键弹起\r
-        try:\r
-            if "vc_config" in globals() and vc_config and vc_config.get_bool("KEY_LED_DEBUG", False):\r
-                try:\r
-                    led = EventMesh.publish("get_led_debug_state")\r
-                except Exception:\r
-                    led = None\r
-                try:\r
-                    t = getattr(self.fsm, "turn", None)\r
-                except Exception:\r
-                    t = None\r
-                print("[KEY] AI_key_release VC_mode={} {} turn={} led={}".format(\r
-                    getattr(self, "VC_mode", -1), self._vc_fsm_log_snip(), t, led))\r
-        except Exception:\r
-            pass\r
-        print("AI_key_release123")\r
-        self.key_state = 0\r
-        if self.trans_flag == 1:#AI翻译模式\r
-            EventMesh.publish("stop_voice_record") #关闭语音录音\r
-            return\r
-        if self.voice_flag == 1:\r
-            self.stop_voice_timer.stop()\r
-            self.voice_flag=0\r
-            if EventMesh.publish("GET_USB_STATE") == 0:\r
-                EventMesh.publish("set_led_state", {"ai_state": "idle"})\r
-            EventMesh.publish("stop_voice_record")#停止静默录音\r
-            tts_play_str = "留言已发送"\r
-            self.voice_flag=0\r
-            EventMesh.publish("play_tts",[tts_play_str,1])\r
-            return\r
-        if self.VC_mode == 1:\r
-            self._ai_key_release_handle_ptt()\r
-            return\r
-    def pwk_key_double_press(self,event,msg):#pwk双击\r
-        print("come 双击")\r
-        # 切换模式:必须打断当前对话/录音/播放,回到 idle,等待按键触发新一轮\r
-        try:\r
-            self.Close_flag = 1\r
-        except Exception:\r
-            pass\r
-        try:\r
-            EventMesh.publish("publish_AI_btnInfo", {"func": self.Close})\r
-        except Exception:\r
-            pass\r
-        if self.VC_mode == 0:\r
-            self.VC_mode = 1\r
-            tts_str = "已切换到单对话模式" \r
-            # 下一次“无 dialog_id 的提示音 TTS”归类为模式切换提示(单对话)\r
-            try:\r
-                self._pending_tts_kind = "mode_switch_ptt"\r
-                self._last_tts_kind = "mode_switch_ptt"\r
-            except Exception:\r
-                pass\r
-        else:\r
-            self.VC_mode = 0 \r
-            tts_str = "已切换到长对话模式" \r
-            # 下一次“无 dialog_id 的提示音 TTS”归类为模式切换提示(长对话)\r
-            try:\r
-                self._pending_tts_kind = "mode_switch_long"\r
-                self._last_tts_kind = "mode_switch_long"\r
-            except Exception:\r
-                pass\r
-        # 提示音播完即退出(Close_flag=1),保持 idle,等待按键\r
-        EventMesh.publish("play_tts",[tts_str,1])\r
-        EventMesh.publish("set_storage_config",["VC_mode",self.VC_mode])\r
-\r
-    def AI_key_long_press(self, event, msg):#按键长按\r
-        try:\r
-            if "vc_config" in globals() and vc_config and vc_config.get_bool("KEY_LED_DEBUG", False):\r
-                try:\r
-                    led = EventMesh.publish("get_led_debug_state")\r
-                except Exception:\r
-                    led = None\r
-                try:\r
-                    t = getattr(self.fsm, "turn", None)\r
-                except Exception:\r
-                    t = None\r
-                print("[KEY] AI_key_long_press VC_mode={} {} turn={} led={}".format(\r
-                    getattr(self, "VC_mode", -1), self._vc_fsm_log_snip(), t, led))\r
-        except Exception:\r
-            pass\r
-        print("AI_key_long_press")\r
-        self._ai_key_long_press_route(event, msg)\r
-\r
-    def pwk_key_release(self, event, msg):#电源键短按,关闭\r
-        try:\r
-            if "vc_config" in globals() and vc_config:\r
-                _m = bool(vc_config.get_bool("SINGLE_POWER_KEY_AI_MERGED", False))\r
-                if not _m:\r
-                    _ai = vc_config.get_attr("EXTINT_AI_GPIO", "GPIO22")\r
-                    _m = _ai is None or _ai == ""\r
-            else:\r
-                _m = False\r
-        except Exception:\r
-            _m = False\r
-        if _m:\r
-            self._handle_pwk_short_release(event, msg, merged_ai_tail=True)\r
-            return\r
-        self._handle_pwk_short_release(event, msg, merged_ai_tail=False)\r
-\r
-\r
-    def key_prompt(self):\r
-        EventMesh.publish("audio_montage_play",["key_prompt.mp3"])\r
-        #self.aud.play(1,1,'U:/static/key_prompt.mp3')#提示请您按住中间的按钮开始讲话,讲完松开即可\r
-    def VC_error(self, event, msg):#错误播报\r
-        self.websocket_error()\r
-        #EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error}) \r
-    def set_VC_record_state_flag(self,data):\r
-        \r
-        self.VC_record_state_flag = data\r
-        print("[VC] record_state_flag={}".format(self.VC_record_state_flag))\r
-    #开始录音,不缓存,直接发\r
-    def VC_Speech_Start_now(self):\r
-        # 新一轮开始录音:允许上行(上一轮可能已置 silence 闸门)\r
-        self._server_vad_silence = 0\r
-        self.VC_Speech_Start()\r
-        if self.VC_record_state_flag  == 1:\r
-            self.VC_record_state_flag =2\r
-        print("[VC] Speech_Start_now record_state={}".format(self.VC_record_state_flag))\r
-    #开始录音,先缓存到buf,等服务器响应再发\r
-    def VC_Speech_Start(self):\r
-        try:\r
-            \r
-            #self.VC_state =  VC_State_SpeechStart\r
-            print("[VC] Speech_Start record_state={} AI_ASR_flag={}".format(self.VC_record_state_flag,self.AI_ASR_flag))\r
-            if self.AI_ASR_flag  != 0:\r
-                return\r
-            \r
-            if self.VC_record_state_flag  == 0:\r
-                # 新一轮开始录音:允许上行(上一轮可能已置 silence 闸门)\r
-                self._server_vad_silence = 0\r
-                self.AI_ASR_str = ""\r
-                self.AI_ASR_str_tmp = ""\r
-                self._last_asr_result_text = ""\r
-                try:\r
-                    self._asr_completed_tone_done = 0\r
-                except Exception:\r
-                    pass\r
-                \r
-                self.VC_record_thread_close()#关闭录音线程\r
-                _thread.stack_size(self.Audio_buf_size+8*1024)\r
-                self.VC_record_thread_id = _thread.start_new_thread(self.VC_record_thread,())\r
-                _thread.stack_size(8*1024)\r
-                print("self.VC_record_thread_id:{}".format(self.VC_record_thread_id))\r
-                try:\r
-                    \r
-                    if self.VC_record_TYPE == VC_record_TYPE_AMR :\r
-                        try :\r
-                            self.record.stream_start(audio.Record.AMRNB, self.VC_record_samplerate, 0)\r
-                        except Exception as e:\r
-                                usys.print_exception(e)\r
-                    elif self.VC_record_TYPE == VC_record_TYPE_PCM:\r
-                        print("record_pcm.stream_read_start()!!!!!!!!!!!!")\r
-                        self.record.stream_start(audio.Record.WAV, self.VC_record_samplerate, 0)\r
-                        # if self.record_pcm != None:\r
-                        #     try:\r
-                        #         self.record_pcm.stream_stop()\r
-                        #         self.record_pcm.close()\r
-                        #         self.record_pcm = None\r
-                        #     except Exception as e:\r
-                        #         usys.print_exception(e)\r
-                        # if self.record_pcm == None:        \r
-                        #     ret = self.record_pcm = audio.Audio.PCM(0,1,16000,0,1,2)\r
-                        #     print("audio.Audio.PCM:ret:{}".format(ret))\r
-                        #     ret = self.record_pcm.stream_read_start()\r
-                        #     print("self.record_pcm.stream_read_start:ret:{}".format(ret))\r
-                except Exception as e:\r
-                    usys.print_exception(e) \r
-                \r
-                self.VC_record_state_flag = 1\r
-                # 录音硬件已起:协议侧应对应 audio.create(PTT)或长对话直传 PCM\r
-                try:\r
-                    print("[VC][proto] record_start sr={} pcm_type={} VC_mode={} session_started={} "\r
-                          "ptt_audio_created={} thread_id={}".format(\r
-                              self.VC_record_samplerate, self.VC_record_TYPE, self.VC_mode,\r
-                              getattr(self, "_vc_session_started", -1),\r
-                              getattr(self, "_ptt_audio_created", -1),\r
-                              self.VC_record_thread_id))\r
-                except Exception:\r
-                    pass\r
-                # 录音开始:聆听灯(PTT 蓝快闪 / 长对话蓝慢闪)\r
-                try:\r
-                    self._vc_publish_led("listening", "VC_Speech_Start")\r
-                except Exception:\r
-                    pass\r
-                try:\r
-                    if getattr(self, "VC_mode", -1) == 1:\r
-                        self._ptt_on_record_hardware_started()\r
-                except Exception:\r
-                    pass\r
-                \r
-        except Exception as e:\r
-            self.websocket_error()\r
-            self.VC_record_state_flag = 1\r
-            self.VC_Speech_End()#关闭录音\r
-            usys.print_exception(e)   \r
-    #停止录音\r
-    def VC_Speech_End(self):\r
-        try:\r
-            self._uplink_mark_end("VC_Speech_End")\r
-            self.AI_ASR_flag = 0\r
-            self.AI_ASR_str_tmp = ""\r
-            # 勿在此清空 AI_ASR_str:vad.silence 停麦常早于 asr.completed,清空会导致 check_text:len:0 连播 think 音\r
-            #EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_asr})\r
-              \r
-            if self.VC_record_state_flag == 0:\r
-                return\r
-            #self.AI_client_asr_StopTranscription_requset()#请求停止\r
-            if self.VC_record_TYPE == VC_record_TYPE_AMR:\r
-                self.record.stream_stop()\r
-                self.record.stop()#关闭录音\r
-                Record_read_buf = bytearray(self.Audio_buf_size)\r
-                for i in range(10):\r
-                    Record_len = self.record.stream_read(Record_read_buf, self.Audio_buf_size)\r
-                    # if VC_AUDIO_FRAME_LOG:\r
-                    #     print("Record_len:!!!!!!{}".format(Record_len))\r
-                    if Record_len <= 0:\r
-                        break\r
-            elif self.VC_record_TYPE == VC_record_TYPE_PCM:\r
-                self.record.stream_stop()\r
-                self.record.stop()#关闭录音\r
-                Record_read_buf = bytearray(self.Audio_buf_size)\r
-                for i in range(10):\r
-                    Record_len = self.record.stream_read(Record_read_buf, self.Audio_buf_size)\r
-                    # if VC_AUDIO_FRAME_LOG:\r
-                    #     print("Record_len:!!!!!!{}".format(Record_len))\r
-                    if Record_len <= 0:\r
-                        break\r
-                # if self.record_pcm != None:\r
-                #     try:\r
-                #         self.record_pcm.stream_stop()\r
-                #         self.record_pcm.close()\r
-                #         self.record_pcm = None\r
-                #     except Exception as e:\r
-                #         usys.print_exception(e)\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-        self.VC_record_thread_close()#关闭录音线程\r
-        if self.AI_record_tmp_queue.empty() != True:\r
-            self.AI_record_tmp_queue.clear()\r
-        \r
-        self.VC_record_state_flag = 0\r
-        # PTT 一轮录音结束:清长按语义,避免后续短按松手误走 end_speech 而不播提示音\r
-        try:\r
-            if getattr(self, "VC_mode", -1) == 1:\r
-                self.AI_key_long_press_flag = 0\r
-        except Exception:\r
-            pass\r
-        # 录音停止:单对话进入思考态(等服务器),勿继续蓝闪聆听\r
-        try:\r
-            if getattr(self, "VC_mode", -1) == 1:\r
-                self._ptt_await_server = 1\r
-                self._vc_publish_led("thinking", "VC_Speech_End/ptt")\r
-            else:\r
-                self._ptt_await_server = 0\r
-                self._vc_publish_led("idle", "VC_Speech_End")\r
-        except Exception:\r
-            pass\r
-        print("[VC] Speech_End record_state={}".format(self.VC_record_state_flag))\r
-    #关闭录音线程   \r
-    def VC_record_thread_close(self): \r
-        if self.VC_record_thread_id != 0:\r
-            count =50\r
-            while True:\r
-                count -= 1\r
-                if self.VC_record_thread_lock == 1:\r
-                    self.VC_record_thread_lock = 2\r
-                if self.VC_record_thread_lock == 0 or count <=0:\r
-                    break\r
-                else:\r
-                    print("VC_record_thread_thread going:{}...".format(count))\r
-                    utime.sleep_ms(1)\r
-            self.VC_record_thread_lock = 0\r
-            print("VC_record_thread_thread_close")\r
-            if self.VC_record_thread_id != 0:\r
-                try:\r
-                    _thread.stop_thread(self.VC_record_thread_id)\r
-                except Exception as e:\r
-                    usys.print_exception(e)\r
-        self.VC_record_thread_id = 0\r
-    #获取录音数据\r
-\r
-    def VC_record_thread(self):\r
-        ##return\r
-        print("[VC] record_thread start")\r
-        count = 0\r
-        RequestToSpeak_flag = 0\r
-        # try:\r
-        #     uos.remove('/usr/test.pcm')\r
-        # except Exception as e:\r
-        #     usys.print_exception(e)\r
-        #record_data_queue = Queue(100)\r
-        #fd = open('/usr/test.pcm','w')\r
-        # if self.VC_record_TYPE == 1:\r
-        #     h_data= [0x23,0x21 ,0x41 ,0x4D ,0x52 ,0x0A ]\r
-        #     h_data_b = bytes(h_data)\r
-        #     self.AI_client_VC_send(h_data_b)\r
-        AI_record_tmp_queue_len = 0\r
-        vad_buf = bytearray()\r
-        VAD_data_count = 0\r
-        vad_state = 0\r
-        self.VAD_count = 0\r
-        while True:\r
-            try:\r
-                if self.VC_record_thread_lock == 2:\r
-                    break\r
-                self.VC_record_thread_lock = 1\r
-                # 死循环接收数据\r
-                #if self.VC_state ==  VC_State_SpeechStart:\r
-                Record_read_buf = bytearray(self.Audio_buf_size)\r
-                \r
-                Record_len = 0\r
-                if self.VC_record_TYPE == VC_record_TYPE_PCM:#pcm\r
-                    Record_len = self.record.stream_read(Record_read_buf, self.Audio_buf_size)\r
-                    #utime.sleep_ms(60)\r
-                    \r
-                else :\r
-                    \r
-                    Record_len = self.record.stream_read(Record_read_buf, self.Audio_buf_size)\r
-                    utime.sleep_ms(600)\r
-\r
-                #Record_len = 1024*2\r
-                #print("VC_record_thread:{}".format(Record_len))\r
-                if Record_len > 0:\r
-                    read_buf = bytes(Record_read_buf[:Record_len])\r
-                    # if VC_AUDIO_FRAME_LOG:\r
-                    #     print("Record_len:{}".format(Record_len))\r
-                    if self.record_data_save_flag == 1:\r
-                        if self.record_fd == None:\r
-                            try:\r
-                                uos.remove('/usr/record_data.pcm')\r
-                            except Exception as e:\r
-                                pass\r
-                            # 二进制写入,避免文本模式导致数据异常\r
-                            self.record_fd = open('/usr/record_data.pcm', 'wb')\r
-                            self.record_fd_len = 0\r
-                        if self.record_fd != None:\r
-                            try:\r
-                                self.record_fd.write(read_buf)\r
-                                print("write record:{}".format(len(read_buf)))\r
-                                self.record_fd_len = self.record_fd_len+len(read_buf)\r
-                                if self.record_fd_len >=500*1024:\r
-                                    EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) \r
-                            except Exception as e:\r
-                                usys.print_exception(e)\r
-                                EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) \r
-                            # count+=Record_len\r
-                            # if count >150*1024:\r
-                            #     self.record_fd.close()\r
-                                #self.record_fd = None\r
-                    #VAD检测\r
-                    if self.is_VAD == 1:\r
-                        vad_state = 0\r
-                        tmp_c = 1000\r
-                        while True:\r
-                            tmp_c -=1\r
-                            if tmp_c <=0:\r
-                                break\r
-                            #vad_buf_len = len(vad_buf)\r
-                            if VAD_data_count == 0:#没数据的时候\r
-                                vad_buf = Record_read_buf[:self.VAD_data_len]\r
-                                VAD_data_count = self.VAD_data_len\r
-                                self.vad.custom_vad_reset()#重置vad\r
-                            else:#有数据的时候\r
-                                if Record_len - VAD_data_count < self.VAD_data_len :#剩余长度不够一次填充,先保留,但不填算法\r
-                                    vad_buf = Record_read_buf[VAD_data_count:Record_len]\r
-                                    VAD_data_count = 0\r
-                                    break\r
-                                else:#剩余长度够填一次数据\r
-                                    vad_buf = Record_read_buf[VAD_data_count:VAD_data_count+self.VAD_data_len]\r
-                                    VAD_data_count += len(vad_buf)\r
-                            #print("vad_buf_len:{},{}".format(len(vad_buf),VAD_data_count))\r
-                            \r
-                            if len(vad_buf) >= self.VAD_data_len:\r
-                                vad_state = self.vad.custom_vad_process(vad_buf,len(vad_buf))#给vad数据\r
-                                #print("vad_state:{}".format(vad_state))\r
-                                \r
-                            if VAD_data_count >= Record_len:\r
-                                VAD_data_count = 0\r
-                                break\r
-                            if vad_state == 1:\r
-                                print("检测到VAD=1!!!!!!!!!!!!!")\r
-   \r
-                                # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_RequestToSpeak})#打断\r
-                                # RequestToSpeak_flag = 1\r
-                                \r
-                                self.VAD_count+=1\r
-                                if self.VAD_count >= 20: \r
-                                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_RequestToSpeak})#打断\r
-                                        \r
-                            else:\r
-                                if self.VAD_count != 0:\r
-                                    EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Play_Pause,"data":0})#恢复音频播放\r
-                                self.VAD_count  = 0\r
-                    if self._vc_tts_pipeline_busy():\r
-                        pass\r
-                        if vad_state == 1:\r
-                            self.AI_record_tmp_queue.put(read_buf)#入缓存队列\r
-                            AI_record_tmp_queue_len += len(read_buf)\r
-                            \r
-                       \r
-                    else:\r
-                        VAD_data_count = 0\r
-                        if self.VC_record_state_flag <=1 :#没发送前先存起来\r
-                            self.AI_record_tmp_queue.put(read_buf)\r
-                            AI_record_tmp_queue_len += len(read_buf)\r
-                        else:\r
-                            AI_record_tmp_queue_len = 0\r
-                            while True:\r
-                                if self.AI_record_tmp_queue.empty() == True:\r
-                                    break\r
-                                else:\r
-                                    pass\r
-                                    # 协议门控:会话未 started 前禁止上行音频(否则 session not initialized)\r
-                                    if VC_PROTO_GUARDS and self._vc_session_started != 1 and self.record_data_save_flag == 0:\r
-                                        _ = self.AI_record_tmp_queue.get()\r
-                                        AI_record_tmp_queue_len = max(0, AI_record_tmp_queue_len - len(_))\r
-                                        continue\r
-                                    if not (self._server_vad_silence == 1 and self.record_data_save_flag == 0):\r
-                                        self.AI_client_VC_send(self.AI_record_tmp_queue.get())  #发送之前存的数据\r
-                                    else:\r
-                                        self._uplink_mark_end("silence_gate(drop_queue)")\r
-                                        _ = self.AI_record_tmp_queue.get()\r
-                            # 当前帧发送前协议门控(可用 VC_PROTO_GUARDS 临时关闭):\r
-                            # - session started 前禁止发帧\r
-                            # - Push-To-Talk 在 audio.create 前禁止发帧\r
-                            if VC_PROTO_GUARDS and (\r
-                                (self._vc_session_started != 1 and self.record_data_save_flag == 0) or\r
-                                (self.VC_mode == 1 and self._ptt_audio_created != 1 and self.record_data_save_flag == 0)\r
-                            ):\r
-                                # 未 ready:转入缓存,等待 started 再发(并做容量控制)\r
-                                try:\r
-                                    self.AI_record_tmp_queue.put(read_buf)\r
-                                    AI_record_tmp_queue_len += len(read_buf)\r
-                                    while AI_record_tmp_queue_len > 128*1024 and self.AI_record_tmp_queue.empty() != True:\r
-                                        d = self.AI_record_tmp_queue.get()\r
-                                        AI_record_tmp_queue_len = max(0, AI_record_tmp_queue_len - len(d))\r
-                                except Exception:\r
-                                    pass\r
-                            else:\r
-                                if not (self._server_vad_silence == 1 and self.record_data_save_flag == 0):\r
-                                    self.AI_client_VC_send(read_buf)\r
-                                else:\r
-                                    self._uplink_mark_end("silence_gate(drop_buf)")\r
-                    \r
-                    \r
-\r
-                    \r
-                    \r
-\r
-                    #print("VC_record_thread:{}".format(len(read_buf)))\r
-                    #count+=len(read_buf)\r
-                # if count>=500*1024:#结束\r
-                #     self.AI_client_VC_end_speech()\r
-                #     break\r
-                # count+=1\r
-                # if count %10 == 0:\r
-                #     print("VC_record_thread on line {} asr recv :{} send:{}".format(self.VC_record_state_flag,_thread.threadIsRunning(self.AI_client_asr_recv_id),_thread.threadIsRunning(self.AI_VC_send_data_queue_thread_id)))\r
-                \r
-                # if self.VC_state >=  VC_State_SpeechEnd:\r
-                #     break\r
-                \r
-            except Exception as e:\r
-                    usys.print_exception(e)\r
-                    #break\r
-            self.VC_record_thread_lock = 0\r
-            if self.record_data_save_flag == 1:\r
-                utime.sleep_ms(1)\r
-            else:\r
-                utime.sleep_ms(100)\r
-        #fd.close()\r
-        print("VC_record_thread:exit")\r
-        self.VC_record_thread_id = 0#线程退出\r
-        self.VC_record_thread_lock = 0\r
-###################################################### VC ###################################################################################\r
-    def websocket_connect_VC(self):\r
-        print("websocket_connect_VC:{} e:{}!!!!!!!!!!!!!!!!!!!!!".format(self.AI_client_VC_isconnect,self.AI_client_VC_debug))\r
-        if self.AI_client_VC_isconnect == 1:\r
-            return\r
-        # 创建VC客户端\r
-        try:\r
-            signal = net.csqQueryPoll()\r
-            if signal == -1 or sim.getStatus() != 1:#无蜂窝/无卡等:弱网提示\r
-                self.websocket_error(play_weak_signal_tone=True)\r
-                return\r
-            \r
-            req_str = ""\r
-            ack = ""\r
-            if self.is_VC_ACK == 1:\r
-                ack = "ack=1"\r
-            lang = "lang=zh-cn"#普通话测试环境\r
-            try:\r
-                if "lang" in self.VC_TTS_voice_type[self.VC_TTS_voice_key]:\r
-                    lang = self.VC_TTS_voice_type[self.VC_TTS_voice_key]["lang"]\r
-                else:\r
-                    print("not lang!!!!!!!!!!!!!!!!!!!!!!")\r
-            except Exception as e:\r
-                usys.print_exception(e)\r
-            \r
-            #女,男,童,粤,川\r
-            self.bot_name = "柒先生"\r
-            try:\r
-                if "bot_name" in self.VC_TTS_voice_type[self.VC_TTS_voice_key]:\r
-                    self.bot_name = self.VC_TTS_voice_type[self.VC_TTS_voice_key]["bot_name"]\r
-                else:\r
-                    print("not bot_name!!!!!!!!!!!!!!!!!!!!!!")\r
-            except Exception as e:\r
-                usys.print_exception(e)\r
-            \r
-            # Headers = {'X-NLS-Token':'0c8b8f05a46f423096be64a9c4c46310'}\r
-            # self.AI_client_VC = uwebsocket.Client.connect('wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1',headers=Headers, debug=False)\r
-            token = EventMesh.publish("get_dev_token")\r
-            print("token:{}".format(token))\r
-            \r
-            req_str = "vc/chat?is_auth={}".format(self.is_auth)\r
-            try:\r
-                if "vc_config" in globals() and vc_config:\r
-                    base = vc_config.get_attr("VC_WS_PATH", "vc/chat") or "vc/chat"\r
-                    req_str = "{}?is_auth={}".format(base, self.is_auth)\r
-            except Exception:\r
-                pass\r
-            if(token):\r
-                url = 'ws://{}/{}&dt={}'.format(self.server_ip,req_str,token)\r
-            else:\r
-                url = 'ws://{}/{}'.format(self.server_ip,req_str)\r
-            #url = 'ws://115.190.94.208:22080/vc/coze?dt={}'.format(token)\r
-            \r
-            if VC_WS_IO_LOG:\r
-                print("[VC] ws url {}".format(url))\r
-            self.AI_client_VC = uwebsocket.Client.connect(url, debug=False)\r
-            #self.AI_WS_connect_Queue.put(self.AI_client_VC)\r
-\r
-       \r
-            print("[VC] websocket_connect ok")\r
-        except Exception as e: #网络连接异常(已通过 CSQ/SIM,多为 URL/握手/服务端问题,勿播「信号弱」)\r
-            usys.print_exception(e)\r
-            print("websocket_connect_VC error!!!\r\n")\r
-            try:\r
-                print("[VC] ws connect exc: {}".format(e))\r
-            except Exception:\r
-                pass\r
-            str_error = str(e)\r
-            if str_error.find("[Errno 22] EINVAL")!= -1: \r
-                print("[VC] websocket EINVAL: {}".format(str_error))\r
-                self.websocket_error(play_weak_signal_tone=False)\r
-                EventMesh.publish("set_storage_config",["powerRestart",1])#调试临时关闭\r
-                utime.sleep_ms(1000)\r
-                self.aud.stopPlayStream()\r
-                self.aud.stopAll()\r
-                Power.powerRestart()\r
-                \r
-            self.websocket_error(play_weak_signal_tone=False)\r
-            return\r
-        #print("AI_client_VC:{}".format(self.AI_client_VC))\r
-        if self.AI_client_VC == None:\r
-            EventMesh.publish("update_dev_token")#获取token\r
-            self.websocket_error(play_weak_signal_tone=False)\r
-            return\r
-        self.AI_client_VC_debug+=1\r
-        utime.sleep_ms(10)\r
-        self.AI_client_VC.settimeout(5)\r
-        #self.AI_VC_send_data_queue_thread_close()#关闭发送线程\r
-        if self.AI_VC_send_data_queue_thread_id == 0:    \r
-            _thread.stack_size(self.Audio_buf_size+8*1024)\r
-            self.AI_VC_send_data_queue_thread_id = _thread.start_new_thread(self.AI_VC_send_data_queue_thread, ())\r
-            _thread.stack_size(8*1024)\r
-            print("AI_VC_start :AI_VC_send_data_queue_thread_id :{} ".format(self.AI_VC_send_data_queue_thread_id))\r
-        self.AI_client_VC_recv_thread_close()#关闭读线程\r
-        # 线程接收数据\r
-        _thread.stack_size(self.Audio_buf_size+8*1024)\r
-        self.AI_client_VC_recv_id = _thread.start_new_thread(self.AI_client_VC_recv, (self.AI_client_VC,))\r
-        _thread.stack_size(8*1024)\r
-        self.AI_client_VC_isconnect = 1\r
-        print("websocket_connect_VC end")\r
-\r
-    def websocket_error(self, TTS_str=None, play_weak_signal_tone=False):\r
-        """play_weak_signal_tone=True 时播 xinhaoruo1(弱网/无卡类);连接/协议失败请传 False,避免误报「信号弱」。"""\r
-        try:\r
-            if getattr(self, "VC_mode", 0) != 1:\r
-                print("[VC][long_idle] websocket_error enter VC_mode={} TTS_str={} weak_hint={} -> Close then uplink_stop(async)".format(\r
-                    getattr(self, "VC_mode", -1), TTS_str, play_weak_signal_tone))\r
-        except Exception:\r
-            pass\r
-        print("websocket_error1!!!!")\r
-        utime.sleep_ms(800)\r
-        self.Close()\r
-        print("websocket_error1.1!!!!")\r
-        print("websocket_error!!!!")\r
-        print("websocket_error!!!!")\r
-        self.VC_Thinking_timerout = 0\r
-        self.VC_satrt_flag = 0\r
-        self.AI_TTS_str = ""\r
-        self.AI_ASR_str = ""\r
-        self.AI_ASR_str_tmp = ""\r
-        self.AI_ASR_flag  = 0\r
-        # 可能由收包线程/其它线程触发:统一切回主线程执行 stop,避免“异常→重试→再异常”的循环\r
-        try:\r
-            if getattr(self, "VC_mode", 0) != 1:\r
-                print("[VC][long_idle] websocket_error schedule uplink_stop reason=websocket_error + set_led_idle")\r
-        except Exception:\r
-            pass\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self._uplink_request_stop, "data":"websocket_error"})\r
-        EventMesh.publish("set_led_state", {"ai_state": "idle"})\r
-        if TTS_str == None:\r
-            self.websocket_error_count+=1\r
-            # if self.websocket_error_count <=3:\r
-            #     EventMesh.publish("audio_montage_play",["xinhaoruo1.mp3"])\r
-            if play_weak_signal_tone and self.websocket_error_count < 10:\r
-                EventMesh.publish("audio_montage_play", ["xinhaoruo1.mp3"])\r
-            elif self.websocket_error_count >=10:\r
-                # EventMesh.publish("paly_iccid_imei")    \r
-                EventMesh.publish("audio_montage_play", ["xinhaoruo1.mp3"])\r
-            # else:\r
-            #     EventMesh.publish("audio_montage_play",["R_SIM_FAILE.mp3"])\r
-            #self.aud.play(1,1,'U:/static/R_SIM_FAILE.mp3')\r
-            if self.websocket_error_count <3 :\r
-                utime.sleep_ms(3000)\r
-                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start})\r
-        else:\r
-            print("websocket_error:tts:{}".format(TTS_str))\r
-            self.AI_TTS_str = TTS_str\r
-            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start})\r
-            self.Close_flag = 1\r
-        print("websocket_error2{}".format(self.websocket_error_count))\r
-       \r
-        \r
-        print("websocket_error4")\r
-        \r
-\r
-    def websocket_deconnect_VC(self):\r
-        #print("websocket_deconnect_VC {}!!!!!!!".format(self.AI_client_VC_isconnect))\r
-        print("websocket_deconnect_VC!!!!!!!")\r
-        self._vc_session_started = 0\r
-        # 断链/模式切换时必须重置“上行录音状态”,否则会被 _uplink_request_start 的幂等门控挡住,\r
-        # 表现为:切回长对话后无法自动开麦,按键 cancel/canceled 也无法恢复。\r
-        try:\r
-            self.VC_record_state_flag = 0\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self._server_vad_silence = 0\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self._ptt_audio_created = 0\r
-        except Exception:\r
-            pass\r
-        try:\r
-            # 若录音线程/录音设备仍在占用,先停掉\r
-            self.VC_Speech_End()\r
-        except Exception:\r
-            try:\r
-                self.VC_record_thread_close()\r
-            except Exception:\r
-                pass\r
-        # 断链必须同步重置 FSM,否则 await_played/turn 残留会导致动态切换后“无法自动开麦/按键无效”\r
-        try:\r
-            self.fsm.session = VCSessionState.Disconnected\r
-            self.fsm.turn = VCTurnState.Idle\r
-            self.fsm.await_played = False\r
-            self.fsm.last_tts_completed_ms = 0\r
-            self.fsm.last_llm_ms = 0\r
-            self.fsm.reset_rx_phases()\r
-        except Exception:\r
-            pass\r
-        self.AI_client_VC_recv_thread_close()#关闭读线程\r
-        self.AI_client_VC_recv_id = 0\r
-        if self.AI_client_VC_isconnect == 1 or self.AI_client_VC!=None:\r
-            try:\r
-                self.AI_client_VC_isconnect = 0\r
-                count = 500\r
-                while True:#等待发完数据\r
-                    count-=1\r
-                    if count <=0 or self.AI_VC_send_data_queue_thread_lock == 0:\r
-                        break\r
-                    utime.sleep_ms(1)\r
-                \r
-                try:\r
-                    self.AI_client_VC.close()\r
-                    self.AI_client_VC_debug -=1\r
-                    #self.AI_client_VC=None\r
-                except Exception as e:\r
-                    usys.print_exception(e)\r
-                    try :\r
-                        self.AI_client_VC.close()\r
-                        self.AI_client_VC_debug -=1\r
-                        #self.AI_client_VC=None\r
-                    except Exception as e:\r
-                        usys.print_exception(e)\r
-                try :\r
-                    self.AI_client_VC._close()\r
-                except Exception as e:\r
-                    usys.print_exception(e)\r
-                self.AI_client_VC = None\r
-            except Exception as e:\r
-                usys.print_exception(e)\r
-        print("websocket_deconnect_VC {}!!!!!!!".format(self.AI_client_VC_isconnect))\r
-    #websocket发送ping\r
-    def AI_client_VC_ping(self):\r
-        pass\r
-        if self.AI_client_VC_isconnect == 1:\r
-            #self.AI_client_VC.write_frame(0x09,b'ping ping')\r
-            self.AI_client_VC_send("ping")\r
-            if VC_HEARTBEAT_LOG:\r
-                print("VC: ping ping")\r
-\r
-    #websocket发送数据    \r
-    def AI_client_VC_send(self,data_str):\r
-        # if isinstance(data_str, str):#是字符串\r
-        #     print("AI_client_VC_send:{}".format(data_str))\r
-        #     pass\r
-        # else:\r
-        #     print("AI_client_VC_send:len{}".format(len(data_str)))\r
-        # 统计“语音上行”真实开始/结束:这里只统计二进制音频(非 ping/字符串)\r
-        try:\r
-            if not isinstance(data_str, str):\r
-                l = len(data_str)\r
-                if l > 0:\r
-                    self._uplink_mark_start(l)\r
-                    self._uplink_bytes += l\r
-        except Exception:\r
-            pass\r
-        self.AI_VC_send_data_queue.put([AI_client_VC_num,data_str]) \r
-     #关闭数据发送线程\r
-    def AI_VC_send_data_queue_thread_close(self):\r
-        if self.AI_VC_send_data_queue_thread_id != 0:\r
-            # 先清空待发队列,避免关闭过程持续堆积/刷屏\r
-            if self.AI_VC_send_data_queue.empty() != True:\r
-                self.AI_VC_send_data_queue.clear()\r
-            # 通知发送线程退出;若发送线程正阻塞在底层 send,则只能等超时或强制 stop_thread\r
-            self.AI_VC_send_data_queue_thread_lock = 2\r
-            count = 500\r
-            while True:\r
-                count -= 1\r
-                if self.AI_VC_send_data_queue_thread_lock == 0 or count <=0:\r
-                    break\r
-                else:\r
-                    # 该打印只是“等待线程停下来”的调试心跳,默认关闭以免影响系统实时性\r
-                    if VC_HEARTBEAT_LOG and (count % 50 == 0 or count > 490):\r
-                        print("AI_VC_send_data_queue_thread going:{}...".format(count))\r
-                    utime.sleep_ms(5)\r
-            self.AI_VC_send_data_queue_thread_lock = 0\r
-            if VC_HEARTBEAT_LOG:\r
-                print("AI_VC_send_data_queue_thread_close")\r
-            if self.AI_VC_send_data_queue_thread_id != 0:\r
-                try:\r
-                    # 线程可能已自然退出/被复用,stop_thread 会抛 ValueError\r
-                    try:\r
-                        running = _thread.threadIsRunning(self.AI_VC_send_data_queue_thread_id)\r
-                    except Exception:\r
-                        running = True\r
-                    if running:\r
-                        _thread.stop_thread(self.AI_VC_send_data_queue_thread_id)\r
-                except Exception as e:\r
-                    usys.print_exception(e)\r
-        self.AI_VC_send_data_queue_thread_id = 0\r
-    #发送数据队列\r
-    def AI_VC_send_data_queue_thread(self):\r
-        print("AI_VC_send_data_queue_thread:start")\r
-        count_t = 0\r
-        while True:\r
-            if self.AI_VC_send_data_queue_thread_lock == 2:\r
-                print("AI_VC_send_data_queue_thread,该退出了")\r
-                break  \r
-            if self.AI_VC_send_data_queue.empty() :#不能去掉,去掉会卡死\r
-                self.AI_VC_send_data_queue_thread_lock = 0  \r
-                count_t+=1\r
-                # if count_t%200 == 0:\r
-                #     print("self.AI_VC_send_data_queue.empty")\r
-                utime.sleep_ms(1)\r
-            else:\r
-                #data[0]是websocket连接对象编号,data[1]是数据\r
-                #print("AI_VC_send_data_queue_thread:get")\r
-                \r
-                self.AI_VC_send_data_queue_thread_lock = 1\r
-                data = self.AI_VC_send_data_queue.get()\r
-                \r
-                \r
-                #print("AI_VC_send_data_queue_thread:num{} len{}".format(data[0],len(data[1])))\r
-                if data != None:\r
-                    try:\r
-                        #data["func"](data["data"])\r
-                        # 发送数据\r
-                        #bytes_data = bytes(data[1])\r
-                        #data[0].send(data[1])\r
-                        #print("AI_VC_send_data_queue_thread:len:{}".format(len(data[1])))\r
-                        #print("AI_VC_send_data_queue_thread on line asr recv :{} vc recv:{}".format(_thread.threadIsRunning(self.AI_client_asr_recv_id),_thread.threadIsRunning(self.AI_client_VC_recv_id)))\r
-                        if data[0] == AI_client_VC_num:\r
-                            \r
-                            if isinstance(data[1], str):#是字符串\r
-                                self.VC_pingping_timerout = 0\r
-                                s = data[1]\r
-                                if s is None:\r
-                                    s = ""\r
-                                # 保活 ping 不落 WS IO 日志(避免周期性刷屏)\r
-                                if VC_WS_IO_LOG and s != "ping":\r
-                                    try:\r
-                                        if len(s) > 240:\r
-                                            preview = s[:160] + " ... " + s[-60:]\r
-                                        else:\r
-                                            preview = s\r
-                                        print("[VC] WS send_txt len={} txt={}".format(len(s), preview))\r
-                                    except Exception:\r
-                                        try:\r
-                                            print("[VC] WS send_txt {}".format(data[1]))\r
-                                        except Exception:\r
-                                            pass\r
-                                elif not VC_WS_IO_LOG:\r
-                                    if s == "ping":\r
-                                        pass\r
-                                    else:\r
-                                        try:\r
-                                            ev, tid = _vc_out_json_event_meta(s)\r
-                                            if ev:\r
-                                                print("[VC][proto] tx event={} trace_id={} len={} ws_conn={}".format(\r
-                                                    ev, tid or "-", len(s), self.AI_client_VC_isconnect))\r
-                                            else:\r
-                                                print("[VC][proto] tx unknown_json len={} ws_conn={}".format(\r
-                                                    len(s), self.AI_client_VC_isconnect))\r
-                                        except Exception:\r
-                                            print("[VC][proto] tx parse_fail len={}".format(len(s)))\r
-                            else:\r
-                                pass\r
-                                # if VC_AUDIO_FRAME_LOG:\r
-                                #     print("VC: send_data len:{}".format(len(data[1])))\r
-                            # print("self.AI_client_VC = {} , self.AI_client_VC_isconnect = {}".format(self.AI_client_VC,self.AI_client_VC_isconnect))    \r
-                            if self.AI_client_VC != None and self.AI_client_VC_isconnect == 1:\r
-                                self.AI_client_VC.send(data[1])\r
-                            else:\r
-                                try:\r
-                                    if isinstance(data[1], str) and data[1] != "ping":\r
-                                        _ev, _tid = _vc_out_json_event_meta(data[1])\r
-                                        print("VC VC: send_data:ERROR len={} event={} trace_id={}".format(\r
-                                            len(data[1]), _ev or "?", _tid or "-"))\r
-                                    else:\r
-                                        print("VC VC: send_data:ERROR:{}".format(len(data[1])))\r
-                                except Exception:\r
-                                    print("VC VC: send_data:ERROR:{}".format(len(data[1])))\r
-                            #print("VC: send_data end")\r
-    \r
-                        #print("VC: send_data end")\r
-                    except Exception as e:\r
-                        print("DeviceInfo_queue_thread:ERROR:{}".format(e))\r
-                        usys.print_exception(e)\r
-                        if data[0] == AI_client_VC_num:\r
-                            #self.websocket_error()\r
-                            pass\r
-\r
-                            \r
-                            try:\r
-                                if self.AI_client_VC_isconnect != 0:\r
-                                    print("VC发送数据错误,退出")\r
-                                    EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":"VC发送出错了,请重试"}) \r
-                                    self.ud_error(["VC",self.VC_taskid])\r
-                            except Exception as e: #网络连接异常\r
-                                usys.print_exception(e)\r
-\r
-                    utime.sleep_ms(1)\r
-        self.AI_VC_send_data_queue_thread_lock = 0              \r
-    #VC关闭读线程\r
-    def AI_client_VC_recv_thread_close(self):\r
-        if self.AI_client_VC_recv_id != 0:\r
-            count =500\r
-            while True:\r
-                count -= 1\r
-                if self.AI_client_VC_recv_lock == 1:\r
-                    self.AI_client_VC_recv_lock = 2\r
-                if self.AI_client_VC_recv_lock == 0 or count <=0:\r
-                    break\r
-                else:\r
-                    print("AI_client_VC_recv_thread going:{}...".format(count))\r
-                    utime.sleep_ms(1)\r
-            self.AI_client_VC_recv_lock = 0\r
-            print("AI_client_VC_recv_thread_close")\r
-            if self.AI_client_VC_recv_id!= 0:\r
-                try:\r
-                    _thread.stop_thread(self.AI_client_VC_recv_id)\r
-                except Exception as e:\r
-                    usys.print_exception(e)\r
-        self.AI_client_VC_recv_id = 0\r
-    #Ai VC websocket返回数据       \r
-    def AI_client_VC_recv(self,cli):\r
-        count = 0\r
-        asr_led_flag = 0\r
-        tts_led_flag = 0\r
-        while True:\r
-            try:\r
-                # 死循环接收数据\r
-                self.AI_client_VC_recv_lock = 0\r
-\r
-                count+=1\r
-                if self._vc_tts_pipeline_busy() and (self.tts_in_data_len - self.tts_out_data_len > 64*1024 or count>5):\r
-                    count = 0\r
-                    utime.sleep_ms(1)\r
-                if not self._vc_tts_pipeline_busy():\r
-                    utime.sleep_ms(1)\r
-                recv_data = cli.recv()\r
-                if self.AI_client_VC_recv_lock == 2:\r
-                    print("AI_client_VC_recv 该退出了")\r
-                    break\r
-                self.AI_client_VC_recv_lock = 1\r
-                if self._vc_rx_resets_long_idle_timer(recv_data):\r
-                    self.close_timerout = 0\r
-                if recv_data == "websocket close":\r
-                    # 服务器关闭连接\r
-                    print("VC 服务器关闭连接!!!!!!!!!!!!!!!!!!!!!!!!!!!")\r
-                    #self.AI_client_VC_isconnect = 0\r
-                    #self.websocket_deconnect_VC()\r
-                    #EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_asr}) \r
-                    # #EventMesh.publish("publish_AI_btnInfo",{"func":utime.sleep_ms,'data':(1500)})\r
-                    #EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_connect_asr}) \r
-                    EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":"VC连接断开了,请重试"}) \r
-                    self.ud_error(["VC",self.VC_taskid])\r
-                    break\r
-                elif recv_data == "":#客户端关闭连接\r
-                    print("VC VC :客户端关闭连接")\r
-                    EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":"VC连接断开了,请重试"}) \r
-                    self.ud_error(["VC",self.VC_taskid])\r
-                    break\r
-                try:\r
-                    self.websocket_error_count = 0\r
-                    if isinstance(recv_data, str):#返回的是字符串\r
-                        jsload_data = ujson.loads(recv_data)\r
-                        if VC_WS_IO_LOG:\r
-                            print("[VC] recv raw:{}".format(recv_data))\r
-                        else:\r
-                            try:\r
-                                _rev = jsload_data.get("event", "")\r
-                                if _rev != "llm.delta":\r
-                                    print("[VC] rx event={} sid={}".format(\r
-                                        _rev, jsload_data.get("sid", "") or "-"))\r
-                            except Exception:\r
-                                pass\r
-                        #print("AI_client_VC_recv jsload_data = {}".format(jsload_data))\r
-                        try:\r
-                            self._cur_sid = jsload_data.get("sid", "") or ""\r
-                        except Exception:\r
-                            self._cur_sid = ""\r
-                        try:\r
-                            ev = jsload_data.get("event", "")\r
-                            if ev:\r
-                                self._vc_dispatch_server_event(ev, jsload_data)\r
-                        except Exception:\r
-                            pass\r
-                        \r
-                        if jsload_data["event"] == "started" :#会话开始\r
-                            #payload = jsload_data["payload"]\r
-                            # self.VC_dialog_id = payload["dialog_id"] \r
-                            # self.VC_task_id = jsload_data["task_id"] \r
-                            # started 仅表示链路就绪:不在这里提前切灯态;\r
-                            # 聆听灯只在“录音真正开始/停止”处更新(VC_Speech_Start/End)。\r
-                            # 会话开始:允许上行;连续对话模式下直接进入“可发送音频”状态\r
-                            self._vc_session_started = 1\r
-                            # started 时不要强制清 Close_flag:\r
-                            # - Close_flag==1:提示音/模式切换播完退出\r
-                            # - Close_flag==0:正常对话流程\r
-                            self._ptt_audio_created = 0\r
-                            self._server_vad_silence = 0\r
-                            self._turn_has_asr_completed = 0\r
-                            self._turn_has_vad_silence = 0\r
-                            self._turn_has_tts_started = 0\r
-                            self._llm_completed_ms = 0\r
-                            if self.VC_mode == 0 and getattr(self, "Close_flag", 0) == 0:\r
-                                _skip_uplink = 0\r
-                                try:\r
-                                    if getattr(self, "_skip_started_uplink_once", 0) == 1:\r
-                                        self._skip_started_uplink_once = 0\r
-                                        _skip_uplink = 1\r
-                                        print("[VC][uplink_ctrl] Deny START by server.started cause=boot_prompt")\r
-                                except Exception:\r
-                                    pass\r
-                                if not _skip_uplink:\r
-                                    # 会话已 started:若此时上行未启动,则在这里补一次开麦,避免“Start 前开麦被 Close 停掉”后\r
-                                    # 出现首轮无音频上行导致“第一次对话没有”的情况。\r
-                                    if self.record_data_save_flag == 0:\r
-                                        if self.VC_record_state_flag == 0:\r
-                                            self.request_uplink_start("server.started")\r
-                                        else:\r
-                                            self._vc_clear_stale_record_state("server.started")\r
-                                            self.request_uplink_start("server.started/retry")\r
-                            \r
-                            print("VC:Started!!!!!!!!\r\n")\r
-                            print("TTS音频为:{},语音下发地址为:{}".format(self.AI_TTS_str,self.AI_new_voice_url))\r
-                            if self.AI_new_voice_url != "" and self.alarm_play_flag == 1 :#有闹钟,先响闹钟\r
-                                self.AI_VC_start()\r
-                                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放铃声\r
-                                self.alarm_play_flag = 0\r
-                                self.voice_choose_flag = 1\r
-                            elif self.AI_TTS_str == "" and  self.AI_new_voice_url != "" and self.music_play_flag == 0:\r
-                                # if EventMesh.publish("get_history_voice_flag") == 1:\r
-                                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言\r
-                                # self.recv_voice = 0#当留言列表为空,留言播放标志清空\r
-                                # else:\r
-                                #     self.voice_cnt = 0\r
-                                #     print("定时器开启222222222")\r
-                                #     self.voice_timer.start(1000,1,self.voice_judge)\r
-                            elif  self.AI_TTS_str != "" and  self.AI_new_voice_url == "":\r
-                                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})\r
-                            \r
-                            elif self.AI_TTS_str != "" and  self.AI_new_voice_url != "" and self.music_play_flag == 0:#遇到tts和语音留言冲突时(并且没有音乐播放)\r
-                                if EventMesh.publish("get_history_voice_flag") == 1:#播放历史留言\r
-                                    if EventMesh.publish("get_wait_voice_flag") == 0:\r
-                                        EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})\r
-                                        self.his_cnt = 0\r
-                                        self.his_timer.start(1000,1,self.history_play) \r
-                                        self.voice_choose_flag = 1\r
-                                    else:#等待留言\r
-                                        if self.pwk_notice_flag == 0:\r
-                                            self.wait_cnt = 0\r
-                                            print("未听播放1")\r
-                                            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})\r
-                                            self.wait_timer.start(1000,1,self.wait_play) \r
-                                            self.voice_choose_flag = 1\r
-                            \r
-                                        else:#已确定播放\r
-                                            print("未听播放2")\r
-                                            self.wait_cnt = 0\r
-                                            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})\r
-                                            self.wait_timer.start(1000,1,self.wait_single_play) \r
-                                            self.voice_choose_flag = 1\r
-                                else:#播放正常留言\r
-                                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})\r
-                                    self.voice_cnt = 0\r
-                                    print("定时器开启111111111")\r
-                                    self.voice_timer.start(1000,1,self.voice_judge) \r
-                                    self.voice_choose_flag = 1\r
-                                # self.AI_TTS_str = ""    \r
-                            elif self.AI_TTS_str != "" and  self.AI_new_voice_url != "" and self.music_play_flag == 1 :#遇到tts和语音留言冲突时(并且有音乐播放)\r
-                                self.music_play_flag = 0\r
-                                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})\r
-                                self.music_timer.start(1000,1,self.music_play) \r
-                                self.voice_choose_flag = 1\r
-                            else:\r
-                                #开始上传语音\r
-                                try:\r
-                                    if "vc_config" in globals() and vc_config and vc_config.get_bool("UPLOAD_START_TONE_ENABLED", False):\r
-                                        self.chenggong_tone()\r
-                                except Exception:\r
-                                    pass\r
-                                if self.VC_mode == 0:\r
-                                    self.request_uplink_start("server.started/idle")\r
-                                else:\r
-                                    # Push-To-Talk:必须先同步发出 audio.create,再开麦。\r
-                                    # request_send_audio_create() 走 EventMesh 异步队列,会导致录音线程先于\r
-                                    # audio.create 上行,深度睡眠唤醒后首轮 ASR 空结果、第二轮才正常。\r
-                                    try:\r
-                                        self.AI_client_VC_send_speech()\r
-                                    except Exception:\r
-                                        self.request_send_audio_create()\r
-                                    # 若长按发生在“未 started”窗口,收到 started 后立即开麦进入新一轮 PTT\r
-                                    try:\r
-                                        if getattr(self, "_ptt_force_record_on_started", 0) == 1:\r
-                                            self._ptt_force_record_on_started = 0\r
-                                            self.VC_Speech_Start_now()\r
-                                    except Exception:\r
-                                        pass\r
-                                \r
-                            \r
-                            # if self.AI_new_voice_url != "":\r
-                            #     EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})\r
-\r
-                        elif jsload_data["event"] == "config.update" :#配置更新中,需要停止录音\r
-                            try:\r
-                                self.fsm.reset_rx_phases()\r
-                            except Exception:\r
-                                pass\r
-                            self.request_uplink_stop("config.update")\r
-\r
-                        elif jsload_data["event"] == "asr.started" :#asr开始 \r
-                            try:\r
-                                self.fsm.on_server_asr_started()\r
-                            except Exception:\r
-                                pass\r
-                            self._server_vad_silence = 0\r
-                            self._turn_has_asr_completed = 0\r
-                            self._turn_has_vad_silence = 0\r
-                            self._turn_has_tts_started = 0\r
-                            self._llm_completed_ms = 0\r
-                            try:\r
-                                self._asr_completed_tone_done = 0\r
-                            except Exception:\r
-                                pass\r
-                            # 单对话:_drop_tts_audio 在 VC_Speech_Start 真开录后再清 0,避免 canceled 后仍播旧 TTS\r
-                            try:\r
-                                if getattr(self, "VC_mode", 0) != 1:\r
-                                    self._drop_tts_audio = 0\r
-                            except Exception:\r
-                                pass\r
-                            # 单对话:勿在 asr.started 置 flag=2(未真开麦会僵死);由 VC_Speech_Start 维护\r
-                            self.LocalRespondingEnded_timerout = 0\r
-                        elif jsload_data["event"] == "asr.result" :#asr结果\r
-                            try:\r
-                                self.AI_ASR_str = jsload_data["payload"]["content"]\r
-                            except Exception:\r
-                                try:\r
-                                    self.AI_ASR_str = jsload_data.get("payload", {}).get("text", "") or ""\r
-                                except Exception:\r
-                                    self.AI_ASR_str = ""\r
-                            print("[VC][ASR] {}".format(self.AI_ASR_str))\r
-                            try:\r
-                                if (self.AI_ASR_str or "").strip():\r
-                                    self._last_asr_result_text = self.AI_ASR_str\r
-                            except Exception:\r
-                                pass\r
-                            if self.AI_ASR_str == "":#没识别到\r
-                                self.AI_TTS_str = "我没听清,请再说一遍"\r
-                                self._vc_led_alert_rb_flash()\r
-                        elif jsload_data["event"] == "asr.completed" :#asr 终止的标识 \r
-                            self._turn_has_asr_completed = 1\r
-                            try:\r
-                                if not (self.AI_ASR_str or "").strip():\r
-                                    self.AI_ASR_str = getattr(self, "_last_asr_result_text", "") or ""\r
-                            except Exception:\r
-                                pass\r
-                            # 本地 think 提示:仅在有有效 ASR 文本时播,避免空轮次连播「滴」\r
-                            try:\r
-                                if getattr(self, "_asr_completed_tone_done", 0) == 1:\r
-                                    pass\r
-                                elif not self._vc_effective_asr_text():\r
-                                    try:\r
-                                        print("[VC][ASR] completed empty, skip think tone")\r
-                                    except Exception:\r
-                                        pass\r
-                                else:\r
-                                    stream_on = False\r
-                                    try:\r
-                                        stream_on = EventMesh.publish("get_vc_tts_playback_active") == 1\r
-                                    except Exception:\r
-                                        stream_on = False\r
-                                    pcm_busy = getattr(self, "play_pcm", None) is not None\r
-                                    playing = getattr(self, "VC_Play_flag", 0) == 1\r
-                                    try:\r
-                                        thinking = getattr(self.fsm, "turn", VCTurnState.Idle) == VCTurnState.Thinking\r
-                                    except Exception:\r
-                                        thinking = False\r
-                                    if (not stream_on) and (not pcm_busy) and (not playing) and (not thinking):\r
-                                        self._vc_local_play('U:/static/think.mp3', 1, 1, 650)\r
-                                        self._asr_completed_tone_done = 1\r
-                            except Exception:\r
-                                pass\r
-                            print("self.commands_mode:{}".format(self.commands_mode))\r
-                            if self.commands_mode ==2:\r
-                                ret= self.check_text(self._vc_effective_asr_text())\r
-                                if ret == 0:#配置指令\r
-                                            \r
-                                    if self.AI_VC_send_data_queue.empty()!=True:\r
-                                        self.AI_VC_send_data_queue.clear()\r
-                                    if self.AI_ASR_send_data_queue.empty()!=True:\r
-                                        self.AI_ASR_send_data_queue.clear()\r
-                                    self.AI_ASR_send_buf_len = 0\r
-                                    EventMesh.publish("set_led_state", {"ai_state": "thinking"})\r
-                                    try:\r
-                                        \r
-                                        self.request_uplink_stop("asr.completed/config_cmd")\r
-                                        EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) \r
-                                        EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) \r
-                                    except Exception as e:\r
-                                        usys.print_exception(e)\r
-                                    continue\r
-                                elif ret == 1:#不处理\r
-                                    pass    \r
-                            #self.VC_Speech_End()\r
-                            #EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})\r
-\r
-                            # if self.record_data_save_flag == 1:\r
-                            #     if self.record_fd :\r
-                            #         self.record_fd.close()\r
-                            #     self.record_fd = None\r
-\r
-                            # if dev_type != "7PIN" :\r
-                            #EventMesh.publish("publish_AI_btnInfo",{"func":self.send_tone}) \r
-                        elif jsload_data["event"] == "vad.active" :#VAD 检测到说话→聆听\r
-                            try:\r
-                                if "vc_config" in globals() and vc_config and vc_config.get_bool("VAD_ACTIVE_BLUE_LED", True):\r
-                                    self._server_vad_silence = 0\r
-                            except Exception:\r
-                                pass\r
-                        elif jsload_data["event"] == "vad.silence" :#VAD 终止的标识 \r
-                            # 说明服务端已判定“本轮说话结束”;此时继续上行只会干扰后续(例如推迟 TTS)。\r
-                            # 仅在“正在录音上行”的语音阶段才停止,避免误触发。\r
-                            self._server_vad_silence = 1\r
-                            self._turn_has_vad_silence = 1\r
-                            self._uplink_mark_end("vad.silence")\r
-                            if self.record_data_save_flag == 0:\r
-                                if self.VC_record_state_flag != 0 and self._vc_vad_uplink_stop_eligible():\r
-                                    self.request_uplink_stop("vad.silence")\r
-                        elif jsload_data["event"] == "llm.created" :#思考(LLM)\r
-                            try:\r
-                                self._ptt_await_server = 0\r
-                            except Exception:\r
-                                pass\r
-                            try:\r
-                                EventMesh.publish("set_led_state", {"ai_state": "thinking"})\r
-                            except Exception:\r
-                                pass\r
-                            self._llm_completed_ms = 0\r
-                            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Play_Start})\r
-                            pass\r
-                        elif jsload_data["event"] == "llm.completed" :#llm完成(可能无 tts)\r
-                            try:\r
-                                self._llm_completed_ms = utime.ticks_ms()\r
-                            except Exception:\r
-                                # 勿用 1:ticks_diff(now,1)≈开机至今,会瞬间满足 thinking 超时误以为该重启\r
-                                self._llm_completed_ms = 0\r
-                            try:\r
-                                self._cur_dialog_id = jsload_data.get("payload", {}).get("dialog_id", "") or ""\r
-                            except Exception:\r
-                                self._cur_dialog_id = ""\r
-                            try:\r
-                                self.fsm.log("server.llm.completed")\r
-                            except Exception:\r
-                                pass\r
-                            pass\r
-                        elif jsload_data["event"] == "llm.delta" :#llm返回数据\r
-                            if self.is_VAD == 0 and self.record_data_save_flag == 0:\r
-                                if self.VC_record_state_flag == 1:#llm返回数据还没停止,就先停止\r
-                                    self.request_uplink_stop("llm.delta")\r
-                        elif jsload_data["event"] == "tts.started" :#tts开始\r
-                            try:\r
-                                self._ptt_await_server = 0\r
-                            except Exception:\r
-                                pass\r
-                            self._server_vad_silence = 1\r
-                            self._turn_has_tts_started = 1\r
-                            self._tts_pending = 0\r
-                            self._tts_pending_ms = 0\r
-                            self._uplink_mark_end("tts.started")\r
-                            # stop uplink + play_start 由 FSM action list 统一触发,避免多处重复调用\r
-                            # 标记本轮 TTS 来源:\r
-                            # - 有 payload.dialog_id:认为是“对话回复 TTS”\r
-                            # - 无 dialog_id:认为是“提示/提醒 TTS”(模式切换可通过 _pending_tts_kind 标记)\r
-                            try:\r
-                                did = jsload_data.get("payload", {}).get("dialog_id", "") or ""\r
-                            except Exception:\r
-                                did = ""\r
-                            # 注意:无 dialog_id 的 tts.started 很可能是提示音/提醒音;\r
-                            # 但“模式切换提示音”的 kind 可能已在 synthesis 时写入 _last_tts_kind,\r
-                            # 这里不应无条件覆盖为 prompt,否则播完无法触发下一轮开麦。\r
-                            try:\r
-                                if did:\r
-                                    self._last_tts_kind = "dialog"\r
-                                else:\r
-                                    k = getattr(self, "_pending_tts_kind", "") or ""\r
-                                    if k:\r
-                                        self._last_tts_kind = k\r
-                                    else:\r
-                                        cur = getattr(self, "_last_tts_kind", "") or ""\r
-                                        if cur in (\r
-                                            "mode_switch_long",\r
-                                            "mode_switch_ptt",\r
-                                            "boot_prompt",\r
-                                        ):\r
-                                            pass\r
-                                        elif not cur:\r
-                                            self._last_tts_kind = "prompt"\r
-                            except Exception:\r
-                                pass\r
-                            try:\r
-                                self._pending_tts_kind = ""\r
-                            except Exception:\r
-                                pass\r
-                            # 播放灯由阶段事件控制\r
-                        elif jsload_data["event"] == "tts.completed" :#tts结束\r
-                            # tts.completed 仅表示下发结束,本地可能仍在播放;绿灯关闭在本地播放结束时处理\r
-                            try:\r
-                                # server event 已在统一入口 on_server_event 触发 FSM,这里避免重复驱动\r
-                                pass\r
-                            except Exception:\r
-                                pass\r
-                            try:\r
-                                try:\r
-                                    sid = getattr(self, "_cur_sid", "")\r
-                                    did = getattr(self, "_cur_dialog_id", "")\r
-                                except Exception:\r
-                                    sid, did = "", ""\r
-                                print("[TTS] completed in_len={} out_len={} q_empty={} play_pcm={} sid={} did={}".format(\r
-                                    getattr(self, "tts_in_data_len", 0),\r
-                                    getattr(self, "tts_out_data_len", 0),\r
-                                    1 if self.AI_audio_queue.empty() else 0,\r
-                                    1 if self.play_pcm != None else 0,\r
-                                    sid,\r
-                                    did,\r
-                                ))\r
-                            except Exception:\r
-                                pass\r
-                            # 播完上报完全由 FSM await_played + play_end 驱动,这里不再使用 LocalRespondingEnded_timerout/_need_audio_played_ack\r
-                            pass\r
-\r
-                        elif jsload_data["event"] == "canceled" :#打断\r
-                            # 停播/清队列已在 _vc_dispatch_server_event(canceled) 完成;此处仅收尾标志\r
-                            self.is_ruppt_tts = 0#设备上发中断tts,收到服务器返回的cancel后置为0\r
-                            self._need_audio_played_ack = 0\r
-                            self._tts_pending = 0\r
-                            self._tts_pending_ms = 0\r
-                            # 开麦由 FSM action list 统一触发(uplink_start:canceled),避免重复调用\r
-                            if self.AI_audio_queue_thread_id ==0:\r
-                                _thread.stack_size(24*1024)\r
-                                self.AI_audio_queue_thread_id = _thread.start_new_thread(self.AI_audio_queue_thread, ())\r
-                                _thread.stack_size(8*1024)\r
-                                print("AI_VC_start :AI_audio_queue_thread_id :{} ".format(self.AI_audio_queue_thread_id))\r
-                            else:\r
-                                print("AI_VC_start :AI_audio_queue_thread_id != 0 ")\r
-                        elif jsload_data["event"] == "completed" :#会话结束(可能无 TTS,如 ASR 空)\r
-                            # 重要:服务端常在 tts.completed 后紧跟 completed。\r
-                            # 若本轮存在 TTS/待发 audio.played,则不能在此清零 LocalRespondingEnded_timerout/_need_audio_played_ack,\r
-                            # 否则会掐断「播完→audio.played→下一轮录音」链路,表现为最多 1~2 轮就停。\r
-                            if self._need_audio_played_ack == 1 or self._vc_tts_pipeline_busy() or self.VC_Play_flag == 1:\r
-                                pass\r
-                            else:\r
-                                # 无 TTS 的 completed:清状态并按需进入下一轮(见下方条件)\r
-                                self.LocalRespondingEnded_timerout = 0\r
-                                self._need_audio_played_ack = 0\r
-                                try:\r
-                                    self.fsm.reset_rx_phases()\r
-                                except Exception:\r
-                                    pass\r
-                            # 连续对话:若本轮没有 TTS(或服务端直接 completed),需要立刻回到聆听状态\r
-                            # 否则会出现“有时 1 轮/2 轮后就停住”的现象(服务端 completed 但端侧未重新开录音上行)。\r
-                            if self.VC_mode == 0 and self.Close_flag == 0:\r
-                                # 仅当“本轮已结束一次语句(vad.silence 或 asr.completed)且从未进入 tts.started”\r
-                                # 才自动进入下一轮;避免把真正的“会话结束/异常结束”误当成下一轮入口。\r
-                                if (self._turn_has_tts_started == 0) and (self._turn_has_vad_silence == 1 or self._turn_has_asr_completed == 1):\r
-                                    self._server_vad_silence = 0\r
-                                    # 开麦由 FSM action list 统一触发(uplink_start:completed),这里不再重复调用\r
-                            pass\r
-\r
-                        elif jsload_data["event"] == "command" :#命令匹配\r
-                            ret = -1\r
-                            try:\r
-                                if self.commands_mode ==1:\r
-                                    if len(jsload_data["payload"]["functions"])>0:\r
-                                        \r
-                                        cmd = jsload_data["payload"]["functions"][0]["function"]["arguments"]\r
-                                        print("llm:cmd:{}".format(cmd))\r
-                                        cmd = ujson.loads(cmd)\r
-                                        ret = self.check_text_llm(cmd)\r
-                                elif self.commands_mode ==0:\r
-\r
-                                    cmd = jsload_data["payload"]\r
-                                    print("server:cmd:{}".format(cmd))\r
-                                    ret = self.check_text_server(cmd)\r
-                                else:\r
-                                    cmd = jsload_data["payload"]["functions"][0]["function"]["arguments"]\r
-                                    print("llm:cmd:{}".format(cmd))\r
-                                    cmd = ujson.loads(cmd)\r
-                                    ret = self.check_text_llm(cmd)\r
-                            except Exception as e:\r
-                                usys.print_exception(e)       \r
-                            if ret == -1:\r
-                                if self.AI_TTS_str  =="":\r
-                                    self.AI_TTS_str = "抱歉,没找到对应操作,可以换个说法试试" \r
-                            #EventMesh.publish("publish_AI_btnInfo",{"func":utime.sleep_ms,'data':(10)})\r
-                            self.request_uplink_stop("command")\r
-                            EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) \r
-                            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) \r
-                        elif jsload_data["event"] == "failed":#接口返回错误\r
-                            if jsload_data["payload"]["error"] == "concurrency_limit_exceeded":\r
-                                EventMesh.publish("publish_AI_btnInfo",{"func":self.concurrency_limit_exceeded_tone}) \r
-                                \r
-                                #utime.sleep_ms(4000)\r
-                                \r
-                            else:\r
-                                EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":jsload_data["payload"]["message"]}) \r
-                            pass\r
-                           \r
-                        elif jsload_data["event"] == "audio.convert.completed":#音频转成完成没有出错 接口 \r
-                            # self.VC_state = VC_State_End\r
-                            print("未读留言初始数值:{}".format(self.pwk_cnt_voice))\r
-                            if self.pwk_notice_flag == 1:#只有在播放的时候才会去计数\r
-                                if self.pwk_cnt_voice > 0:\r
-                                    self.pwk_cnt_voice -= 1#减1\r
-                                    print("未读留言又-1,当前留言剩余数量为:{}".format(self.pwk_cnt_voice))\r
-                                else:\r
-                                    print("未读列表为空")\r
-                            if self.pwk_cnt_voice == 0:#积累的留言已经全部播完\r
-                                self.pwk_notice_flag = 0\r
-                                self.wait_sort = 0\r
-                                self.wait_first_flag = 0#让下次未读播放提示恢复\r
-                                self.voicerupt_flag = 0 #标志位置0\r
-                                self.wait_voicerupt_flag = 0\r
-                                self.voice_notice = 1#开启回复\r
-                                if self.history_voice_flag == 1 and self.wait_voice_flag == 0:\r
-                                    self.voice_notice = 0\r
-                                EventMesh.publish("set_storage_config",["is_noread_voice",0])\r
-                                \r
-                            if self.voice_choose_flag == 1:#播放完语音后,有冲突的时候关闭\r
-                                self.voice_choose_flag = 0\r
-                                self.Close_flag = 1\r
-                                self.LocalRespondingEnded_timerout = 8   \r
-                                # EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) \r
-                            if self.voice_notice == 1:#已收到留言,进行该留言回复逻辑处理\r
-                                tts_play_str = "如需回复,长按确认键开始录制,松开发送。十秒内无操作自动退出"\r
-                                EventMesh.publish("play_tts",[tts_play_str,1])\r
-                                self.voice_notice = 0#标志位清空\r
-                                self.voice_timer.stop()\r
-                                self.revoice_cnt = 0\r
-                                self.voice_flag = 1\r
-                                print("语音留言回复停止")\r
-                                self.revoice_timer.start(1000,1,self.revoice_judge)\r
-                            print("是否未读:{},是否播完:{}".format(EventMesh.publish("get_wait_voice_flag"),len(EventMesh.publish("get_play_new_voice_list"))))\r
-                            # if EventMesh.publish("get_wait_voice_flag") == 1 and len(EventMesh.publish("get_play_new_voice_list")) ==0:#还在未读并且列表为空\r
-                            #     self.wait_first_flag = 0\r
-                            #     print("未读留言播完,清空标志")\r
-                            #     EventMesh.publish("set_wait_voice_flag",0)\r
-                            #     EventMesh.publish("set_history_voice_flag",0)\r
-                            EventMesh.publish("update_play_new_voice_info")#更新已读状态\r
-                            self.recv_voice = 0\r
-                            print("不开启定时器标志位置0")\r
-                            self.LocalRespondingEnded_timerout = 8\r
-                            try:\r
-                                self.fsm.reset_rx_phases()\r
-                            except Exception:\r
-                                pass\r
-                                \r
-                            pass\r
-                        elif jsload_data["event"] == "audio.convert.failed": #音频转换失败了\r
-                            pass\r
-                                \r
-                        \r
-                    else:#下发音频数据\r
-                        #self.AI_ASR_str = ""#下发音频后清掉asr结果\r
-                        \r
-                        \r
-                        # PTT 打断后丢弃残留 TTS 音频帧,避免“断断续续继续播”\r
-                        try:\r
-                            if self._ptt_should_drop_inbound_tts():\r
-                                continue\r
-                        except Exception:\r
-                            pass\r
-                        if self.VC_stop_play_audio_flag == 0:\r
-                            print("新的AI对话不允许以前的播放数据:")\r
-                            break\r
-\r
-                        # if VC_AUDIO_FRAME_LOG:\r
-                        #     print("下发音频数据1:{}".format(len(recv_data)))\r
-\r
-                        if self.play_data_save_flag == 1:\r
-                            if self.play_fd ==None:\r
-                                try:\r
-                                    uos.remove('/usr/play_data.pcm')\r
-                                except Exception as e:\r
-                                    usys.print_exception(e)\r
-                                self.play_fd = open('/usr/play_data.pcm','w')\r
-                            if self.play_fd !=None:\r
-                                self.play_fd.write(recv_data)\r
-                        # print("下发音频数据2:{}".format(len(recv_data)))\r
-                        #if self.VC_state >= VC_State_TTSStart:\r
-                            #self.put_record_data([recv_data,""])\r
-                        if self.is_ruppt_tts == 0:#被设备主动打断打断则不播放,知道收到服务器返回的cancel才修改标志位\r
-                            if self.VC_play_TYPE == VC_play_TYPE_PCM:\r
-                                if self.play_pcm != None:\r
-                                    try:\r
-                                        #print("play_pcm.stream_write !!!")\r
-                                        buf_len = self.play_pcm.stream_get_buf_len()\r
-                                        count_tmp = 1000\r
-                                        while buf_len < len(recv_data):\r
-                                            utime.sleep_ms(1)\r
-                                            count_tmp-=1\r
-                                            if count_tmp<=0 or self.play_pcm == None:\r
-                                                break\r
-                                            buf_len = self.play_pcm.stream_get_buf_len()\r
-                                        #print("buf已满,等待释放:buf_len:{},count_tmp:{}".format(buf_len,count_tmp))\r
-                                    except Exception as e:\r
-                                        usys.print_exception(e)\r
-                            EventMesh.publish("publish_AI_btnInfo",{"func":self.put_record_data,"data":[recv_data,""]}) \r
-                        else:\r
-                            print("被打断不播放") \r
-                        utime.sleep_ms(1)\r
-                except Exception as e:\r
-                    usys.print_exception(e)\r
-                \r
-            except Exception as e:\r
-                    error_str = str(e)\r
-                    #print("VC VC:e:{}".format(error_str))\r
-                    if error_str.find("[Errno 110] ETIMEDOUT")!= -1 or error_str.find("[Errno 115] ")!= -1:\r
-                        if VC_HEARTBEAT_LOG:\r
-                            print("VC VC:recv 正常报错:{}".format(error_str))\r
-                        # recv 超时仅表示本周期无帧,链路仍可能在等 LLM/TTS;清零避免误触发「接口无响应」\r
-                        self.close_timerout = 0\r
-                        pass\r
-                    else:\r
-                        usys.print_exception(e)\r
-                        #self.websocket_error()\r
-                        print("VC VC :读数据报错,退出")\r
-                        EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":"VC出错了,请重试"}) \r
-                        self.ud_error(["VC",self.VC_taskid])\r
-                        break\r
-        self.AI_client_VC_isconnect = 0\r
-        self.AI_client_VC_recv_id = 0 \r
-        self.AI_client_VC_recv_lock = 0\r
-        print("[VC] recv_thread exit")\r
-    \r
-    def voice_play_10S(self,event,msg):#按键开启留言播放等待10S定时器判断\r
-        self.voice_timer.start(1000,1,self.voice_judge)\r
-    #语音留言定时器\r
-    def voice_judge(self, *args, **kwargs):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.voice_judger_cb})  \r
-    def voice_judger_cb(self):\r
-        self.voice_cnt += 1\r
-        # print("语音留言定时器cnt = {}".format(self.voice_cnt))\r
-        if self.voicerupt_flag == 1:#按键被按下\r
-           self.pwk_notice_flag = 1#表示进入收听未听留言\r
-           self.AI_VC_start()\r
-           EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言\r
-           self.voice_cnt = 0\r
-        #    self.recv_voice = 0#进入留言定时器,留言清空\r
-           print("进入留言定时器1,留言清空!!!!!")\r
-           EventMesh.publish("update_play_new_voice_info")#更新已读状态\r
-           self.voice_timer.stop()\r
-           if self.pwk_cnt_voice > 0:\r
-               EventMesh.publish("get_new_voice_msg_S")\r
-        if self.voice_cnt > 15:#10S后退出播放,5S是为了给语音提示留时间\r
-           self.voice_cnt = 0\r
-           self.recv_voice = 0#进入留言定时器,留言清空\r
-           if self.voice_choose_flag == 1:#播放完语音后,有冲突的时候关闭\r
-              self.voice_choose_flag = 0\r
-           print("进入留言定时器2,留言清空!!!!!")\r
-           self.voice_timer.stop()\r
-    #语音留言回复定时器\r
-    def revoice_judge(self, *args, **kwargs):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.revoice_judger_cb})  \r
-    def revoice_judger_cb(self):\r
-        self.revoice_cnt += 1\r
-        # print("语音留言回复定时器cnt = {}".format(self.revoice_cnt))\r
-        if self.revoice_cnt > 20:#10S后退出播放,10S是为了给语音提示留时间\r
-           self.revoice_cnt = 0\r
-           self.voice_flag=0\r
-           EventMesh.publish("set_1M_nowplay_flag",0)\r
-           print("语音留言回复停止")\r
-           self.revoice_timer.stop()\r
-    #音乐播放定时器\r
-    def music_play(self, *args, **kwargs):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.music_play_cb})  \r
-    def music_play_cb(self):\r
-        self.music_cnt += 1\r
-        if self.music_cnt > 4:#4S后播放音乐,4S是为了给语音提示留时间\r
-           self.AI_VC_start()\r
-           EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言\r
-           self.music_cnt = 0\r
-           self.music_timer.stop() \r
-    #历史留言播放定时器\r
-    def history_play(self, *args, **kwargs):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.history_play_cb})  \r
-    def history_play_cb(self):\r
-        self.his_cnt += 1\r
-        if self.his_cnt > 4:#4S后播放,4S是为了给语音提示留时间\r
-           self.AI_VC_start()\r
-           EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言\r
-           self.his_cnt = 0\r
-           self.his_timer.stop() \r
-    #未读留言是否播放定时器\r
-    def wait_play(self, *args, **kwargs):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.wait_play_cb})  \r
-    def wait_play_cb(self):\r
-        self.wait_cnt += 1\r
-        if self.wait_voicerupt_flag == 1:#按键被按下\r
-           EventMesh.publish("set_is_listen_wait",1)#已听\r
-           self.pwk_notice_flag = 1#表示进入收听未听留言\r
-           self.AI_VC_start()\r
-           self.wait_sort = 1\r
-           self.AI_TTS_str = "第"+str(self.wait_sort)+"条" \r
-        #    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言\r
-        #    self.wait_cnt = 0\r
-        #    print("进入留言定时器1,留言清空!!!!!")\r
-        #    EventMesh.publish("update_play_new_voice_info")#更新已读状态\r
-           self.wait_timer.stop()\r
-        if self.wait_cnt > 10:#10S后退出播放,5S是为了给语音提示留时间\r
-           self.wait_cnt = 0\r
-           self.recv_voice = 0#进入留言定时器,留言清空\r
-           EventMesh.publish("set_history_voice_flag",0)\r
-           EventMesh.publish("set_wait_voice_flag",0)\r
-           EventMesh.publish("set_open_device",0)#开机不听\r
-           self.wait_first_flag = 0\r
-           if self.voice_choose_flag == 1:#播放完语音后,有冲突的时候关闭\r
-              self.voice_choose_flag = 0\r
-           print("进入留言定时器2,留言清空!!!!!")\r
-           self.wait_timer.stop()\r
-    #未读留言一条条播放定时器\r
-    def wait_single_play(self, *args, **kwargs):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.wait_single_play_cb})  \r
-    def wait_single_play_cb(self):\r
-        self.wait_cnt += 1\r
-        if self.wait_cnt > 2:#2S后播放,2S是为了给语音提示留时间\r
-           self.AI_VC_start()\r
-           EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言\r
-           self.wait_cnt = 0\r
-           self.wait_timer.stop() \r
-    def set_voicerupt_flag(self,event,msg):#按键控制当前留言播放\r
-        self.voicerupt_flag = msg\r
-    def set_wait_voicerupt_flag(self,event,msg):#按键控制未读留言播放\r
-        self.wait_voicerupt_flag = msg        \r
-    def get_voicerupt_flag(self,event,msg):#获取按键控制留言播放状态\r
-        return self.voicerupt_flag\r
-    def get_recv_voice(self,event,msg):\r
-        return self.recv_voice\r
-    def set_recv_voice(self,event,msg):\r
-        return self.recv_voice\r
-    def put_record_data(self,data):\r
-        try:\r
-            if self._ptt_should_drop_inbound_tts():\r
-                return\r
-        except Exception:\r
-            pass\r
-        #print("下发音频数据1:{}".format(len(data[0])))\r
-        ret=self.AI_audio_queue.put(data)\r
-        try:\r
-            if self.tts_in_data_len == 0 and data and len(data[0]) > 0:\r
-                try:\r
-                    sid = getattr(self, "_cur_sid", "")\r
-                    did = getattr(self, "_cur_dialog_id", "")\r
-                except Exception:\r
-                    sid, did = "", ""\r
-                print("[TTS] first_chunk len={} play_pcm={} VC_Play_flag={} sid={} did={}".format(\r
-                    len(data[0]),\r
-                    1 if self.play_pcm != None else 0,\r
-                    getattr(self, "VC_Play_flag", 0),\r
-                    sid,\r
-                    did,\r
-                ))\r
-        except Exception:\r
-            pass\r
-        self.tts_in_data_len += len(data[0])\r
-        #print("下发音频数据2:{},{}".format(len(data[0]),self.tts_in_data_len))\r
-    #msg[0]是tts播放字符串,msg[1]是是否关闭当前tts\r
-    def play_tts(self,event = None,msg= None):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_tts_cb,"data":msg}) \r
-        \r
-    def play_tts_cb(self,data):\r
-        try:\r
-            tts_str = data[0]\r
-            is_close_VC = data[1]\r
-            if tts_str != "":\r
-                self.AI_TTS_str = tts_str\r
-            if is_close_VC != None:\r
-                self.Close_flag = is_close_VC\r
-            if self.AI_VC_send_data_queue.empty()!=True:\r
-                self.AI_VC_send_data_queue.clear()\r
-            if self.AI_ASR_send_data_queue.empty()!=True:\r
-                self.AI_ASR_send_data_queue.clear()\r
-            try:\r
-                if is_close_VC:\r
-                    EventMesh.publish("set_led_state", {"ai_state": "idle"})\r
-                else:\r
-                    EventMesh.publish("set_led_state", {"ai_state": "thinking"})\r
-            except Exception:\r
-                pass\r
-            self.AI_ASR_send_buf_len = 0\r
-            self._uplink_request_stop("play_tts_cb")\r
-            self.websocket_deconnect_VC()\r
-            self.AI_VC_start()\r
-            \r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) \r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) \r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-        pass\r
-    def check_text_server(self,cmd):\r
-        return -1\r
-\r
-    def _apply_vc_text_intent(self, text, cmd, llm_style_tts):\r
-        """表驱动文本意图(vc_text_intent)。未命中返回 None;命中返回 0/1/-1。"""\r
-        if vc_text_intent is None:\r
-            return None\r
-        try:\r
-            try:\r
-                _btv = bool("vc_config" in globals() and vc_config and vc_config.get_bool("BT_VOICE_FEATURES_ENABLED", False))\r
-            except Exception:\r
-                _btv = False\r
-            r = vc_text_intent.match_vc_command(text, {"bt_voice": _btv})\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-            return None\r
-        if not r:\r
-            return None\r
-        return self._apply_vc_text_intent_matched(r, cmd, llm_style_tts)\r
-\r
-    def _apply_vc_text_intent_matched(self, r, cmd, llm_style_tts):\r
-        if r.get("needs_payload") and cmd is None:\r
-            return None\r
-        it = r["intent"]\r
-        if r.get("tts"):\r
-            self.AI_TTS_str = r["tts"]\r
-        if r.get("close_flag"):\r
-            self.Close_flag = 1\r
-        if it == "goodbye":\r
-            return 0\r
-        if it == "shutdown":\r
-            if llm_style_tts:\r
-                EventMesh.publish("publish_AI_btnInfo", {"func": self.Close})\r
-                EventMesh.publish("Power_off")\r
-                return 1\r
-            try:\r
-                close_only = ("vc_config" in globals() and vc_config and vc_config.get_bool("POWEROFF_CMD_CLOSE_ONLY", True))\r
-            except Exception:\r
-                close_only = True\r
-            if close_only:\r
-                EventMesh.publish("publish_AI_btnInfo", {"func": self.Close})\r
-                return 1\r
-            EventMesh.publish("publish_AI_btnInfo", {"func": self.Close})\r
-            EventMesh.publish("Power_off")\r
-            return 1\r
-        if it == "toggle_dialog_mode":\r
-            if self.VC_mode == 0:\r
-                self.VC_mode = 1\r
-                if llm_style_tts:\r
-                    self.AI_TTS_str = "了解,已切换到单对话模式"\r
-                else:\r
-                    try:\r
-                        self.AI_TTS_str = vc_config.get_attr("CMD_TOGGLE_MODE_TO_PTT_TTS", "已切换到单对话模式")\r
-                    except Exception:\r
-                        self.AI_TTS_str = "已切换到单对话模式"\r
-            else:\r
-                self.VC_mode = 0\r
-                if llm_style_tts:\r
-                    self.AI_TTS_str = "了解,已切换到长对话模式"\r
-                else:\r
-                    try:\r
-                        self.AI_TTS_str = vc_config.get_attr("CMD_TOGGLE_MODE_TO_LONG_TTS", "已切换到长对话模式")\r
-                    except Exception:\r
-                        self.AI_TTS_str = "已切换到长对话模式"\r
-            EventMesh.publish("set_storage_config", ["VC_mode", self.VC_mode])\r
-            return 0\r
-        if it == "volume_set":\r
-            try:\r
-                pj = ujson.loads(cmd["command"]["params_json"])\r
-                volume = pj["n"]\r
-            except Exception as e:\r
-                usys.print_exception(e)\r
-                try:\r
-                    self.AI_TTS_str = cmd["command"]["fail_prompt"]\r
-                except Exception:\r
-                    self.AI_TTS_str = ""\r
-                return -1\r
-            if volume > 100 or volume < 0:\r
-                self.AI_TTS_str = "音量调整超出范围了,只能0-100哦"\r
-            else:\r
-                self.VC_TTS_volume = volume\r
-                self.aud.setVolume(self.VC_TTS_volume // 10)\r
-                EventMesh.publish("set_storage_config", ["VC_TTS_volume", self.VC_TTS_volume])\r
-                self.AI_TTS_str = "了解,音量已经调整,当前音量是{}".format(self.VC_TTS_volume)\r
-                EventMesh.publish("post_device_state")\r
-                EventMesh.publish("adjust_volume_toBle", self.VC_TTS_volume)\r
-            return 0\r
-        if it == "volume_up":\r
-            if llm_style_tts:\r
-                try:\r
-                    print("self.VC_TTS_volume add:{}".format(self.VC_TTS_volume))\r
-                except Exception:\r
-                    pass\r
-            self.VC_TTS_volume += 10\r
-            if self.VC_TTS_volume > 100:\r
-                self.AI_TTS_str = "音量已经是最大啦" if llm_style_tts else "音量已最大"\r
-                self.VC_TTS_volume = 100\r
-            else:\r
-                self.aud.setVolume(self.VC_TTS_volume // 10)\r
-                EventMesh.publish("set_storage_config", ["VC_TTS_volume", self.VC_TTS_volume])\r
-                EventMesh.publish("post_device_state")\r
-                EventMesh.publish("adjust_volume_toBle", self.VC_TTS_volume)\r
-                self.AI_TTS_str = (\r
-                    "了解,音量已经调大了,当前音量是{}".format(self.VC_TTS_volume)\r
-                    if llm_style_tts\r
-                    else "配置成功,当前音量 {}".format(self.VC_TTS_volume)\r
-                )\r
-            return 0\r
-        if it == "volume_down":\r
-            if llm_style_tts:\r
-                try:\r
-                    print("self.VC_TTS_volume end:{}".format(self.VC_TTS_volume))\r
-                except Exception:\r
-                    pass\r
-            self.VC_TTS_volume -= 10\r
-            if self.VC_TTS_volume < 0:\r
-                self.VC_TTS_volume = 0\r
-                self.AI_TTS_str = "音量已经是最小啦" if llm_style_tts else "音量已最小"\r
-            else:\r
-                self.aud.setVolume(self.VC_TTS_volume // 10)\r
-                EventMesh.publish("set_storage_config", ["VC_TTS_volume", self.VC_TTS_volume])\r
-                EventMesh.publish("post_device_state")\r
-                EventMesh.publish("adjust_volume_toBle", self.VC_TTS_volume)\r
-                self.AI_TTS_str = (\r
-                    "好嘞,音量已经调小了,当前音量是{}".format(self.VC_TTS_volume)\r
-                    if llm_style_tts\r
-                    else "配置成功,当前音量 {}".format(self.VC_TTS_volume)\r
-                )\r
-            return 0\r
-        if it in ("speech_faster", "speech_rate_up"):\r
-            self.speech_rate += 10\r
-            if self.speech_rate > 50:\r
-                self.AI_TTS_str = "语速已经是最快了"\r
-                self.speech_rate = 50\r
-            else:\r
-                EventMesh.publish("set_storage_config", ["speech_rate", self.speech_rate])\r
-                self.AI_TTS_str = "明白,语速已经调快了,当前语速是{}".format(self.speech_rate)\r
-            return 0\r
-        if it in ("speech_slower", "speech_rate_down"):\r
-            self.speech_rate -= 10\r
-            if self.speech_rate < -50:\r
-                self.speech_rate = -50\r
-                self.AI_TTS_str = "语速已经是最慢了"\r
-            else:\r
-                EventMesh.publish("set_storage_config", ["speech_rate", self.speech_rate])\r
-                self.AI_TTS_str = "明白,语速已经调慢了,当前语速是{}".format(self.speech_rate)\r
-            return 0\r
-        if it == "vad_faster":\r
-            self.vad_silence -= 200\r
-            if self.vad_silence < 100:\r
-                self.AI_TTS_str = "已经是最快了"\r
-                self.vad_silence = 100\r
-            else:\r
-                self.AI_TTS_str = "配置成功,当前语音断句速度是{}毫秒".format(self.vad_silence)\r
-            EventMesh.publish("set_storage_config", ["vad_silence", self.vad_silence])\r
-            return 0\r
-        if it == "vad_slower":\r
-            self.vad_silence += 200\r
-            if self.vad_silence > 2000:\r
-                self.vad_silence = 2000\r
-                self.AI_TTS_str = "已经是最慢了"\r
-            else:\r
-                self.AI_TTS_str = "配置成功,当前语音断句速度是{}毫秒".format(self.vad_silence)\r
-            EventMesh.publish("set_storage_config", ["vad_silence", self.vad_silence])\r
-            return 0\r
-        if it == "query_signal":\r
-            signal = net.csqQueryPoll()\r
-            self.AI_TTS_str = "当前信号{}".format(signal)\r
-            return 0\r
-        if it == "query_battery":\r
-            battery = EventMesh.publish("get_battery")\r
-            try:\r
-                print("电量查询:{}".format(battery))\r
-            except Exception:\r
-                pass\r
-            self.AI_TTS_str = "电量剩余百分之{}".format(battery)\r
-            return 0\r
-        if it == "query_voltage":\r
-            Vbatt = Power.getVbatt()\r
-            self.AI_TTS_str = "当前电池电压{}".format(Vbatt)\r
-            return 0\r
-        if it == "query_iccid":\r
-            iccid = sim.getIccid()\r
-            if iccid != -1 and len(iccid) > 5:\r
-                try:\r
-                    print("iccid{}".format(iccid))\r
-                except Exception:\r
-                    pass\r
-                self.AI_TTS_str = "当前卡号为:{}".format(iccid)\r
-            else:\r
-                self.AI_TTS_str = "卡号查询失败"\r
-            return 0\r
-        if it == "query_imei":\r
-            imei = modem.getDevImei()\r
-            if imei != -1 and len(imei) > 5:\r
-                try:\r
-                    print("IMEI:{}".format(imei))\r
-                except Exception:\r
-                    pass\r
-                self.AI_TTS_str = "当前设备号为:{}".format(imei)\r
-            else:\r
-                self.AI_TTS_str = "设备号查询失败"\r
-            return 0\r
-        if it == "query_version":\r
-            bt_versions = EventMesh.publish("get_bt_versions")\r
-            if bt_versions is None or bt_versions == "":\r
-                self.AI_TTS_str = "当前版本号:" + version_util.get_tts_version()\r
-            else:\r
-                self.AI_TTS_str = "当前版本号:" + version_util.get_tts_version(bt_versions)\r
-            return 0\r
-        if it == "query_server":\r
-            self.AI_TTS_str = "服务器地址" + self.server_ip\r
-            return 0\r
-        if it == "ota_check":\r
-            if self.ai_OTA.min_sys_ota() == 1:\r
-                self.AI_TTS_str = "有新版本,准备开始升级,升级过程会多次重启设备,请保证流量充足及电量充足"\r
-            else:\r
-                self.AI_TTS_str = "当前已是最新版本"\r
-            return 0\r
-        if it == "factory_mode":\r
-            self.factory_mode()\r
-            return 1\r
-        if it == "alarm_set":\r
-            try:\r
-                alarm = ujson.loads(cmd["command"]["params_json"])\r
-                try:\r
-                    print("params_json:{}".format(cmd["command"]["params_json"]))\r
-                except Exception:\r
-                    pass\r
-                add_alarm_tmp = EventMesh.publish("AI_add_alarm", alarm["alarm"])\r
-                if add_alarm_tmp[0] > 0:\r
-                    self.AI_TTS_str = add_alarm_tmp[1]\r
-                    return 1\r
-            except Exception as e:\r
-                usys.print_exception(e)\r
-            try:\r
-                self.AI_TTS_str = cmd["command"]["fail_prompt"]\r
-            except Exception:\r
-                self.AI_TTS_str = ""\r
-            return -1\r
-        if it == "alarm_clear":\r
-            try:\r
-                self.AI_TTS_str = EventMesh.publish("AI_delete_alarm")\r
-            except Exception as e:\r
-                usys.print_exception(e)\r
-            return 0\r
-        if it == "alarm_list":\r
-            EventMesh.publish("read_alarm_list")\r
-            return 0\r
-        if it == "voice_message_mode":\r
-            self.voice_flag = 1\r
-            self.AI_TTS_str = "已进入语音留言模式,长按按钮开始录音,松开按钮结束"\r
-            self.Close_flag = 1\r
-            self.stop_voice_timer.stop()\r
-            self.stop_voice_timer.start(30 * 1000, 0, self.exit_voice_message_timer)\r
-            try:\r
-                print("语音留言!!!!!!!!!!")\r
-            except Exception:\r
-                pass\r
-            return 0\r
-        if it == "bt_ble_adv_on":\r
-            self.AI_TTS_str = "正在开蓝牙广播"\r
-            EventMesh.publish("AI_send_bt_ble_switch_toble", 1)\r
-            try:\r
-                print("正在开蓝牙广播")\r
-            except Exception:\r
-                pass\r
-            return 0\r
-        if it == "bt_ble_adv_off":\r
-            self.AI_TTS_str = "正在关蓝牙广播"\r
-            EventMesh.publish("AI_send_bt_ble_switch_toble", 2)\r
-            try:\r
-                print("正在关蓝牙广播")\r
-            except Exception:\r
-                pass\r
-            return 0\r
-        if it == "bt_classic_on":\r
-            self.AI_TTS_str = "正在开经典蓝牙"\r
-            EventMesh.publish("AI_send_bt_ble_switch_toble", 3)\r
-            try:\r
-                print("正在开经典蓝牙")\r
-            except Exception:\r
-                pass\r
-            return 0\r
-        if it == "bt_classic_off":\r
-            self.AI_TTS_str = "正在关经典蓝牙"\r
-            EventMesh.publish("AI_send_bt_ble_switch_toble", 4)\r
-            try:\r
-                print("正在关经典蓝牙")\r
-            except Exception:\r
-                pass\r
-            return 0\r
-        if it == "unbind_confirm":\r
-            self.AI_TTS_str = "设备解绑中...."\r
-            self.Close_flag = 1\r
-            EventMesh.publish("send_unbind_dev_to_iot")\r
-            try:\r
-                print("解绑设备!!!!!!!!!!!!!!")\r
-            except Exception:\r
-                pass\r
-            return 0\r
-        if it == "clear_earphone":\r
-            self.AI_TTS_str = "正在删除"\r
-            EventMesh.publish("clear_earphone_record")\r
-            try:\r
-                print("正在删除")\r
-            except Exception:\r
-                pass\r
-            return 0\r
-        return None\r
-\r
-    def check_text_llm(self,cmd):\r
-        try:\r
-            desc = cmd["command"]["chinese_description"]\r
-            if desc.find("切换音色") != -1:\r
-                text = cmd["command"]["params_json"]\r
-                try:\r
-                    for key in self.VC_TTS_voice_type:\r
-                        if text.find(key) != -1:\r
-                            self.VC_TTS_voice_key = key\r
-                            EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])\r
-                            self.AI_TTS_str = "了解,声音已切换"\r
-                            return 0\r
-                except Exception as e:\r
-                    usys.print_exception(e)\r
-                if text.find("男") != -1:\r
-                    if "男" in self.VC_TTS_voice_type:\r
-                        self.VC_TTS_voice_key = "男"\r
-                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])\r
-                        self.AI_TTS_str = "了解,声音已切换"\r
-                        return 0\r
-                if text.find("女") != -1:\r
-                    if "女" in self.VC_TTS_voice_type:\r
-                        self.VC_TTS_voice_key = "女"\r
-                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])\r
-                        self.AI_TTS_str = "了解,声音已切换"\r
-                        return 0\r
-                if text.find("同声") != -1 or text.find("童") != -1:\r
-                    if "童" in self.VC_TTS_voice_type:\r
-                        self.VC_TTS_voice_key = "童"\r
-                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])\r
-                        self.AI_TTS_str = "了解,声音已切换"\r
-                        return 0\r
-                if text.find("粤") != -1 or text.find("越野") != -1 or text.find("广东话") != -1:\r
-                    if "粤" in self.VC_TTS_voice_type:\r
-                        self.VC_TTS_voice_key = "粤"\r
-                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])\r
-                        self.AI_TTS_str = "了解,声音已切换"\r
-                        return 0\r
-                if text.find("四川") != -1:\r
-                    if "四川" in self.VC_TTS_voice_type:\r
-                        self.VC_TTS_voice_key = "四川"\r
-                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])\r
-                        self.AI_TTS_str = "了解,声音已切换"\r
-                        return 0\r
-                if text.find("个性") != -1:\r
-                    if "个性" in self.VC_TTS_voice_type:\r
-                        self.VC_TTS_voice_key = "个性"\r
-                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])\r
-                        self.AI_TTS_str = "了解,声音已切换"\r
-                        return 0\r
-                if text.find("英语") != -1:\r
-                    if "英语" in self.VC_TTS_voice_type:\r
-                        self.VC_TTS_voice_key = "英语"\r
-                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])\r
-                        self.AI_TTS_str = "了解,声音已切换"\r
-                        return 0\r
-                self.AI_TTS_str = "很抱歉,没找到对应声音"\r
-                return 0\r
-            ret = self._apply_vc_text_intent(desc, cmd, True)\r
-            if ret is not None:\r
-                return ret\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-        try:\r
-            self.AI_TTS_str = cmd["command"]["fail_prompt"]\r
-        except Exception:\r
-            self.AI_TTS_str = ""\r
-        return -1\r
-\r
-    def check_text(self, text):\r
-        print("check_text:len:{}".format(len(text)))\r
-        if len(text) > 60:\r
-            return -1\r
-        try:\r
-            if not isinstance(text, str):\r
-                return -1\r
-            ret = self._apply_vc_text_intent(text, None, False)\r
-            if ret is not None:\r
-                return ret\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-        return -1\r
-\r
-    #获取trace_id\r
-    def get_trace_id(self):\r
-        t = utime.mktime(utime.localtime())\r
-        t = t*1000+urandom.randint(0, 1000)\r
-        md5_data = "{}".format(t)#md5签名\r
-        sign_data = ""\r
-        hash_obj = uhashlib.md5(md5_data)\r
-        res = hash_obj.digest()\r
-\r
-        # 转成十六进制表示\r
-        sign_data = ubinascii.hexlify(res)\r
-        sign_data = sign_data.decode('ascii')\r
-        return sign_data\r
-    \r
-    def get_session_id(self,data = "session_id"):\r
-        t = utime.mktime(utime.localtime())\r
-        t = t*1000+urandom.randint(0, 1000)\r
-        md5_data = "{}{}".format(data,t)#md5签名\r
-        sign_data = ""\r
-        hash_obj = uhashlib.md5(md5_data)\r
-        res = hash_obj.digest()\r
-\r
-        # 转成十六进制表示\r
-        sign_data = ubinascii.hexlify(res)\r
-        sign_data = sign_data.decode('ascii')\r
-        return sign_data\r
-    \r
-    def get_new_message_id(self):\r
-        message_id = self.get_trace_id()+"aaa"+self.get_trace_id()\r
-        self.VC_message_id=message_id[:32]\r
-        return self.VC_message_id \r
-    def get_new_task_id(self):\r
-        task_id = self.get_trace_id()+"aaa"+self.get_trace_id()\r
-        self.VC_task_id=task_id[:32]\r
-        return self.VC_task_id\r
-    #开始播放声音\r
-    def VC_Play_Start(self):\r
-        try:\r
-            if self._ptt_should_skip_remote_playback():\r
-                if self._vc_led_state_debug_enabled():\r
-                    print("[VC] VC_Play_Start skip (ptt_interrupt)")\r
-                return\r
-        except Exception:\r
-            pass\r
-        self.tts_in_data_len = 0\r
-        self.tts_out_data_len = 0\r
-        self._tts_first_write_ms = 0\r
-        if self.VC_Play_flag == 0:\r
-            try:\r
-                _ptt_fast_open = False\r
-                try:\r
-                    if getattr(self, "VC_mode", -1) == 1:\r
-                        _ptt_fast_open = True\r
-                except Exception:\r
-                    pass\r
-                # 本地提示音/文件播放可能占用同一音频通道,导致 TTS 流播放“写入成功但无声”;\r
-                # 在开启 TTS PCM 前先停掉本地播放,避免资源冲突。\r
-                try:\r
-                    self.aud.stopPlayStream()\r
-                    self.aud.stopAll()\r
-                except Exception:\r
-                    pass\r
-                # 资源互斥:播放前确保录音上行已停止(无 AEC 场景)\r
-                try:\r
-                    if self.record_data_save_flag == 0 and self.VC_record_state_flag != 0:\r
-                        self._uplink_request_stop("VC_Play_Start")\r
-                        utime.sleep_ms(30)\r
-                except Exception:\r
-                    pass\r
-                if self.VC_play_TYPE == VC_play_TYPE_PCM:\r
-                    if self.play_pcm == None:\r
-                        # 刚停采集时延后打开播音 PCM,且失败时重试,降低「pcm open write only fail」\r
-                        last_err = None\r
-                        _open_retries = 1 if _ptt_fast_open else 3\r
-                        for i in range(_open_retries):\r
-                            try:\r
-                                if not _ptt_fast_open:\r
-                                    utime.sleep_ms(80 + i * 60)\r
-                                self.play_pcm = audio.Audio.PCM(0, 1, self.VC_play_samplerate, 1, 1, 2)\r
-                                ret = self.play_pcm.stream_write_start()\r
-                                print("self.record_pcm.stream_write_start:ret:{}".format(ret))\r
-                                last_err = None\r
-                                break\r
-                            except Exception as e:\r
-                                last_err = e\r
-                                try:\r
-                                    if self.play_pcm != None:\r
-                                        self.play_pcm.close()\r
-                                except Exception:\r
-                                    pass\r
-                                self.play_pcm = None\r
-                        if last_err:\r
-                            raise last_err\r
-                elif self.VC_play_TYPE == VC_play_TYPE_MP3:\r
-                    pass\r
-                self.VC_Play_flag = 1\r
-                try:\r
-                    if not self._ptt_should_skip_remote_playback():\r
-                        self._vc_publish_led("playing", "VC_Play_Start/tts")\r
-                except Exception:\r
-                    pass\r
-            except Exception as e:\r
-                self.VC_Play_End()\r
-                usys.print_exception(e)\r
-    #暂停/继续声音播放   Pause_flag = 1暂停\r
-    def VC_Play_Pause(self,Pause_flag):\r
-        try:\r
-            if self.VC_play_TYPE == VC_play_TYPE_PCM:\r
-                if self.play_pcm != None:    \r
-                    self.play_pcm.stream_pause(Pause_flag)\r
-        except Exception as e:\r
-            self.VC_Play_End()\r
-            usys.print_exception(e)\r
-    #停止播放声音\r
-    def VC_Play_End(self):\r
-        self.VC_Play_flag = 0\r
-        if self.VC_play_TYPE == VC_play_TYPE_PCM:\r
-            if self.VC_play_samplerate == 8000:\r
-                self.aud.stopPlayStream()\r
-                self.aud.stopAll()\r
-            elif self.VC_play_samplerate == 16000:\r
-                if self.play_pcm != None:\r
-                    print("Close:play_pcm.close()!!!!!!!!!!!!!!!!!!")\r
-                    try:\r
-                        self.play_pcm.stream_stop()\r
-                        self.play_pcm.close()\r
-                    except Exception as e:\r
-                        usys.print_exception(e)\r
-                    self.play_pcm=None\r
-                    \r
-                self.aud.stopPlayStream()\r
-                self.aud.stopAll()\r
-        elif self.VC_play_TYPE == VC_play_TYPE_MP3:\r
-            self.aud.stopPlayStream()\r
-            self.aud.stopAll()\r
-        if self.AI_audio_queue.empty()!=True:\r
-            self.AI_audio_queue.clear()\r
-\r
-        # TTS 播完:按真实 FSM/录音态恢复灯;本地短音不走此路径\r
-        try:\r
-            if getattr(self, "_vc_play_end_suppress_idle_led", 0):\r
-                if self._vc_led_state_debug_enabled():\r
-                    print("[LED] VC_Play_End skip led (suppress)")\r
-            elif getattr(self, "VC_record_state_flag", 0) != 0:\r
-                self._vc_publish_led("listening", "VC_Play_End/recording")\r
-            else:\r
-                self._vc_led_sync_from_fsm()\r
-        except Exception:\r
-            pass\r
-        # 关键日志:定位“tts 结束绿灯仍不灭”究竟是 base 态(如 charging/full)还是 playing 未退出\r
-        try:\r
-            if "vc_config" in globals() and vc_config and (\r
-                vc_config.get_bool("LED_DEBUG", False) or vc_config.get_bool("KEY_LED_DEBUG", False) or vc_config.get_bool("HW_STATUS_DEBUG", False)\r
-            ):\r
-                try:\r
-                    led = EventMesh.publish("get_led_debug_state")\r
-                except Exception:\r
-                    led = None\r
-                print("[VC] VC_Play_End led={}".format(led))\r
-        except Exception:\r
-            pass\r
-        \r
-\r
-    def _vc_tts_bytes_per_sec(self):\r
-        try:\r
-            return int(self.VC_play_samplerate) * 2\r
-        except Exception:\r
-            return 32000\r
-\r
-    def _vc_try_play_end_tick(self):\r
-        """tts.completed 后判定本地 PCM 是否真正播完,避免过早 VC_Play_End 截断 TTS。"""\r
-        try:\r
-            if not getattr(self.fsm, "await_played", False):\r
-                return False\r
-            in_len = getattr(self, "tts_in_data_len", 0)\r
-            out_len = getattr(self, "tts_out_data_len", 0)\r
-            try:\r
-                q_empty = self.AI_audio_queue.empty()\r
-            except Exception:\r
-                q_empty = True\r
-            tts_done_ms = getattr(self.fsm, "last_tts_completed_ms", 0)\r
-            if not (in_len > 0 and out_len >= in_len and q_empty and tts_done_ms):\r
-                return False\r
-\r
-            is_pcm_empty = (self.play_pcm is None) or (\r
-                self.play_pcm is not None and self.play_pcm.stream_is_empty() == 1\r
-            )\r
-            pcm_buf = 0\r
-            if self.play_pcm is not None:\r
-                try:\r
-                    pcm_buf = self.play_pcm.stream_get_buf_len()\r
-                except Exception:\r
-                    pcm_buf = -1\r
-\r
-            now = utime.ticks_ms()\r
-            bps = self._vc_tts_bytes_per_sec()\r
-            total_ms = int(out_len * 1000 // bps) if bps > 0 else 0\r
-            fw = getattr(self, "_tts_first_write_ms", 0) or tts_done_ms\r
-            try:\r
-                elapsed_at_done = utime.ticks_diff(tts_done_ms, fw)\r
-            except Exception:\r
-                elapsed_at_done = 0\r
-            if elapsed_at_done < 0:\r
-                elapsed_at_done = 0\r
-            remain_ms = max(400, total_ms - elapsed_at_done) if total_ms > 0 else 400\r
-            try:\r
-                since_done = utime.ticks_diff(now, tts_done_ms)\r
-            except Exception:\r
-                since_done = 0\r
-            tail_ok = since_done >= remain_ms\r
-            pcm_ok = is_pcm_empty and (pcm_buf == 0 or pcm_buf == -1)\r
-            too_long = since_done > max(remain_ms + 2000, 12000)\r
-\r
-            ready = (pcm_ok and tail_ok) or too_long\r
-            if not ready:\r
-                try:\r
-                    if not getattr(self, "_tts_play_check_log_ms", 0) or utime.ticks_diff(\r
-                        now, self._tts_play_check_log_ms\r
-                    ) >= 800:\r
-                        self._tts_play_check_log_ms = now\r
-                        print(\r
-                            "[TTS] play_end_wait since_done={}ms remain~{}ms total~{}ms "\r
-                            "in={} out={} q_empty={} pcm_buf={} pcm_empty={}".format(\r
-                                since_done, remain_ms, total_ms, in_len, out_len,\r
-                                1 if q_empty else 0, pcm_buf, 1 if is_pcm_empty else 0,\r
-                            )\r
-                        )\r
-                except Exception:\r
-                    pass\r
-                return False\r
-\r
-            try:\r
-                print(\r
-                    "[TTS] play_end_fire since_done={}ms remain~{}ms total~{}ms "\r
-                    "in={} out={} pcm_buf={} too_long={}".format(\r
-                        since_done, remain_ms, total_ms, in_len, out_len, pcm_buf, 1 if too_long else 0,\r
-                    )\r
-                )\r
-            except Exception:\r
-                pass\r
-            EventMesh.publish("publish_AI_btnInfo", {"func": self.VC_Play_End})\r
-            self._fsm_apply_actions(self.fsm.on_local_play_end())\r
-            try:\r
-                self.fsm.reset_rx_phases()\r
-            except Exception:\r
-                pass\r
-            self._tts_first_write_ms = 0\r
-            return True\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-            return False\r
-\r
-    def _1S_timer_cb(self,*argc):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self._1S_timer_cb_}) \r
-\r
-    def _fsm_timeout_table_tick(self, now_ms):\r
-        # 兜底统一入口:按“状态机状态 + 超时阈值 + 节流”驱动,避免兜底抢跑/多入口打架。\r
-        # 只在对应 turn/session 状态触发动作。\r
-        try:\r
-            turn = getattr(self.fsm, "turn", VCTurnState.Idle)\r
-        except Exception:\r
-            turn = VCTurnState.Idle\r
-\r
-        # 1) synthesis→tts.started 超时:只在 Thinking 期允许清 pending 并重启会话\r
-        #    根因:服务端未返回 tts.started 时,继续“回到聆听/开麦”会把本轮状态机拉扯得更乱;\r
-        #    这里直接重启会话,让协议重新进入 started→下一轮流程。\r
-        try:\r
-            if turn == VCTurnState.Thinking and self._tts_pending == 1 and self._tts_pending_ms:\r
-                try:\r
-                    if self._fb_last_tts_pending_timeout_ms:\r
-                        tts_gap_ok = utime.ticks_diff(now_ms, self._fb_last_tts_pending_timeout_ms) > 15000\r
-                    else:\r
-                        tts_gap_ok = True\r
-                except Exception:\r
-                    tts_gap_ok = True\r
-                if utime.ticks_diff(now_ms, self._tts_pending_ms) > 8000 and tts_gap_ok:\r
-                    self._fb_last_tts_pending_timeout_ms = now_ms\r
-                    print("[VC][tts_pending] timeout, clear pending and restart session")\r
-                    self._tts_pending = 0\r
-                    self._tts_pending_ms = 0\r
-                    if self.VC_mode == 0 and self.Close_flag == 0:\r
-                        try:\r
-                            self.fsm.reset_rx_phases()\r
-                        except Exception:\r
-                            pass\r
-                        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_VC_start})\r
-        except Exception:\r
-            pass\r
-\r
-        # 2) thinking 超时:只在「llm.completed 已收到但 tts.started 长时间未到」时才重启会话\r
-        #    注意:不能以 llm.created 的时间戳作为超时基准,否则会在 LLM 正常推理期间误判超时(notts 场景)。\r
-        try:\r
-            if self.VC_mode == 0 and self.Close_flag == 0:\r
-                llm_completed_ms = getattr(self, "_llm_completed_ms", 0)\r
-                # 只有在 llm.completed 之后才允许超时重启\r
-                if turn == VCTurnState.Thinking and llm_completed_ms and self._turn_has_tts_started == 0:\r
-                    age_ms = utime.ticks_diff(now_ms, llm_completed_ms)\r
-                    restart_threshold_ms = 20000\r
-                    # _fb_last_thinking_restart_ms==0 时 ticks_diff(now,0) 非预期(间隔被误判极大),\r
-                    # 会在 llm.completed 后首批 tick 就触发 AI_VC_start → notts(无 tts.started);首次仅信任 age 门限\r
-                    try:\r
-                        if self._fb_last_thinking_restart_ms:\r
-                            gap_ok = utime.ticks_diff(now_ms, self._fb_last_thinking_restart_ms) > 15000\r
-                        else:\r
-                            gap_ok = True\r
-                    except Exception:\r
-                        gap_ok = True\r
-                    if (age_ms > restart_threshold_ms and gap_ok and\r
-                        self._tts_pending == 0 and self._need_audio_played_ack == 0 and self.VC_Play_flag == 0):\r
-                        self._fb_last_thinking_restart_ms = now_ms\r
-                        self._llm_completed_ms = 0\r
-                        self._server_vad_silence = 0\r
-                        try:\r
-                            self.fsm.reset_rx_phases()\r
-                        except Exception:\r
-                            pass\r
-                        try:\r
-                            print("[VC][thinking_timeout] AI_VC_start age_ms={} thr={}".format(\r
-                                age_ms, restart_threshold_ms))\r
-                        except Exception:\r
-                            pass\r
-                        self._vc_led_alert_rb_flash()\r
-                        # thinking_timeout 策略:重启会话(而不是抢开麦),避免干扰服务端 LLM/TTS 流程\r
-                        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_VC_start})\r
-        except Exception:\r
-            pass\r
-\r
-    def _1S_timer_cb_(self):\r
-        try:\r
-            self._vc_tick_serial_cancel_timeout()\r
-        except Exception:\r
-            pass\r
-\r
-        EventMesh.publish("system_sleep_rest")#防止黑屏\r
-        # --- Level 1:仅长对话(VC_mode==0),无业务下行 idle_sec 后结束会话(需再次按键进入)\r
-        # close_timerout:保活清除策略见 _vc_rx_resets_long_idle_timer(排除文本 ping/pong、heartbeat 等)\r
-        # 与浅休眠/灭屏解耦;单对话不走本计时\r
-        idle_sec = 20\r
-        try:\r
-            if "vc_config" in globals() and vc_config:\r
-                idle_sec = int(vc_config.get_int("VC_IDLE_AUTO_CLOSE_SEC", 20))\r
-        except Exception:\r
-            idle_sec = 20\r
-        if idle_sec < 5:\r
-            idle_sec = 5\r
-        if self.VC_mode == 0 and self.VC_satrt_flag == 1 and getattr(self, "AI_client_VC_isconnect", 0) == 1:\r
-            self.close_timerout += 1\r
-        else:\r
-            self.close_timerout = 0\r
-        if self.VC_mode == 0 and idle_sec > 0 and self.close_timerout > idle_sec:\r
-            try:\r
-                print("[VC][idle_L1] 长对话无业务下行超时: elapsed={}s thr={}s VC_mode={} VC_satrt={} ws_conn={} {}".format(\r
-                    self.close_timerout, idle_sec, self.VC_mode,\r
-                    getattr(self, "VC_satrt_flag", -1), getattr(self, "AI_client_VC_isconnect", -1),\r
-                    self._vc_fsm_log_snip()))\r
-            except Exception:\r
-                pass\r
-            if self._vc_long_idle_active_speech_or_later():\r
-                try:\r
-                    print("[VC][idle_L1] 处理=websocket_error(对话中无服务端响应)")\r
-                except Exception:\r
-                    pass\r
-                print("[VC][long_idle] branch=speech_no_rsp -> websocket_error + ud_error")\r
-                print("接口无响应,超时退出")\r
-                EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":"接口超时了。请重试"}) \r
-                self.ud_error(["VC",self.VC_taskid])\r
-            else:\r
-                try:\r
-                    print("[VC][idle_L1] 处理=Close(结束对话,需再次按键进入)")\r
-                except Exception:\r
-                    pass\r
-                print("[VC][long_idle] branch=idle_timeout -> Close")\r
-                EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) \r
-                #EventMesh.publish("sleep_BT")#休眠蓝牙\r
-\r
-        # --- Level 2:长对话+单对话,业务 idle 累计 VC_IDLE_DEEP_SLEEP_SEC → 断开(若仍连)+ 深睡事件\r
-        deep_sec = 60\r
-        deep_en = 1\r
-        try:\r
-            if "vc_config" in globals() and vc_config:\r
-                deep_sec = int(vc_config.get_int("VC_IDLE_DEEP_SLEEP_SEC", 60))\r
-                deep_en = int(vc_config.get_int("VC_IDLE_DEEP_SLEEP_ENABLE", 1))\r
-        except Exception:\r
-            deep_sec, deep_en = 60, 1\r
-        active_deep = bool(deep_en) and deep_sec > 0\r
-        if active_deep and deep_sec < 10:\r
-            deep_sec = 10\r
-        if not active_deep:\r
-            self._idle_deep_sec = 0\r
-        elif not self._vc_is_business_idle():\r
-            self._idle_deep_sec = 0\r
-            try:\r
-                self._vc_idle_deep_armed_suppress = 0\r
-            except Exception:\r
-                pass\r
-        elif getattr(self, "_vc_idle_deep_armed_suppress", 0):\r
-            pass\r
-        else:\r
-            self._idle_deep_sec += 1\r
-        if active_deep and self._idle_deep_sec > deep_sec:\r
-            try:\r
-                print("[VC][idle_L2] 业务idle累计超时: elapsed={}s thr={}s VC_mode={} VC_satrt={} ws_conn={}".format(\r
-                    self._idle_deep_sec, deep_sec, self.VC_mode,\r
-                    getattr(self, "VC_satrt_flag", -1), getattr(self, "AI_client_VC_isconnect", -1)))\r
-            except Exception:\r
-                pass\r
-            try:\r
-                self._vc_idle_deep_armed_suppress = 1\r
-            except Exception:\r
-                pass\r
-            self._idle_deep_sec = 0\r
-            EventMesh.publish("publish_AI_btnInfo", {"func": self._vc_idle_deep_sleep_impl})\r
-\r
-        # 统一兜底表(按状态机状态触发)\r
-        try:\r
-            self._fsm_timeout_table_tick(utime.ticks_ms())\r
-        except Exception:\r
-            pass\r
-\r
-        # (不做额外兜底)动态切换依赖 FSM 状态正确清理后,由 server.tts.completed→local.play_end 驱动自动开麦\r
-\r
-        # print("get_audio_state !!!!!{} {} {}\r\n".format(self.play_end,EventMesh.publish("get_audio_state"),self.play_flag))\r
-        # ===== 播放结束检测:用 FSM 驱动 play_end 动作链 =====\r
-        try:\r
-            self._vc_try_play_end_tick()\r
-        except Exception:\r
-            pass\r
-        if self.online_kws == 1:\r
-            if self.VC_satrt_kws_flag == 1 and self.VC_satrt_flag == 0:#要打开录音上传数据实现kws\r
-                pass\r
-\r
-        # thinking/llm→tts 等兜底已在 _fsm_timeout_table_tick 内统一处理\r
-        # VC WebSocket 文本 ping:间隔由 default_config 的 VC_WS_PING_INTERVAL_SEC 控制(秒),<=0 表示关闭 ping\r
-        try:\r
-            _ping_iv = int(vc_config.get_int("VC_WS_PING_INTERVAL_SEC", 4))\r
-        except Exception:\r
-            _ping_iv = 4\r
-        if _ping_iv > 0:\r
-            self.VC_pingping_timerout += 1\r
-            if self.VC_pingping_timerout >= _ping_iv:\r
-                self.VC_pingping_timerout = 0\r
-                self.AI_client_VC_ping()\r
-        # self.asr_pingping_timerout += 1\r
-        # if self.asr_pingping_timerout>3:\r
-        #     self.asr_pingping_timerout=0\r
-        #     self.AI_client_asr_ping()    \r
-        # 历史遗留:VC_Thinking_timerout 的重启通道会在 llm.created 后抢跑触发 AI_VC_start,\r
-        # 直接打断本轮 LLM→TTS(notts 日志:llm.created 后立刻 AI_VC_start,导致无 tts.started)。\r
-        # 现在统一由 _fsm_timeout_table_tick(Thinking 超时) 负责重启会话,因此禁用该旧通道。\r
-        if self.VC_Thinking_timerout > 0 and self.VC_Thinking_timerout < 100:\r
-            self.VC_Thinking_timerout = 100\r
-    # def AI_kws_start(self):    \r
-    #     print("AI_kws_start")\r
-    #     EventMesh.publish("wakeup_BT")\r
-    #     EventMesh.publish("system_sleep_rest")#防止黑屏\r
-    #     url="ws://118.145.158.207:22080/wakeup/ws"\r
-    #     self.AI_client_kws = uwebsocket.Client.connect(url, debug=True)\r
-    #     self.AI_client_VC_recv_id = _thread.start_new_thread(self.AI_client_VC_recv, (self.AI_client_VC,))\r
-    #     self.VC_Speech_Start()#开启线程获取录音数据\r
-    #VC开始\r
-    def AI_VC_start(self):\r
-        print("AI_VC_start1")\r
-        \r
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
-        self.VC_satrt_flag = 1\r
-        self.VC_stop_play_audio_flag = 1\r
-        self.VC_satrt_kws_flag = 1\r
-        if self.VC_Thinking_timerout < 100:\r
-            self.VC_Thinking_timerout = 0\r
-        # 不在这里提前切灯态:播放/聆听灯只在“播放/录音真正开始/停止”处更新\r
-        EventMesh.publish("wakeup_BT")\r
-        #self.VC_Play_End()#关闭音频播放\r
-        print("AI_VC_start2")\r
-        #self.aud.aud_tone_play(16,200)#播放按键音\r
-        self.LocalRespondingEnded_timerout = 0\r
-        self.close_timerout = 0\r
-        self._idle_deep_sec = 0\r
-        self._vc_idle_deep_armed_suppress = 0\r
-        \r
-        #\r
-        if self.AI_client_VC_isconnect == 0:#没连接的时候请求开始\r
-            self.websocket_connect_VC()#连接服务器\r
-            \r
-            #utime.sleep_ms(50)\r
-        else:\r
-            print("AI_VC_start2 (already connected) {}".format(self._vc_fsm_log_snip()))\r
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
-        if self.AI_client_VC_isconnect == 0:\r
-            #self.Close()\r
-            return\r
-        # 单对话 PTT:会话已 started 时勿重复发 Start(否则第二轮起上行/ASR 易乱)\r
-        _ptt_skip_start = False\r
-        try:\r
-            if self.VC_mode == 1 and getattr(self, "_vc_session_started", 0) == 1:\r
-                _ptt_skip_start = True\r
-                print("[PTT] AI_VC_start skip duplicate Start (session active)")\r
-        except Exception:\r
-            pass\r
-        if not _ptt_skip_start:\r
-            self.AI_client_VC_start_requset()\r
-        print("AI_VC_start3")\r
-         \r
-        if (not _ptt_skip_start) and self.AI_TTS_str == "" and self.AI_new_voice_url == "":\r
-            self._uplink_request_start("AI_VC_start")\r
-           \r
-                \r
-        #     pass\r
-        print("AI_VC_start4")\r
-\r
-        #self.VC_Play_Start()\r
-        #EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Play_Start})\r
-        #开始线程\r
-        \r
-        # if self.AI_VC_send_data_queue_thread_id == 0:    \r
-        #     _thread.stack_size(self.Audio_buf_size+8*1024)\r
-        #     self.AI_VC_send_data_queue_thread_id = _thread.start_new_thread(self.AI_VC_send_data_queue_thread, ())\r
-        #     _thread.stack_size(8*1024)\r
-        #     print("AI_VC_start :AI_VC_send_data_queue_thread_id :{} ".format(self.AI_VC_send_data_queue_thread_id))\r
-        \r
-        if self.AI_audio_queue_thread_id ==0:\r
-            _thread.stack_size(24*1024)\r
-            self.AI_audio_queue_thread_id = _thread.start_new_thread(self.AI_audio_queue_thread, ())\r
-            _thread.stack_size(8*1024)\r
-            print("AI_VC_start :AI_audio_queue_thread_id :{} ".format(self.AI_audio_queue_thread_id))\r
-        else:\r
-            print("AI_VC_start :AI_audio_queue_thread_id != 0 ")\r
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
-        self._1S_timer.start(1000,1,self._1S_timer_cb)\r
-        # self.VC_pingping_timerout = 0\r
-        # self.asr_pingping_timerout = 0\r
-        print("AI_VC_start5")\r
-    #开始会话\r
-    def AI_client_VC_start_requset(self):\r
-        voice="zh_male_wennuanahu_moon_bigtts"\r
-        try:\r
-            voice = self.VC_TTS_voice_type[self.VC_TTS_voice_key]["voice"]\r
-        except Exception as e:\r
-            usys.print_exception(e)    \r
-        #voice="zh_female_dongmanxiaoyang_moon_bigtts"\r
-        token = EventMesh.publish("get_dev_token")\r
-        if token ==None:\r
-            token = ""\r
-        # bot_option:优先从 default_config(usr 可覆盖)读取 VC_BOT_ID / VC_WORKFLOW_ID,允许空字符串\r
-        _def_bot_id = "7462617881076351002"\r
-        _def_workflow_id = "7462621757327196187"\r
-        try:\r
-            if "vc_config" in globals() and vc_config:\r
-                _vc_bot_id = vc_config.get_attr("VC_BOT_ID", _def_bot_id)\r
-                _vc_workflow_id = vc_config.get_attr("VC_WORKFLOW_ID", _def_workflow_id)\r
-                if _vc_bot_id is None:\r
-                    _vc_bot_id = ""\r
-                if _vc_workflow_id is None:\r
-                    _vc_workflow_id = ""\r
-            else:\r
-                _vc_bot_id, _vc_workflow_id = _def_bot_id, _def_workflow_id\r
-        except Exception:\r
-            _vc_bot_id, _vc_workflow_id = _def_bot_id, _def_workflow_id\r
-        json_data = \\r
-        {\r
-            "header": {\r
-                "event": "Start",\r
-                "trace_id": self.get_trace_id(),\r
-            },\r
-            "payload": {\r
-                "simple_response": True,#不返回llm过程文本\r
-                "device_id": modem.getDevImei(),\r
-                \r
-                "user_agent": "AudioClient/1.0",\r
-                "dialog_attributes": {\r
-                    #"provider": "doubao",\r
-                    # "provider": "coze",\r
-\r
-                    "cfg": {\r
-                        "disable_empty_prompt" : True,#拦截空字符请求大模型\r
-                        "custom_variables": {\r
-                            "name": self.bot_name,\r
-                            "devToken":token,\r
-                            },\r
-                        "greeting":{\r
-                            "enabled":False,\r
-                            "content":""\r
-                        },\r
-                        "openai_option":{\r
-                           },\r
-\r
-                        "bot_option": {\r
-                            "use_workflow": True,\r
-                            #"input_format": "text",\r
-                            "bot_id": _vc_bot_id,\r
-                            "workflow_id": _vc_workflow_id,\r
-                           \r
-                        }\r
-                    }\r
-                },\r
-                "outbound_attributes": {\r
-                    "provider": "volcengine",\r
-                    "cfg": {\r
-                        "format": "pcm",\r
-                        "sample_rate": self.VC_play_samplerate,\r
-                        "bit_rate": 16,\r
-                        "channel": 1,\r
-                        "volume": self.VC_TTS_volume,\r
-                        "emotion": self.emotion,\r
-                        #"voice": "7426720361732980745",\r
-                        "voice": voice,#音色\r
-                        "speech_rate": self.speech_rate,#语速\r
-                        "chunk_interval_ms": 10,  # 配置每段音频入队间隔时间 100ms\r
-                    }\r
-                },\r
-                "inbound_attributes": {\r
-                    # "provider": "xun-fei",#\r
-                    "provider": "volcengine",\r
-                    "disable_vad":True,\r
-                    "vad_silence":self.vad_silence,\r
-                    "interrupt_options":{\r
-                        "concatenate_results":True,\r
-                        "thinking_time_only": True,\r
-                        #"keyword_interrupt": false, // 启用打断词功能 (打断词只有在思考以及之后才能打断)\r
-                        #"interrupt_keywords": ["停止", "打断","打断一下", "等一下", "别说了", "打住"] // 配置打断词列表\r
-                    },\r
-                    "cfg": {\r
-                        "format": "pcm",\r
-                        "sample_rate": self.VC_record_samplerate,\r
-                        "bit_rate": 16,\r
-                        "channel": 1,\r
-                        "enable_punctuation": True,\r
-                        "enable_itn":True,\r
-                        "keywords":[]\r
-                    }\r
-                },\r
-                "commands":self.commands,\r
-            }\r
-        }\r
-        # "bot_id":"7481164663863459850",#鸭子\r
-        # "workflow_id":"7480485093845991443",\r
-        # "bot_id":"7462617881076351002",#语言助手\r
-        # "workflow_id":"7462621757327196187",\r
-        # "bot_id":"7469964258592931852",#深圳百事通\r
-        # "workflow_id":"7469968083063603241",\r
-        # "bot_id":"7411071327182438412",#柒牌\r
-        # "workflow_id":"7469049680291610636",\r
-        # if self.online_kws == 1:#打开\r
-        #     json_data["payload"]["inbound_attributes"]["interrupt_options"]["keyword_interrupt"] = True\r
-        #     json_data["payload"]["inbound_attributes"]["interrupt_options"]["interrupt_keywords"] = ["你好,柒先生","你好"]\r
-        #如果\r
-        json_data["payload"]["outbound_attributes"]["provider"]=self.outbound_attributess_provider#TTS音色服务选择,火山\r
-        json_data["payload"]["inbound_attributes"]["provider"]=self.inbound_attributess_provider#ASR服务选择,火山\r
-        #优先选择self.VC_TTS_voice_type[self.VC_TTS_voice_key]里得参数\r
-        # if "inbound_attributess_provider" in self.VC_TTS_voice_type[self.VC_TTS_voice_key]:\r
-        #     if len(self.VC_TTS_voice_type[self.VC_TTS_voice_key]["inbound_attributess_provider"])>0:\r
-        #         json_data["payload"]["inbound_attributes"]["provider"]=self.VC_TTS_voice_type[self.VC_TTS_voice_key]["inbound_attributess_provider"]\r
-        #         print("inbound_attributess_provider是VC_TTS_voice_type里的:{}".format(self.VC_TTS_voice_type[self.VC_TTS_voice_key]["inbound_attributess_provider"]))\r
-        if "outbound_attributess_provider" in self.VC_TTS_voice_type[self.VC_TTS_voice_key]:\r
-            if len(self.VC_TTS_voice_type[self.VC_TTS_voice_key]["outbound_attributess_provider"])>0:\r
-                json_data["payload"]["outbound_attributes"]["provider"]=self.VC_TTS_voice_type[self.VC_TTS_voice_key]["outbound_attributess_provider"]\r
-                print("outbound_attributess_provider是VC_TTS_voice_type里的:{}".format(self.VC_TTS_voice_type[self.VC_TTS_voice_key]["outbound_attributess_provider"]))\r
-\r
-        # 根据配置设置默认 bot/workflow,并允许 VC_TTS_voice_type 覆盖\r
-        try:\r
-            if "vc_config" in globals() and vc_config:\r
-                opt = vc_config.get_attr("DIALOG_BOT_OPTION", None)\r
-                if isinstance(opt, dict):\r
-                    if "bot_id" in opt:\r
-                        json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]["bot_id"] = opt.get("bot_id") or ""\r
-                    if "workflow_id" in opt:\r
-                        json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]["workflow_id"] = opt.get("workflow_id") or ""\r
-                    if "use_workflow" in opt:\r
-                        json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]["use_workflow"] = bool(opt.get("use_workflow"))\r
-        except Exception:\r
-            pass\r
-\r
-        try:\r
-            # voice 配置可覆盖 bot_id/workflow_id/openai_option\r
-            cur = self.VC_TTS_voice_type[self.VC_TTS_voice_key]\r
-            if isinstance(cur, dict):\r
-                if cur.get("bot_id"):\r
-                    json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]["bot_id"] = cur.get("bot_id")\r
-                if "workflow_id" in cur:\r
-                    json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]["workflow_id"] = cur.get("workflow_id") or ""\r
-                if cur.get("openai_option"):\r
-                    json_data["payload"]["dialog_attributes"]["cfg"]["openai_option"] = cur.get("openai_option")\r
-        except Exception:\r
-            pass\r
-\r
-        try:\r
-            _bo = json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]\r
-            if not _bo.get("bot_id", "") or not _bo.get("workflow_id", ""):\r
-                _bo["use_workflow"] = False\r
-        except Exception:\r
-            pass\r
-        try:\r
-            if "vc_config" in globals() and vc_config:\r
-                p = vc_config.get_attr("DIALOG_PROVIDER_DEFAULT", None)\r
-                if p:\r
-                    json_data["payload"]["dialog_attributes"]["provider"] = p\r
-        except Exception:\r
-            pass\r
-\r
-        try:\r
-            if "vc_config" in globals() and vc_config and vc_config.get_bool("KEYWORDS_FROM_VOICE_TYPE", False):\r
-                keywords = []\r
-                for key in self.VC_TTS_voice_type:\r
-                    keywords.append(key)\r
-                json_data["payload"]["inbound_attributes"]["cfg"]["keywords"] = keywords\r
-        except Exception:\r
-            pass\r
-        if self.dialog_attributes_provider != None:\r
-            json_data["payload"]["dialog_attributes"]["provider"] = self.dialog_attributes_provider\r
-        if self.commands_mode !=0 :\r
-            json_data["payload"]["commands"] = []\r
-        if self.VC_mode == 0 :#and dev_type != "7PIN_BK_coze":#长对话模式打开vad\r
-            json_data["payload"]["inbound_attributes"]["disable_vad"] = False\r
-        # 新会话开始:在收到服务端 started 前,禁止发送音频帧(避免 session not initialized)\r
-        self._vc_session_started = 0\r
-        try:\r
-            self.fsm.on_session_start_request()\r
-        except Exception:\r
-            pass\r
-        self.AI_client_VC_send(ujson.dumps(json_data))\r
-        try:\r
-            _tid = json_data.get("header", {}).get("trace_id", "")\r
-            print("[VC][proto] Start enqueued trace_id={} VC_mode={} disable_vad={} ws_conn={}".format(\r
-                _tid or "-",\r
-                self.VC_mode,\r
-                json_data.get("payload", {}).get("inbound_attributes", {}).get("disable_vad", "?"),\r
-                self.AI_client_VC_isconnect))\r
-        except Exception:\r
-            pass\r
- \r
-    #开始上传语音\r
-    def AI_client_VC_send_speech(self):\r
-        json_data = \\r
-        {\r
-            "header": {\r
-                "event": "audio.create",\r
-                \r
-            },\r
-        }\r
-        \r
-        if self.VC_mode == 1:#单对话模式\r
-            # 协议门控(可用 VC_PROTO_GUARDS 临时关闭)\r
-            if (not VC_PROTO_GUARDS) or (self._vc_session_started == 1):\r
-                self._ptt_audio_created = 1\r
-                try:\r
-                    print("[VC][proto] audio.create send session_started={} proto_guards={}".format(\r
-                        self._vc_session_started, VC_PROTO_GUARDS))\r
-                except Exception:\r
-                    pass\r
-                self.AI_client_VC_send(ujson.dumps(json_data))\r
-            else:\r
-                try:\r
-                    print("[VC][proto] audio.create SKIP session_started={} VC_PROTO_GUARDS={} "\r
-                          "(等对端 started 后再开麦)".format(self._vc_session_started, VC_PROTO_GUARDS))\r
-                except Exception:\r
-                    pass\r
-        \r
-\r
-            \r
-            \r
-        \r
-        \r
-        try:\r
-            self.fsm.on_server_asr_started()\r
-        except Exception:\r
-            pass\r
-    #结束上传语音\r
-    def AI_client_VC_end_speech(self):\r
-        json_data = \\r
-        {\r
-            "header": {\r
-                "event": "audio.completed",\r
-                \r
-            },\r
-        }\r
-        self.AI_client_VC_send(ujson.dumps(json_data))\r
-        self._ptt_audio_created = 0\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self._uplink_request_stop, "data":"audio.completed"})\r
-\r
-  \r
-    #客户端打断对话\r
-    def AI_client_VC_RequestToSpeak(self):\r
-        json_data = \\r
-        {\r
-            "event": "cancel",\r
-            "payload": {}\r
-        }\r
-        print("AI_client_VC_RequestToSpeak!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")\r
-        # 模式切换等场景 play_tts(..., is_close_VC=1) 会置 Close_flag=1;用户再按 AI 键走 cancel→canceled→\r
-        # uplink_start 时,FSM 与 _uplink_request_start 均会因 Close_flag 拒绝开麦,表现为“切回长对话后按键无上行”。\r
-        try:\r
-            if self.VC_mode == 0:\r
-                self.Close_flag = 0\r
-                self._vc_pending_interrupt_uplink = 1\r
-                self._vc_clear_stale_record_state("RequestToSpeak")\r
-        except Exception:\r
-            pass\r
-        # 只做“打断+清播放队列”,不要在这里关音频队列线程(会导致后续 TTS/提示音不稳定,且触发竞态)\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_init})\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_send,"data":ujson.dumps(json_data)})\r
-        utime.sleep_ms(1)\r
-        try:\r
-            self.fsm.reset_rx_phases()\r
-        except Exception:\r
-            pass\r
-        # #self.Close()\r
-        # self.AI_VC_start() \r
-    #TTS播放\r
-    def AI_client_VC_synthesis(self):\r
-        # 幂等保护:在上一条 synthesis 还未等到 tts.started 前,不重复发(防止多兜底/多入口重复触发)\r
-        try:\r
-            if self._tts_pending == 1:\r
-                return\r
-        except Exception:\r
-            pass\r
-        json_data = \\r
-        {\r
-            "event": "synthesis",\r
-            "payload": {\r
-                "text": self.AI_TTS_str\r
-            }\r
-        }\r
-        # 语义标记只用于“是否自动开麦”的策略。\r
-        # 当前策略:长对话不自动开麦(需按键开始),因此这里不对提示音做自动开麦标记。\r
-        self.AI_TTS_str = ""\r
-        # 标记为“等待 tts.started”,避免在 synthesis→tts.started 窗口误开下一轮录音上行\r
-        self._tts_pending = 1\r
-        try:\r
-            self._tts_pending_ms = utime.ticks_ms()\r
-        except Exception:\r
-            self._tts_pending_ms = 0\r
-        try:\r
-            self.fsm.on_local_synthesis_sent()\r
-        except Exception:\r
-            pass\r
-        print("AI_client_VC_synthesis!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")\r
-        # if self.VC_state == VC_State_RequestToRespond:\r
-        #     print("AI_client_VC_RequestToRespond 重复 tts_str:{}\nasr_str:{}".format(self.AI_TTS_str,self.AI_ASR_str))\r
-        self.AI_client_VC_send(ujson.dumps(json_data))\r
-    # 金三 V2.5:AudioPlayed — 端侧 TTS 播报结束后上报,服务端进入下一轮对话(轻度重同步,不断开 WebSocket)\r
-    def AI_client_VC_audio_played(self):\r
-        # V2.5:播完上报;与 VC_PROTO_GUARDS 无关(见文件头说明)\r
-        try:\r
-            if self.AI_client_VC_isconnect != 1 or self.AI_client_VC == None:\r
-                return\r
-            json_data = {"event": "audio.played", "payload": {}}\r
-            print("AI_client_VC_audio_played")\r
-            self.AI_client_VC_send(ujson.dumps(json_data))\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-    def kws_even(self,event = None,msg= None):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.kws_even_cb}) \r
-    def kws_even_cb(self):\r
-        if EventMesh.publish("get_sos_flag") == 1:\r
-            return\r
-        print("kws_even_cb")\r
-        if self.VC_mode == 1: \r
-            try:\r
-                tts_str = vc_config.get_attr("KWS_PTT_HINT", "请您按住圆形的按钮开始讲话,讲完松开即可 ")\r
-            except Exception:\r
-                tts_str = "请您按住圆形的按钮开始讲话,讲完松开即可 "\r
-            EventMesh.publish("play_tts",[tts_str,1]) \r
-            return\r
-        if self.VC_mode == 0:\r
-            if EventMesh.publish("get_voice_record_flag") == 1:#停止静默录音\r
-                if EventMesh.publish("get_voice_record_mode") == 2:#语音留言不能被打断\r
-                    return\r
-                elif  EventMesh.publish("get_voice_record_mode") == 1: #静默录音需要打断\r
-                    EventMesh.publish("stop_voice_record")#停止静默录音\r
-            self.aud.aud_tone_play(10,400)\r
-            # EventMesh.publish("audio_montage_play",["chenggong.mp3"])  \r
-            self.Close()\r
-            # if dev_type == "7PIN" :\r
-            #     self.aud.aud_tone_play(10,400)#播放按键音\r
-            #self.aud.aud_tone_play(10,200)#播放按键音\r
-            self.AI_TTS_str = ""#"你好,有什么吩咐"\r
-            self.AI_ASR_str = ""\r
-            self.AI_ASR_str_tmp = ""\r
-            self._uplink_request_stop("kws_even_cb")\r
-            self._uplink_request_start("kws_even_cb")\r
-            self.AI_VC_start() \r
-\r
-    def set_vad_silence(self,event = None,msg= None):\r
-        self.vad_silence = msg\r
-        EventMesh.publish("set_storage_config",["vad_silence",self.vad_silence])  \r
-        # EventMesh.publish("publish_AI_btnInfo",{"func":self.play_new_voice_cb,"data":msg}) \r
-        #设置tts参数\r
-    def set_wait_first_flag(self,event = None,msg= None):#设置未读语音播报初次记录flag\r
-        self.wait_first_flag = msg\r
-    def set_VC_TTS_voice_type(self,event = None,msg= None):\r
-        self.VC_TTS_voice_type["APP_voice"] = msg\r
-        #self.VC_TTS_voice_type[self.VC_TTS_voice_key]\r
-        self.VC_TTS_voice_key = "APP_voice"\r
-        EventMesh.publish("set_storage_config",["VC_TTS_voice_type",self.VC_TTS_voice_type])\r
-        EventMesh.publish("set_storage_config",["VC_TTS_voice_key",self.VC_TTS_voice_key])\r
-        print("[VC] TTS_voice key={} map={}".format(self.VC_TTS_voice_key, self.VC_TTS_voice_type))\r
-    #修改音量大小 msg为1则为加大,msg为0则为减小,返回1成功,返回0失败\r
-    def change_volume(self,event =None, msg =None):\r
-        if msg == 0 :\r
-            self.VC_TTS_volume -= 10\r
-            if self.VC_TTS_volume < 0:\r
-                self.VC_TTS_volume = 0\r
-                return 0\r
-            else:\r
-                self.aud.setVolume(self.VC_TTS_volume //10)\r
-                EventMesh.publish("set_storage_config",["VC_TTS_volume",self.VC_TTS_volume])\r
-                EventMesh.publish("post_device_state")\r
-                EventMesh.publish("adjust_volume_toBle",self.VC_TTS_volume)\r
-                return 1\r
-        elif msg == 1:\r
-            self.VC_TTS_volume += 10\r
-            if self.VC_TTS_volume > 100:\r
-                self.VC_TTS_volume = 100\r
-                return 0\r
-            else:\r
-                self.aud.setVolume(self.VC_TTS_volume //10)\r
-                EventMesh.publish("set_storage_config",["VC_TTS_volume",self.VC_TTS_volume])\r
-                EventMesh.publish("post_device_state")\r
-                EventMesh.publish("adjust_volume_toBle",self.VC_TTS_volume)\r
-                return 1\r
-        elif msg >= 100:#修改为具体音量\r
-            if msg > 200:\r
-                self.VC_TTS_volume = 100\r
-                return 0\r
-            else:\r
-                self.VC_TTS_volume = msg - 100#还原数据\r
-                self.aud.setVolume(self.VC_TTS_volume //10)\r
-                EventMesh.publish("set_storage_config",["VC_TTS_volume",self.VC_TTS_volume])\r
-                EventMesh.publish("post_device_state")\r
-                EventMesh.publish("adjust_volume_toBle",self.VC_TTS_volume)\r
-                return 1\r
-    #调节聆听快慢(以秒为单位)\r
-    def NLP_set_vad_silence(self,event,msg):\r
-        if msg == 1:#聆听变快\r
-            self.vad_silence -= 1000\r
-            if self.vad_silence < 1000:\r
-                AI_TTS_str = "已经是最快了" \r
-                self.vad_silence = 1000\r
-            else:\r
-                AI_TTS_str = "配置成功,当前语音断句速度是{}秒".format(self.vad_silence/1000)\r
-        elif msg == 0:#聆听变慢\r
-            self.vad_silence += 1000\r
-            if self.vad_silence > 4000:\r
-                self.vad_silence = 4000\r
-                #EventMesh.publish("audio_montage_play",["yinliangyizuixiao.mp3"])\r
-                #self.aud.play(1,0,'U:/static/yinliangyizuixiao.mp3')#音量已最小\r
-                AI_TTS_str = "已经是最慢了"\r
-            else:\r
-                AI_TTS_str = "配置成功,当前语音断句速度是{}秒".format(self.vad_silence/1000)        \r
-        EventMesh.publish("set_storage_config",["vad_silence",self.vad_silence])\r
-        return AI_TTS_str\r
-    #查询指令判断\r
-    def find_cmd(self,event ,msg):\r
-        if "电量" in msg:\r
-            battery = EventMesh.publish("get_battery")\r
-            print("电量查询:{}".format(battery))\r
-            tts_play_str = "电量剩余百分之{}".format(battery)\r
-            EventMesh.publish("play_tts",[tts_play_str,0])\r
-            # print("电量!!!!!!!!!!!")\r
-        elif "信号" in msg:\r
-            signal = net.csqQueryPoll()\r
-            tts_play_str = "当前信号{}".format(signal)\r
-            EventMesh.publish("play_tts",[tts_play_str,0])\r
-            # print("信号!!!!!!!!!!!")\r
-        elif "卡号" in msg:\r
-            # print("卡号!!!!!!!!!!!")\r
-            iccid = sim.getIccid()\r
-            iccid = num_str_to_chinese(iccid)#转为中文\r
-            if iccid != -1 and len(iccid) > 5:\r
-                print("iccid{}".format(iccid))\r
-                tts_play_str = "当前卡号为:{}".format(iccid)\r
-            else:\r
-                tts_play_str = "卡号查询失败"\r
-            EventMesh.publish("play_tts",[tts_play_str,0])\r
-        elif "设备号" in msg:\r
-            imei = modem.getDevImei()\r
-            if imei != -1 and len(imei) > 5:\r
-                print("IMEI:{}".format(imei))\r
-                tts_play_str = "当前设备号为:{}".format(imei)\r
-            else:\r
-                tts_play_str = "设备号查询失败"\r
-            EventMesh.publish("play_tts",[tts_play_str,0])\r
-        elif "音量" in msg :\r
-            tts_play_str = "当前音量是{}".format(self.VC_TTS_volume)\r
-            EventMesh.publish("play_tts",[tts_play_str,0])\r
-        elif "语速" in msg :\r
-            tts_play_str = "当前语速是{}".format(EventMesh.publish("find_rate"))\r
-            EventMesh.publish("play_tts",[tts_play_str,0])\r
-    \r
-    #改变音色判断\r
-    def change_voice(self,event ,msg):\r
-        voice = msg\r
-        # if "男声" in msg or "普通话" in msg:\r
-        #     self.VC_TTS_voice_key = "男"\r
-        # else:\r
-        #     self.VC_TTS_voice_key = voice\r
-        self.outbound_attributess_provider = "xun-fei"\r
-        if "男声" in msg or "普通话" in msg:\r
-            self.VC_TTS_voice_key = "男"\r
-            self.outbound_attributess_provider = "volcengine"\r
-        elif "女声" in msg:\r
-            self.VC_TTS_voice_key = "女"\r
-            self.outbound_attributess_provider = "volcengine"\r
-        elif "童声" in msg:\r
-            self.VC_TTS_voice_key = "童"    \r
-            self.outbound_attributess_provider = "volcengine"\r
-        elif "香港粤语" in msg:\r
-            self.VC_TTS_voice_key = "香港粤语"\r
-        elif "四川话" in msg:\r
-            self.VC_TTS_voice_key = "四川"    \r
-        elif "上海话" in msg:\r
-            self.VC_TTS_voice_key = "上海" \r
-        elif "安徽合肥话" in msg:\r
-            self.VC_TTS_voice_key = "安徽合肥"    \r
-        elif "山东话" in msg:\r
-            self.VC_TTS_voice_key = "山东话" \r
-        elif "陕西话" in msg:\r
-            self.VC_TTS_voice_key = "陕西"    \r
-        elif "广东话" in msg:\r
-            self.VC_TTS_voice_key = "广东" \r
-        elif "成都话" in msg:\r
-            self.VC_TTS_voice_key = "成都"    \r
-        elif "湖南话" in msg:\r
-            self.VC_TTS_voice_key = "湖南" \r
-        elif "苏州话" in msg:\r
-            self.VC_TTS_voice_key = "苏州"    \r
-        elif "东北话" in msg:\r
-            self.VC_TTS_voice_key = "东北" \r
-        elif "河南话" in msg:\r
-            self.VC_TTS_voice_key = "河南"    \r
-        elif "湖北话" in msg:\r
-            self.VC_TTS_voice_key = "湖北" \r
-        elif "闽南话" in msg:\r
-            self.VC_TTS_voice_key = "闽南"    \r
-        elif "内蒙古话" in msg:\r
-            self.VC_TTS_voice_key = "内蒙古话" \r
-        EventMesh.publish("set_storage_config",["VC_TTS_voice_key",self.VC_TTS_voice_key])\r
-        tts_play_str = "声音已改为{}".format(voice)\r
-        EventMesh.publish("play_tts",[tts_play_str,0])\r
-\r
-    #闹钟的url播放接口\r
-    def play_single_voice(self,event = None,msg= None):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_single_voice_cb,"data":msg}) \r
-    def play_single_voice_cb(self,data):\r
-        try:\r
-            new_voice_url = data[0] #音频地址\r
-            is_close_VC = data[1]\r
-            if new_voice_url != "":\r
-                self.AI_new_voice_url = new_voice_url\r
-                self.alarm_play_flag = 1\r
-            if is_close_VC != None:\r
-                self.Close_flag = is_close_VC\r
-            if self.AI_VC_send_data_queue.empty()!=True:\r
-                self.AI_VC_send_data_queue.clear()\r
-            if self.AI_ASR_send_data_queue.empty()!=True:\r
-                self.AI_ASR_send_data_queue.clear()\r
-            EventMesh.publish("set_led_state", {"ai_state": "thinking"})\r
-            self.AI_ASR_send_buf_len = 0\r
-            self._uplink_request_stop("play_single_voice_cb")\r
-            self.websocket_deconnect_VC()\r
-            self.AI_VC_start()\r
-            self.play_new_voice_flag = 1\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) \r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) \r
-        except Exception as e:\r
-                usys.print_exception(e)   \r
-        pass       \r
-    #语音留言\r
-    def play_new_voice(self,event = None,msg= None):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_new_voice_cb,"data":msg}) \r
-    def play_new_voice_cb(self,data):\r
-        try:\r
-            new_voice_url = data[0] #音频地址\r
-            is_close_VC = data[1]\r
-            if new_voice_url != "":\r
-                self.recv_voice = 1#收到留言\r
-                print("已收到最新留言!!!!!")\r
-                # EventMesh.publish("set_motor_time",[1,1,5,8,4])#设置马达参数\r
-                self.AI_new_voice_url = new_voice_url\r
-                # if self.pwk_cnt_voice == 0 and self.pwk_notice_flag == 0:#并且不在多条留言的状态\r
-                #     EventMesh.publish("play_tts",["您有一条语音留言,10秒内按下中间键可以收听",1])\r
-                if self.pwk_notice_flag == 0 and EventMesh.publish("get_app_new_voice_flag"):#不处于播放状态并且是小程序下发的语音而不是请求的\r
-                    self.pwk_cnt_voice += 1#加上当前这条\r
-                    EventMesh.publish("set_storage_config",["is_noread_voice",1])\r
-                    EventMesh.publish("set_app_new_voice_flag",0)\r
-                    print("voice_add_to = {}".format(self.pwk_cnt_voice))\r
-                print("self.pwk_notice_flag = {}".format(self.pwk_notice_flag))\r
-                if self.pwk_cnt_voice >1:\r
-                    if self.pwk_notice_flag == 0:\r
-                        tts_str = "您有" + str(self.pwk_cnt_voice) + "条未读语音留言,10秒内按下中间键可以收听"\r
-                        EventMesh.publish("play_tts",[tts_str,1])\r
-                elif self.pwk_cnt_voice == 1:\r
-                    if self.pwk_notice_flag == 0:\r
-                        tts_str = "您有1条语音留言,10秒内按下中间键可以收听"\r
-                        EventMesh.publish("play_tts",[tts_str,1])\r
-            if is_close_VC != None:\r
-                self.Close_flag = is_close_VC\r
-            if self.AI_VC_send_data_queue.empty()!=True:\r
-                self.AI_VC_send_data_queue.clear()\r
-            if self.AI_ASR_send_data_queue.empty()!=True:\r
-                self.AI_ASR_send_data_queue.clear()\r
-            EventMesh.publish("set_led_state", {"ai_state": "thinking"})\r
-            self.AI_ASR_send_buf_len = 0\r
-            self._uplink_request_stop("play_new_voice_cb")\r
-            self.websocket_deconnect_VC()\r
-            self.AI_VC_start()\r
-            self.play_new_voice_flag = 1\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) \r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) \r
-        except Exception as e:\r
-                usys.print_exception(e)   \r
-        pass\r
-    #历史留言查看\r
-    def play_his_voice(self,event = None,msg= None):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_his_voice_cb,"data":msg}) \r
-    def play_his_voice_cb(self,data):\r
-        try:\r
-            new_voice_url = data[0] #音频地址\r
-            is_close_VC = data[1]\r
-            time = data[2]\r
-            tts_play_str = time + "收到" \r
-            EventMesh.publish("play_tts",[tts_play_str,1])\r
-            if new_voice_url != "":\r
-                self.recv_voice = 1#收到留言\r
-                print("历史留言查看")\r
-                # tts_play_str = "收到" \r
-                # EventMesh.publish("play_tts",[tts_play_str,1])\r
-                # EventMesh.publish("set_motor_time",[1,1,5,8,4])#设置马达参数\r
-                self.AI_new_voice_url = new_voice_url\r
-            if is_close_VC != None:\r
-                self.Close_flag = is_close_VC\r
-            if self.AI_VC_send_data_queue.empty()!=True:\r
-                self.AI_VC_send_data_queue.clear()\r
-            if self.AI_ASR_send_data_queue.empty()!=True:\r
-                self.AI_ASR_send_data_queue.clear()\r
-            EventMesh.publish("set_led_state", {"ai_state": "thinking"})\r
-            self.AI_ASR_send_buf_len = 0\r
-            self._uplink_request_stop("play_his_voice_cb")\r
-            self.websocket_deconnect_VC()\r
-            self.AI_VC_start()\r
-            self.play_new_voice_flag = 1\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) \r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) \r
-        except Exception as e:\r
-                usys.print_exception(e)   \r
-        pass\r
-    #等待未读留言查看\r
-    def play_wait_voice(self,event = None,msg= None):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_wait_voice_cb,"data":msg}) \r
-    def play_wait_voice_cb(self,data):\r
-        try:\r
-            new_voice_url = data[0] #音频地址\r
-            is_close_VC = data[1]\r
-            print("是否为第一次:{}".format(self.wait_first_flag))\r
-            if self.wait_first_flag == 0:\r
-                tts_play_str = "您有" + str(len(EventMesh.publish("get_play_new_voice_list"))) + "条未听留言,10秒内按下中间键可以收听"\r
-                EventMesh.publish("play_tts",[tts_play_str,1])  \r
-                # self.wait_first_timer.start(1000,1,self.noread_wait_vocie)\r
-                self.wait_first_flag = 1\r
-            elif self.wait_first_flag == 1:\r
-                self.wait_sort += 1\r
-                self.AI_TTS_str = "第"+str(self.wait_sort)+"条" \r
-            if new_voice_url != "":\r
-                self.recv_voice = 1#收到留言\r
-                print("未读留言查看")\r
-                self.AI_new_voice_url = new_voice_url\r
-            if is_close_VC != None:\r
-                self.Close_flag = is_close_VC\r
-            if self.AI_VC_send_data_queue.empty()!=True:\r
-                self.AI_VC_send_data_queue.clear()\r
-            if self.AI_ASR_send_data_queue.empty()!=True:\r
-                self.AI_ASR_send_data_queue.clear()\r
-            EventMesh.publish("set_led_state", {"ai_state": "thinking"})\r
-            self.AI_ASR_send_buf_len = 0\r
-            self._uplink_request_stop("play_wait_voice_cb")\r
-            self.websocket_deconnect_VC()\r
-            self.AI_VC_start()\r
-            self.play_new_voice_flag = 1\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) \r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) \r
-        except Exception as e:\r
-                usys.print_exception(e)   \r
-        pass\r
-    def noread_wait_vocie(self,*args):#如果一直没读\r
-        self.wait_first_cnt += 1\r
-        print("未读留言等待收听中:{}".format(self.wait_first_cnt))\r
-        if EventMesh.publish("get_recv_voice") == 1:#如果检测到留言播放\r
-            self.wait_first_cnt = 0\r
-            self.wait_first_timer.stop()\r
-        if self.wait_first_cnt == 15:\r
-            EventMesh.publish("set_wait_first_flag",0)\r
-            EventMesh.publish("set_history_voice_flag",0)\r
-            EventMesh.publish("set_wait_voice_flag",0)\r
-            self.wait_first_cnt = 0\r
-            self.wait_first_timer.stop()\r
-    #音乐播放\r
-    def play_music(self,event = None,msg= None):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_music_cb,"data":msg}) \r
-    def play_music_cb(self,data):\r
-        try:\r
-            music_name = data[0]\r
-            new_voice_url = data[1] #音频地址\r
-            is_close_VC = data[2]\r
-            if new_voice_url != "":\r
-                self.AI_new_voice_url = new_voice_url\r
-                self.music_play_flag = 1#表示音乐播放开启\r
-                EventMesh.publish("play_tts",["下面为您播放歌曲{}".format(music_name),1])\r
-            if is_close_VC != None:\r
-                self.Close_flag = is_close_VC\r
-            if self.AI_VC_send_data_queue.empty()!=True:\r
-                self.AI_VC_send_data_queue.clear()\r
-            if self.AI_ASR_send_data_queue.empty()!=True:\r
-                self.AI_ASR_send_data_queue.clear()\r
-            EventMesh.publish("set_led_state", {"ai_state": "thinking"})\r
-            self.AI_ASR_send_buf_len = 0\r
-            self._uplink_request_stop("play_music_cb")\r
-            self.websocket_deconnect_VC()\r
-            self.AI_VC_start()\r
-            self.play_new_voice_flag = 1\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) \r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) \r
-        except Exception as e:\r
-                usys.print_exception(e)   \r
-\r
-    #AI翻译接收TTS\r
-    def play_trans_tts(self,event = None,msg= None):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_trans_tts_cb,"data":msg}) \r
-    def play_trans_tts_cb(self,data):\r
-        try:\r
-            new_voice_url = data #音频地址\r
-            if new_voice_url != "":\r
-                self.AI_new_voice_url = new_voice_url\r
-            if self.AI_VC_send_data_queue.empty()!=True:\r
-                self.AI_VC_send_data_queue.clear()\r
-            if self.AI_ASR_send_data_queue.empty()!=True:\r
-                self.AI_ASR_send_data_queue.clear()\r
-            EventMesh.publish("set_led_state", {"ai_state": "thinking"})\r
-            self.AI_ASR_send_buf_len = 0\r
-            self._uplink_request_stop("play_trans_tts_cb")\r
-            self.websocket_deconnect_VC()\r
-            self.AI_VC_start()\r
-            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言\r
-            self.play_new_voice_flag = 1\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})\r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) \r
-            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) \r
-        except Exception as e:\r
-                usys.print_exception(e)   \r
-    def AI_client_new_voice(self): #上传未读的语音地址/包括音频地址也可以\r
-        json_data = \\r
-        {\r
-            "event": "audio.convert",\r
-            "payload": {\r
-                "audio_convert_request": {\r
-                    "audio_url": self.AI_new_voice_url,\r
-                    "bit_depth": 16,\r
-                    "channels": 1,\r
-                    "sample_rate": 16000,\r
-                    "target_format": "pcm"\r
-                }\r
-            }\r
-        }\r
-        self.AI_new_voice_url = ""\r
-        print("AI_client_new_voice!!!!!!!!!!!!!!!!!!!!!!!!!!!!!{}".format(json_data))\r
-        self.AI_client_VC_send(ujson.dumps(json_data))\r
-        \r
-\r
-    def chenggong_tone(self):\r
-        EventMesh.publish("audio_montage_play",["chenggong.mp3"])    \r
-        # self.aud.aud_tone_play(10,400)\r
-    def send_tone(self):\r
-        EventMesh.publish("audio_montage_play",["send_tone.mp3"])\r
-    def concurrency_limit_exceeded_tone(self):\r
-        self.Close()\r
-        EventMesh.publish("audio_montage_play",["chaobingfatis.mp3"])    #播放超并发提醒\r
-    #给服务器回应ack\r
-\r
-      \r
-    ############################################\r
-    #关闭音频播放线程\r
-    def AI_audio_queue_thread_close(self):\r
-        print("AI_audio_queue_thread_close1!!!!!!!!!!!!!!!!!!!!!!")\r
-        if self.AI_audio_queue_thread_id != 0:\r
-            count =500\r
-            while True:\r
-                count -= 1\r
-                if self.AI_audio_queue_thread_lock == 0 or count <=0:\r
-                    break\r
-                else:\r
-                    self.AI_audio_queue_thread_lock = 2\r
-                    print("AI_audio_queue_thread going:{}...".format(count))\r
-                    utime.sleep_ms(1)\r
-            self.AI_audio_queue_thread_lock = 0\r
-            print("AI_audio_queue_thread_close2!!!!!!!!!!!!!!!!!")\r
-            if self.AI_audio_queue_thread_id != 0:\r
-                try:\r
-                    _thread.stop_thread(self.AI_audio_queue_thread_id)\r
-                except Exception as e:\r
-                    usys.print_exception(e)\r
-        self.AI_audio_queue_thread_id = 0\r
-    # 音频队列        \r
-    def AI_audio_queue_thread(self):\r
-        print("AI_audio_queue_thread start")\r
-        count = 0\r
-        if self.AI_audio_queue.empty()!=True:\r
-            self.AI_audio_queue.clear()\r
-            print("音频数据还有剩余!!!!!!!!!!!!")\r
-        while True:\r
-            try:\r
-                if self.AI_audio_queue_thread_lock == 2 or self.VC_stop_play_audio_flag == 0:\r
-                    print("AI_audio_queue_thread 该退出了")\r
-                    break\r
-                #\r
-                count+=1\r
-                if count%500 == 1 and VC_HEARTBEAT_LOG:\r
-                    print("AI_audio_queue_thread on")\r
-                utime.sleep_ms(1)\r
-                #print("AI_audio_queue_thread")\r
-                if self.AI_audio_queue.empty() :\r
-                    self.AI_audio_queue_thread_lock = 0\r
-                    \r
-                    \r
-                else:\r
-                    self.AI_audio_queue_thread_lock = 1\r
-                    data = self.AI_audio_queue.get()\r
-                    self.close_timerout = 0\r
-                    if self.play_flag == 0:\r
-                        self.play_flag =1\r
-                    #self.AI_client_tts_data_len=len(data)\r
-                    #print("audio_queue:len:{}".format(len(data[0])))\r
-                    \r
-                    if len(data[0]) > 10:\r
-                        try:\r
-                            if self._ptt_should_drop_inbound_tts():\r
-                                continue\r
-                        except Exception:\r
-                            pass\r
-                        for i in range(5): \r
-                            if self.VC_Play_flag == 0:\r
-                                self.VC_Play_Start()\r
-                                utime.sleep_ms(2)\r
-                            else:\r
-                                break\r
-                        # 打断后 PCM 偶发未打开:加重试,避免「队列有数据、play_pcm=None、无声」\r
-                        if self.VC_play_TYPE == VC_play_TYPE_PCM and self.play_pcm is None and self.VC_stop_play_audio_flag == 1:\r
-                            for j in range(4):\r
-                                if self.VC_Play_flag == 0:\r
-                                    self.VC_Play_Start()\r
-                                utime.sleep_ms(30 + j * 35)\r
-                                if self.play_pcm is not None:\r
-                                    break\r
-                        try:\r
-                            if self.VC_play_TYPE == VC_play_TYPE_PCM:\r
-                                # if self.VC_play_samplerate == 8000:\r
-                                #     tmp= self.aud.playStream(1,data[0] )\r
-                                # else:\r
-                                    write_buf = bytearray(data[0])\r
-                                    #print("play_pcm.stream_write1 len:{}".format(len(write_buf)))\r
-                                    if self.play_pcm != None:\r
-                                        #print("play_pcm.stream_write !!!")\r
-                                        ret = self.play_pcm.stream_write(write_buf,len(write_buf))\r
-                                        #ret= self.aud.playStream(1, data[0])#播放音频流\r
-                                        if self.tts_out_data_len == 0:\r
-                                            try:\r
-                                                self._tts_first_write_ms = utime.ticks_ms()\r
-                                            except Exception:\r
-                                                self._tts_first_write_ms = 0\r
-                                            try:\r
-                                                sid = getattr(self, "_cur_sid", "")\r
-                                                did = getattr(self, "_cur_dialog_id", "")\r
-                                            except Exception:\r
-                                                sid, did = "", ""\r
-                                            print("[TTS] first_write ret={} len={} sid={} did={}".format(\r
-                                                ret, len(write_buf), sid, did\r
-                                            ))\r
-                                        if ret <= 0:\r
-                                            print("[TTS] stream_write ret={} len={} (no audio?)".format(ret, len(write_buf)))\r
-                                        self.tts_out_data_len += ret\r
-                                        try:\r
-                                            if getattr(self.fsm, "await_played", False) and self.AI_audio_queue.empty():\r
-                                                self._vc_try_play_end_tick()\r
-                                        except Exception:\r
-                                            pass\r
-                                        #print("play_pcm.stream_write2:{}".format(self.tts_out_data_len))\r
-                            elif self.VC_play_TYPE == VC_play_TYPE_MP3:\r
-                                tmp= self.aud.playStream(3, data[0])#播放音频流\r
-                                \r
-                                if tmp != 0:\r
-                                    print("2playStream:error tmp:{} data len:{}!!!!!!!!!!!!".format(tmp,len(data[0])))\r
-                                    # # #播放失败的时候填充WAV头\r
-                                    # data_len = len(data[0])\r
-                                    # data_len_b = data_len.to_bytes(4,'little')#数据长度转16进制数组\r
-                                    # h_data = [ 0x52 ,0x49 ,0x46 ,0x46 #,0x24 ,0x00 ,0x00 ,0x00 #文件标识、文件大小\r
-                                    #         ,0x57 ,0x41 ,0x56 ,0x45 ,0x66 ,0x6D ,0x74 ,0x20  #文件格式、格式块标识符\r
-                                    #         ,0x10 ,0x00 ,0x00 ,0x00 ,0x01 ,0x00 ,0x01 ,0x00 ,0x80 ,0x3E ,0x00 ,0x00  #格式块大小4、音频格式2、声道数2、采样率4\r
-                                    #         ,0x80 ,0x3E ,0x00 ,0x00 ,0x02 ,0x00 ,0x10 ,0x00 ,0x64 ,0x61 ,0x74 ,0x61]#每秒字节数4、数据块对齐2、位深度2、data4\r
-                                    # h_data_b = bytes(h_data)\r
-                                    # file_len = data_len+len(h_data_b)+4+4\r
-                                    # file_len_b = file_len.to_bytes(4,'little')#文件长度转16进制数组\r
-                                    # h_data_b = h_data_b[:4]+file_len_b+h_data_b[4:]+data_len_b\r
-                                    # #self.aud.playStream(2, h_data_b)#播放音频流\r
-                                    # tmp= self.aud.playStream(2, h_data_b+data[0])#播放音频流\r
-                                    # print("3playStream:tmp:{} !!!!!!!!!!!!".format(tmp))\r
-                            \r
-                            \r
-                            mem = gc.mem_free()\r
-                            #print('1剩余内存RAM:{} KB'.format(mem / 1024))\r
-                        except Exception as e:\r
-                            print("AI_audio_queue_thread:ERROR:")\r
-                            usys.print_exception(e)\r
-                    \r
-                    # if self.VC_state == VC_State_TTSEnd and self.AI_audio_queue.empty() :#self.AI_audio_queue为空且tts下发结束\r
-                    #     #通知服务器播放结束\r
-                    #     self.AI_client_VC_LocalRespondingEnded()\r
-            except Exception as e:\r
-                print("AI_audio_queue_thread:ERROR:")\r
-                usys.print_exception(e)\r
-        self.AI_audio_queue_thread_lock = 0\r
-        self.AI_audio_queue_thread_id = 0\r
-        print("AI_audio_queue_thread:exit")\r
-    def factory_mode(self):\r
-        print("factory_mode")\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) \r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.factory_start}) \r
-    def factory_start(self):\r
-        self.factory_timer.start(3000, 1, self.factory_timer_cb)\r
-    def factory_timer_cb(self, *args, **kwargs):\r
-        print("factory_timer_cb play......")\r
-        #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#切换成功\r
-        EventMesh.publish("audio_montage_play",["xinhaoruo1.mp3"])\r
-    def Close_VC(self,event,msg):\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) \r
-    def Close(self):\r
-        try:\r
-            if getattr(self, "VC_mode", 0) != 1:\r
-                print("[VC][long_idle] Close() enter VC_mode={} {} VC_satrt_flag={} VC_record_state_flag={}".format(\r
-                    getattr(self, "VC_mode", -1), self._vc_fsm_log_snip(),\r
-                    getattr(self, "VC_satrt_flag", -1), getattr(self, "VC_record_state_flag", -1)))\r
-        except Exception:\r
-            pass\r
-        self.VC_stop_play_audio_flag = 0\r
-        try:\r
-            self.AI_key_long_press_flag = 0\r
-        except Exception:\r
-            pass\r
-        self.VC_Thinking_timerout = 0\r
-        self.AI_ASR_timer.stop()\r
-        self._1S_timer.stop()\r
-        self.close_timerout = 0\r
-        self._idle_deep_sec = 0\r
-        self.factory_timer.stop()\r
-        self.AI_TTS_str = ""\r
-        self.AI_ASR_str = ""\r
-        self.AI_ASR_str_tmp = ""\r
-        self.AI_ASR_flag  = 0\r
-        self.AI_ASR_check_timeout = 0\r
-        self.AI_ASR_error_timeout = 0\r
-        self.LocalRespondingEnded_timerout = 0\r
-        self._need_audio_played_ack = 0\r
-        self._tts_pending = 0\r
-        self._tts_pending_ms = 0\r
-        self._vc_session_started = 0\r
-        self._ptt_audio_created = 0\r
-        self._vc_pending_interrupt_uplink = 0\r
-        try:\r
-            self._skip_started_uplink_once = 0\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self._ptt_abort_interrupt_arm()\r
-        except Exception:\r
-            pass\r
-        # Power 短按停止对话后,再次按 AI 需要新建 dialog_id:清理本轮 dialog/session 标识\r
-        try:\r
-            self._cur_dialog_id = ""\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self._cur_sid = ""\r
-        except Exception:\r
-            pass\r
-        self.is_ruppt_tts = 0#打断标志位清零\r
-        print("ai_VC Close1!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")\r
-        try:\r
-            if getattr(self, "VC_mode", 0) != 1:\r
-                print("[VC][long_idle] step=set_led_state_idle (first)")\r
-        except Exception:\r
-            pass\r
-        EventMesh.publish("set_led_state", {"ai_state": "idle"})\r
-        self.tts_in_data_len = 0\r
-        self.tts_out_data_len = 0\r
-        #必须先销毁线程,不然线程会容易报错\r
-        \r
-        try:\r
-            self.AI_audio_queue_thread_close()#销毁音频线程\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-        try:\r
-            self.AI_VC_send_data_queue_thread_close()#销毁数据发送线程\r
-        except Exception as e: \r
-            usys.print_exception(e)\r
-        \r
-        # Close 可能被不同线程触发:统一切回主线程执行 stop\r
-        try:\r
-            if getattr(self, "VC_mode", 0) != 1:\r
-                print("[VC][long_idle] step=schedule_uplink_stop reason=Close (async publish_AI_btnInfo)")\r
-        except Exception:\r
-            pass\r
-        EventMesh.publish("publish_AI_btnInfo",{"func":self._uplink_request_stop, "data":"Close"})\r
-        \r
-        print("ai_VC Close1.1!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")\r
-        \r
-        print("ai_VC Close1.2!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")\r
-        \r
-        try:\r
-            if getattr(self, "VC_mode", 0) != 1:\r
-                print("[VC][long_idle] step=websocket_deconnect_VC")\r
-        except Exception:\r
-            pass\r
-        try:\r
-            self.websocket_deconnect_VC()\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-        \r
-\r
-        \r
-\r
-\r
-        print("ai_VC Close1.3!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")\r
-        \r
-        print("ai_VC Close2!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")\r
-        \r
-        \r
-        \r
-        \r
-        if self.record_data_save_flag == 1:\r
-            if self.record_fd :\r
-                self.record_fd.close()\r
-            self.record_fd = None\r
-        if self.play_data_save_flag == 1:\r
-            if self.play_fd !=None:\r
-                self.play_fd.close()\r
-                self.play_fd = None\r
-        print("ai_VC Close3!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n")\r
-        \r
-        if self.AI_VC_send_data_queue.empty()!=True:\r
-            self.AI_VC_send_data_queue.clear()\r
-        self.play_new_voice_flag = 0 #语音消息播放状态\r
-        if self.voice_choose_flag == 0:  # 如果没有未读语音\r
-            self.AI_new_voice_url = ""  # 语音播放地址\r
-            print("关闭并重置语音标志位")\r
-        else:\r
-            print("关闭但是没有重置语音标志位")\r
-        self.AI_ASR_send_buf_len = 0\r
-        #self.AI_WS_connect_Queue.put(self.AI_client_VC)\r
-        try:\r
-            if getattr(self, "VC_mode", 0) != 1:\r
-                print("[VC][long_idle] step=set_led_state_idle (second) -> VC_init VC_satrt_flag=0")\r
-        except Exception:\r
-            pass\r
-        EventMesh.publish("set_led_state", {"ai_state": "idle"})\r
-        self.VC_init()\r
-        self.VC_satrt_flag = 0\r
-        # Level2:会话结束后仍可继续 1s 节拍累计 idle 直至深睡(依赖 VC_IDLE_DEEP_SLEEP_SEC)\r
-        try:\r
-            _dsec = 60\r
-            _den = 1\r
-            if "vc_config" in globals() and vc_config:\r
-                _dsec = int(vc_config.get_int("VC_IDLE_DEEP_SLEEP_SEC", 60))\r
-                _den = int(vc_config.get_int("VC_IDLE_DEEP_SLEEP_ENABLE", 1))\r
-            if _den and _dsec > 0:\r
-                self._1S_timer.start(1000, 1, self._1S_timer_cb)\r
-        except Exception:\r
-            pass\r
-        # try:\r
-        #     while True:\r
-        #         if self.AI_WS_connect_Queue.empty()!=True:\r
-        #             AI_WS_connect = self.AI_WS_connect_Queue.get()\r
-        #             if AI_WS_connect :\r
-        #                 try:\r
-        #                     AI_WS_connect._close()\r
-        #                     AI_WS_connect = None\r
-        #                 except Exception as e:\r
-        #                     usys.print_exception(e)\r
-        #             else:\r
-        #                 print("websocket链接已为空")\r
-        #         else:\r
-        #             break\r
-        # except Exception as e:\r
-        #     usys.print_exception(e)\r
-\r
-    def VC_init2(self):\r
-        self.VC_init()\r
-        \r
-        print("ai_VC VC_init2!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")\r
-        try:\r
-            self.websocket_deconnect_VC()\r
-        except Exception as e:\r
-            usys.print_exception(e)\r
-        print("ai_VC Close3!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n")\r
-        self._1S_timer.stop()\r
-        if self.AI_VC_send_data_queue.empty()!=True:\r
-            self.AI_VC_send_data_queue.clear()\r
-\r
-        self.AI_ASR_send_buf_len = 0\r
-        if self.AI_audio_queue.empty()!=True:\r
-            self.AI_audio_queue.clear()\r
-    def VC_init(self):\r
-        _q0 = self.AI_audio_queue.size()\r
-        self.VC_Play_End()\r
-        if self.AI_audio_queue.empty()!=True:\r
-            self.AI_audio_queue.clear()\r
-        print("[VC] init audio_queue {} -> {}".format(_q0, self.AI_audio_queue.size()))\r
-        # num = EventMesh.publish("get_volum_size")\r
-        # if num == None:\r
-        #     num = 8\r
-        # self.aud.setVolume(num)#恢复原始音量\r
+# -*- coding: utf-8 -*-
+"""语音对话编排:按键/PTT、AI_VC_start/Close、定时器与 FSM 编排。"""
+from vc_session_imports import *  # noqa: F401,F403
+import _thread
+from vc_session_io import VCSessionIoMixin
+from vc_session_media import VCSessionMediaMixin
+
+class AI_VC(VCSessionIoMixin, VCSessionMediaMixin, Abstract):
+    def _vc_fsm_log_snip(self):
+        try:
+            f = self.fsm
+            return "sess={} turn={} tts_rx={} await={} sp_up={}".format(
+                int(getattr(f, "session", -1)),
+                int(getattr(f, "turn", -1)),
+                int(getattr(f, "tts_rx_phase", -1)),
+                1 if getattr(f, "await_played", False) else 0,
+                1 if getattr(f, "speech_uplink_open", 0) else 0,
+            )
+        except Exception:
+            return "fsm=?"
+    def _vc_capture_allowed(self, reason=""):
+        """采集/上行开麦前:下行播放或本地短音未结束则禁止。"""
+        try:
+            if self._vc_tts_pipeline_busy():
+                return False
+            if self._vc_local_play_busy():
+                return False
+        except Exception:
+            return False
+        return True
+    def _vc_stop_downlink_for_interrupt(self, reason="interrupt"):
+        """详设 DES-SCN-003:打断/取消时先本地停播清队列,再允许开麦。"""
+        try:
+            if getattr(self, "AI_audio_queue", None) is not None:
+                if self.AI_audio_queue.empty() != True:
+                    self.AI_audio_queue.clear()
+        except Exception:
+            pass
+        try:
+            if self._vc_tts_pipeline_busy() or getattr(self, "is_ruppt_tts", 0) == 1:
+                self._drop_tts_audio = 1
+        except Exception:
+            pass
+        try:
+            self._ptt_hard_mute_local_playback()
+        except Exception:
+            pass
+        try:
+            if getattr(self, "VC_Play_flag", 0) == 1 or getattr(self, "play_pcm", None) is not None:
+                self.VC_Play_End()
+        except Exception:
+            pass
+        try:
+            self._vc_local_play_active = 0
+            self._vc_local_play_until_ms = 0
+        except Exception:
+            pass
+        try:
+            self.fsm.await_played = False
+            self._need_audio_played_ack = 0
+            self._tts_pending = 0
+            self._tts_pending_ms = 0
+        except Exception:
+            pass
+        try:
+            pending_intr = getattr(self, "_vc_pending_interrupt_uplink", 0) == 1
+            if (
+                not pending_intr
+                and getattr(self, "VC_record_state_flag", 0) != 0
+                and getattr(self, "record_data_save_flag", 0) == 0
+            ):
+                self._uplink_request_stop("stop_downlink/" + str(reason))
+        except Exception:
+            pass
+    def _vc_long_idle_active_speech_or_later(self):
+        # 对应旧 VC_state >= SpeechIng
+        try:
+            t = getattr(self.fsm, "turn", VCTurnState.Idle)
+            return t not in (VCTurnState.Idle, VCTurnState.Listening)
+        except Exception:
+            return False
+    def _vc_is_business_idle(self):
+        # Level2:LED ai_state 为 idle,且无录音/流播/音乐闹钟等(与 WS ping 无关)
+        try:
+            if getattr(self, "music_play_flag", 0) == 1:
+                return False
+            if getattr(self, "alarm_play_flag", 0) == 1:
+                return False
+        except Exception:
+            pass
+        try:
+            if getattr(self, "VC_record_state_flag", 0) != 0:
+                return False
+            if getattr(self, "VC_Play_flag", 0) != 0:
+                return False
+        except Exception:
+            pass
+        try:
+            if EventMesh.publish("get_vc_stream_active") == 1:
+                return False
+        except Exception:
+            pass
+        try:
+            st = EventMesh.publish("get_led_debug_state")
+            if st and isinstance(st, dict):
+                if (st.get("ai") or "") != "idle":
+                    return False
+        except Exception:
+            return False
+        return True
+    def _vc_idle_deep_sleep_impl(self):
+        try:
+            _need_close = (self.VC_satrt_flag == 1) or (getattr(self, "AI_client_VC_isconnect", 0) == 1)
+            print("[VC][idle_L2] 触发休眠流程 need_Close={} VC_mode={}".format(
+                1 if _need_close else 0, getattr(self, "VC_mode", -1)))
+        except Exception:
+            pass
+        try:
+            if self.VC_satrt_flag == 1 or getattr(self, "AI_client_VC_isconnect", 0) == 1:
+                try:
+                    print("[VC][idle_L2] 调用 Close 断开会话/链路")
+                except Exception:
+                    pass
+                self.Close()
+        except Exception:
+            pass
+        try:
+            utime.sleep_ms(100)
+        except Exception:
+            pass
+        try:
+            print("[VC][idle_L2] 发布 Hsensor_set_sleep")
+            EventMesh.publish("Hsensor_set_sleep")
+        except Exception:
+            pass
+        try:
+            print("[VC][idle_L2] 发布 SYSTEM_SLEEP ({})".format(constants.EVENT.SYSTEM_SLEEP))
+            EventMesh.publish(constants.EVENT.SYSTEM_SLEEP)
+        except Exception:
+            pass
+    def _vc_rx_resets_long_idle_timer(self, recv_data):
+        """是否应因本帧下行清零 close_timerout(排除保活/心跳,含 V2.5 文本 ping 对端应答)。"""
+        try:
+            if recv_data is None:
+                return False
+            if isinstance(recv_data, (bytes, bytearray)):
+                return True
+            if not isinstance(recv_data, str):
+                return False
+            t = recv_data.strip()
+            if not t:
+                return False
+            tl = t.lower()
+            if tl in ("ping", "pong"):
+                return False
+            try:
+                jd = ujson.loads(t)
+            except Exception:
+                return True
+            ev = jd.get("event")
+            if ev is None or ev == "":
+                ev = jd.get("type")
+            if ev is None:
+                return True
+            if str(ev).strip().lower() in _VC_KEEPALIVE_RX_EVENTS:
+                return False
+            return True
+        except Exception:
+            return True
+    def _handle_pwk_short_release(self, event=None, msg=None, merged_ai_tail=False):
+        """电源键短按:读留言标志、退会议/翻译;否则 Close,或单键模式下走 AI_key_release。"""
+        print("pwk_key_release")
+        if EventMesh.publish("get_recv_voice") == 1:
+            EventMesh.publish("update_play_new_voice_info")
+            self.recv_voice = 0
+        EventMesh.publish("set_history_voice_flag", 0)
+        self.AI_new_voice_url = ""
+        if self.trans_flag == 1 or self.meet_flag == 1:
+            EventMesh.publish("exit_meet_mode")
+            EventMesh.publish("exit_trans_mode")
+            return
+        if merged_ai_tail:
+            self.AI_key_release(event, msg)
+        else:
+            EventMesh.publish("publish_AI_btnInfo", {"func": self.Close})
+        EventMesh.publish("stop_sos_call")
+    def _ai_key_press_ptt_mode(self):
+        """单对话 PTT:短按仅停 TTS(不换 sid);非播放态短按吞掉;长按由 AI_key_long_press→ptt_begin。"""
+        if self.VC_mode != 1:
+            return False
+        try:
+            playing = False
+            try:
+                t = getattr(self.fsm, "turn", VCTurnState.Idle)
+                playing = t in (VCTurnState.TtsReceiving, VCTurnState.Playing)
+            except Exception:
+                playing = False
+            if playing or self._vc_tts_pipeline_busy():
+                self._ptt_short_press_stop = 1
+                try:
+                    self._ptt_hard_mute_local_playback()
+                except Exception:
+                    pass
+                try:
+                    self.VC_Play_End()
+                except Exception:
+                    pass
+                try:
+                    self.fsm.await_played = False
+                except Exception:
+                    pass
+                try:
+                    self._ptt_led_lock_listening = 0
+                    self._ptt_interrupt_active = 0
+                    self.is_ruppt_tts = 0
+                except Exception:
+                    pass
+                try:
+                    self._vc_publish_led("idle", "ptt_short_press/stop_tts")
+                except Exception:
+                    pass
+                return True
+        except Exception:
+            pass
+        return True
+    def _ai_key_press_long_dialogue_interrupt(self):
+        """长对话:TTS/播放忙或 turn==Thinking 时短按 → RequestToSpeak(先 cancel,canceled 后再开麦)。"""
+        if self.VC_mode != 0:
+            return False
+        try:
+            if getattr(self, "AI_client_VC_isconnect", 0) != 1 or getattr(self, "_vc_session_started", 0) != 1:
+                return False
+        except Exception:
+            return False
+        need_cancel = False
+        if self._vc_tts_pipeline_busy():
+            need_cancel = True
+        else:
+            try:
+                if getattr(self.fsm, "turn", VCTurnState.Idle) == VCTurnState.Thinking:
+                    need_cancel = True
+            except Exception:
+                pass
+        if not need_cancel:
+            return False
+        print("[KEY] long interrupt -> RequestToSpeak (cancel then uplink)")
+        self.is_ruppt_tts = 1
+        try:
+            self._vc_pending_interrupt_uplink = 1
+        except Exception:
+            pass
+        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_RequestToSpeak})
+        return True
+    def _ai_key_press_long_dialogue_wake(self):
+        """长对话未连接或未 started:直接 AI_VC_start,勿 Close+sleep。"""
+        if self.VC_mode != 0:
+            return False
+        if getattr(self, "voice_flag", 0) == 1 or getattr(self, "meet_flag", 0) == 1 or getattr(self, "trans_flag", 0) == 1:
+            return False
+        try:
+            if getattr(self, "AI_client_VC_isconnect", 0) == 1 and getattr(self, "_vc_session_started", 0) == 1:
+                return False
+        except Exception:
+            pass
+        print("[KEY] long wake AI_VC_start (no Close)")
+        try:
+            self.Close_flag = 0
+        except Exception:
+            pass
+        self.get_config_timer_count = 0
+        self.key_state = 1
+        self.AI_TTS_str = ""
+        self.AI_ASR_str = ""
+        self.AI_ASR_str_tmp = ""
+        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_VC_start})
+        self.AI_key_long_press_flag = 0
+        return True
+    def _vc_led_alert_rb_flash(self):
+        try:
+            EventMesh.publish("set_led_state", {"alert": "rb_flash_2"})
+        except Exception:
+            pass
+    def _ai_key_press_long_dialogue_when_session_ready(self):
+        """长对话已连接且 session 已 started:非播放态直接开麦续聊(勿 idle 时发 cancel)。"""
+        if self.VC_mode != 0:
+            return False
+        try:
+            if getattr(self, "AI_client_VC_isconnect", 0) == 1 and getattr(self, "_vc_session_started", 0) == 1:
+                # 模式切换提示音会置 Close_flag=1;用户再短按须恢复可开麦
+                try:
+                    self.Close_flag = 0
+                except Exception:
+                    pass
+                self._vc_clear_stale_record_state("key.press/continue")
+                self.request_uplink_start("key.press/continue")
+                return True
+        except Exception:
+            pass
+        return False
+    def _ai_key_release_handle_ptt(self):
+        """单对话 PTT 松手:仅在上行录音中时 end_speech;禁止误触发 AI_VC_start(否则新 sid / cancel 竞态)。"""
+        if self.AI_key_long_press_flag == 1:
+            if getattr(self, "VC_record_state_flag", 0) != 0:
+                try:
+                    gap = 200
+                    if "vc_config" in globals() and vc_config:
+                        gap = int(vc_config.get_int("PTT_RELEASE_END_SPEECH_DELAY_MS", 200))
+                    if gap > 0:
+                        utime.sleep_ms(gap)
+                except Exception:
+                    pass
+                print("[PTT] release end_speech asr_str={!r} rec={}".format(
+                    self.AI_ASR_str, getattr(self, "VC_record_state_flag", -1)))
+                self.AI_client_VC_end_speech()
+            else:
+                self.AI_key_long_press_flag = 0
+                try:
+                    if getattr(self, "_ptt_short_press_stop", 0) == 1:
+                        self._ptt_short_press_stop = 0
+                except Exception:
+                    pass
+                print("[PTT] release_no_uplink_skip")
+            return
+        try:
+            if getattr(self, "_ptt_short_press_stop", 0) == 1:
+                self._ptt_short_press_stop = 0
+        except Exception:
+            pass
+        print("[PTT] release_short_no_long")
+    def _ai_key_long_press_route(self, event=None, msg=None):
+        """长按:翻译先 Close 再开翻译录音;单对话→ptt_begin;长对话→与短按一致走 AI_key_press 链路。"""
+        if self.trans_flag == 1:
+            self.Close()
+            EventMesh.publish("start_voice_record", 4)
+        self.AI_key_long_press_flag = 1
+        if self.VC_mode == 1:
+            EventMesh.publish("publish_AI_btnInfo", {"func": self.ptt_begin})
+        else:
+            EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_key_press})
+    def _ptt_end_led_suppress(self):
+        # 保持 _vc_play_end_suppress_idle_led / _ptt_led_lock_listening 直至真开录(VC_Speech_Start)
+        pass
+    def _ptt_should_drop_inbound_tts(self):
+        """单对话打断窗口:丢弃迟到 TTS 帧 / 禁止起播。"""
+        try:
+            if getattr(self, "VC_mode", -1) != 1:
+                return False
+            if getattr(self, "_drop_tts_audio", 0) == 1:
+                return True
+            if getattr(self, "is_ruppt_tts", 0) == 1:
+                return True
+            if getattr(self, "_ptt_interrupt_active", 0) == 1:
+                return True
+            if getattr(self, "_vc_serial_canceling", 0) == 1:
+                return True
+            if getattr(self, "_ptt_pending_arm", 0) == 1:
+                return True
+        except Exception:
+            pass
+        return False
+    def _ptt_should_skip_remote_playback(self):
+        return self._ptt_should_drop_inbound_tts()
+    def _ptt_enter_interrupt_arm(self):
+        try:
+            self._ptt_arm_epoch = int(getattr(self, "_ptt_arm_epoch", 0)) + 1
+            self._ptt_interrupt_active = 1
+            self._ptt_led_lock_listening = 1
+            self._vc_play_end_suppress_idle_led = 1
+            self._drop_tts_audio = 1
+            self.is_ruppt_tts = 1
+        except Exception:
+            pass
+    def _ptt_abort_interrupt_arm(self):
+        try:
+            self._ptt_interrupt_active = 0
+            self._ptt_led_lock_listening = 0
+            self._vc_play_end_suppress_idle_led = 0
+            self._drop_tts_audio = 0
+            self.is_ruppt_tts = 0
+        except Exception:
+            pass
+    def _ptt_on_record_hardware_started(self):
+        """真开录后:允许新 TTS、放开灯锁;旧 TTS 已在打断期丢弃。"""
+        try:
+            self._ptt_interrupt_active = 0
+            self._ptt_led_lock_listening = 0
+            self._vc_play_end_suppress_idle_led = 0
+            self._drop_tts_audio = 0
+            self.is_ruppt_tts = 0
+        except Exception:
+            pass
+    def _ptt_hard_mute_local_playback(self):
+        try:
+            self.VC_Play_flag = 0
+            if getattr(self, "VC_play_TYPE", None) == VC_play_TYPE_PCM:
+                if getattr(self, "play_pcm", None) is not None:
+                    try:
+                        self.play_pcm.stream_stop()
+                        self.play_pcm.close()
+                    except Exception:
+                        pass
+                    self.play_pcm = None
+            try:
+                self.aud.stopPlayStream()
+                self.aud.stopAll()
+            except Exception:
+                pass
+            if getattr(self, "AI_audio_queue", None) is not None:
+                try:
+                    if self.AI_audio_queue.empty() != True:
+                        self.AI_audio_queue.clear()
+                except Exception:
+                    pass
+        except Exception:
+            pass
+        try:
+            gap = 0
+            if "vc_config" in globals() and vc_config:
+                gap = int(vc_config.get_int("PTT_POST_MUTE_MS", 30))
+            if gap > 0:
+                utime.sleep_ms(gap)
+        except Exception:
+            pass
+    def battery_priority_mute_vc(self, event=None, msg=None):
+        """低电/关机提示音:短暂停 VC TTS,避免被流式播放挡住。"""
+        try:
+            self._ptt_hard_mute_local_playback()
+        except Exception:
+            pass
+        try:
+            if getattr(self, "VC_Play_flag", 0) == 1 or getattr(self, "play_pcm", None) is not None:
+                self.VC_Play_End()
+        except Exception:
+            pass
+    def _vc_enqueue_btn(self, func, data=None):
+        """统一经 publish_AI_btnInfo 入队,在 btnInfo_queue_thread 串行执行。"""
+        try:
+            if data is not None:
+                EventMesh.publish("publish_AI_btnInfo", {"func": func, "data": data})
+            else:
+                EventMesh.publish("publish_AI_btnInfo", {"func": func})
+        except Exception:
+            pass
+    def _vc_ptt_cancel_wait_ms(self):
+        try:
+            if "vc_config" in globals() and vc_config:
+                return int(vc_config.get_int("PTT_CANCEL_WAIT_MS", 2500))
+        except Exception:
+            pass
+        return 2500
+    def _vc_ptt_need_interrupt_wait(self):
+        """单对话:服务端仍在思考/收 TTS/播报时,开录前须先 cancel 并等 canceled。"""
+        try:
+            if getattr(self, "VC_mode", -1) != 1:
+                return False
+            turn = getattr(self.fsm, "turn", VCTurnState.Idle)
+            if self._vc_tts_pipeline_busy():
+                return True
+            if turn in (
+                VCTurnState.Thinking, VCTurnState.TtsReceiving,
+                VCTurnState.Playing, VCTurnState.AwaitPlayedAck,
+            ):
+                return True
+        except Exception:
+            pass
+        return False
+    def _vc_begin_ptt_cancel_wait(self, reason="ptt_interrupt"):
+        try:
+            self._ptt_pending_arm = 1
+            self._vc_serial_canceling = 1
+            try:
+                self._vc_cancel_deadline_ms = utime.ticks_add(
+                    utime.ticks_ms(), self._vc_ptt_cancel_wait_ms())
+            except Exception:
+                self._vc_cancel_deadline_ms = 0
+            json_data = {"event": "cancel", "payload": {}}
+            self.AI_client_VC_send(ujson.dumps(json_data))
+            try:
+                self.fsm.reset_rx_phases()
+            except Exception:
+                pass
+            self._drop_tts_audio = 1
+            print("[VC][serial] cancel_wait reason={} turn={}".format(
+                reason, getattr(getattr(self, "fsm", None), "turn", "?")))
+        except Exception:
+            pass
+    def _vc_on_ptt_cancel_settled(self, via="canceled"):
+        """btn 线程:cancel 已收尾,执行挂起的 PTT 开录。"""
+        try:
+            if not getattr(self, "_vc_serial_canceling", 0) and not getattr(self, "_ptt_pending_arm", 0):
+                return
+            self._vc_serial_canceling = 0
+            self._vc_cancel_deadline_ms = 0
+            if getattr(self, "_ptt_pending_arm", 0):
+                self._ptt_pending_arm = 0
+                print("[VC][serial] ptt_arm after {}".format(via))
+                self._ptt_arm_uplink()
+        except Exception:
+            pass
+    def _vc_tick_serial_cancel_timeout(self):
+        if not getattr(self, "_vc_serial_canceling", 0):
+            return
+        try:
+            dl = getattr(self, "_vc_cancel_deadline_ms", 0)
+            if dl and utime.ticks_diff(utime.ticks_ms(), dl) >= 0:
+                print("[VC][serial] cancel_wait timeout -> arm")
+                self._vc_on_ptt_cancel_settled(via="timeout")
+        except Exception:
+            pass
+    def _ptt_arm_uplink(self, t0=0):
+        """btn 线程:cancel 已结束或无需 cancel 时,发 audio.create 并开麦。"""
+        try:
+            self.is_ruppt_tts = 0
+        except Exception:
+            pass
+        try:
+            self._ptt_audio_created = 0
+        except Exception:
+            pass
+        try:
+            self.AI_ASR_flag = 0
+        except Exception:
+            pass
+        try:
+            self.AI_client_VC_send_speech()
+        except Exception:
+            pass
+        try:
+            self.VC_Speech_Start_now()
+        except Exception:
+            pass
+        try:
+            self._vc_publish_led("listening", "ptt_begin/recording")
+        except Exception:
+            pass
+        self._ptt_end_led_suppress()
+        try:
+            if t0:
+                print("[PTT] record started (ms={})".format(utime.ticks_ms() - t0))
+        except Exception:
+            pass
+    def ptt_begin(self, reason="key.long_press"):
+        try:
+            if self.VC_mode != 1:
+                return
+        except Exception:
+            return
+
+        # PTT 新一轮开始:清理模式切换/提示音退出标志
+        try:
+            self.Close_flag = 0
+            self._ptt_await_server = 0
+        except Exception:
+            pass
+
+        t0 = 0
+        try:
+            t0 = utime.ticks_ms()
+        except Exception:
+            t0 = 0
+
+        self._ptt_enter_interrupt_arm()
+        try:
+            self._vc_publish_led("listening", "ptt_begin/pre")
+        except Exception:
+            pass
+
+        # 本地先硬静音再 VC_init,避免 DMA/队列尾包漏播
+        try:
+            self._ptt_hard_mute_local_playback()
+        except Exception:
+            pass
+        try:
+            self.VC_init()
+        except Exception:
+            pass
+        try:
+            if getattr(self, "VC_Play_flag", 0) == 1 or getattr(self, "play_pcm", None) is not None:
+                self.VC_Play_End()
+        except Exception:
+            pass
+        try:
+            self._ptt_hard_mute_local_playback()
+        except Exception:
+            pass
+
+        # 若上一轮录音尚未结束,先强制停掉,避免 record_thread 复用出错
+        try:
+            if getattr(self, "VC_record_state_flag", 0) != 0:
+                self.VC_Speech_End()
+        except Exception:
+            pass
+
+        # 已在等 canceled:只刷新「待开录」,勿重复 audio.create
+        try:
+            if getattr(self, "_vc_serial_canceling", 0):
+                self._ptt_pending_arm = 1
+                print("[PTT] cancel in progress, refresh pending arm")
+                self._ptt_end_led_suppress()
+                return
+        except Exception:
+            pass
+
+        # 会话未连接:先建链并发 Start,等待 started 后再自动开麦
+        try:
+            if getattr(self, "AI_client_VC_isconnect", 0) != 1:
+                self._ptt_force_record_on_started = 1
+                self.AI_VC_start()
+                return
+        except Exception:
+            self._ptt_force_record_on_started = 1
+            try:
+                self.AI_VC_start()
+            except Exception:
+                pass
+            return
+
+        # 会话还没 initialized:先发 Start,请求服务端进入 started
+        if getattr(self, "_vc_session_started", 0) != 1:
+            self._ptt_force_record_on_started = 1
+            try:
+                try:
+                    if t0:
+                        print("[PTT] wait server.started (ms={})".format(utime.ticks_ms() - t0))
+                except Exception:
+                    pass
+                self.AI_client_VC_start_requset()
+            except Exception:
+                pass
+            return
+
+        # 服务端仍在思考/TTS:先 cancel,在 btn 队列收到 canceled 后再开录(方案甲串行)
+        if self._vc_ptt_need_interrupt_wait():
+            self._vc_begin_ptt_cancel_wait("ptt_begin")
+            return
+
+        self._ptt_arm_uplink(t0)
+    def _vc_led_state_debug_enabled(self):
+        try:
+            if "vc_config" in globals() and vc_config:
+                return vc_config.get_bool("LED_STATE_DEBUG", True)
+        except Exception:
+            pass
+        return True
+    def _vc_led_should_standby_idle(self):
+        """未录音且非 TTS 播中:待机/提示音/模式切换/单对话轮间应灭灯(DES-L3 idle),勿被 FSM.turn=Listening 误导。"""
+        try:
+            if getattr(self, "VC_record_state_flag", 0) != 0:
+                return False
+            if self._vc_tts_playback_active():
+                return False
+            if getattr(self, "_ptt_await_server", 0) == 1:
+                return False
+            t = getattr(self.fsm, "turn", VCTurnState.Idle)
+            if t in (
+                VCTurnState.Thinking,
+                VCTurnState.TtsReceiving,
+                VCTurnState.Playing,
+                VCTurnState.AwaitPlayedAck,
+            ):
+                return False
+            if getattr(self, "Close_flag", 0) == 1:
+                return True
+            if getattr(self, "VC_mode", -1) == 1:
+                return True
+            k = getattr(self, "_last_tts_kind", "") or ""
+            if k in ("prompt", "boot_prompt", "mode_switch_long", "mode_switch_ptt"):
+                return True
+        except Exception:
+            pass
+        return False
+    def _vc_publish_led(self, ai_state, reason=""):
+        try:
+            if getattr(self, "_ptt_led_lock_listening", 0) == 1:
+                if ai_state != "listening":
+                    if self._vc_led_state_debug_enabled():
+                        print("[LED] publish blocked ai={} reason={} (ptt_arm_lock)".format(
+                            ai_state, reason))
+                    return
+        except Exception:
+            pass
+        try:
+            if self._vc_led_state_debug_enabled():
+                print("[LED] publish ai={} reason={} VC_mode={} rec={} turn={}".format(
+                    ai_state, reason, getattr(self, "VC_mode", -1),
+                    getattr(self, "VC_record_state_flag", -1),
+                    getattr(getattr(self, "fsm", None), "turn", "?")))
+        except Exception:
+            pass
+        try:
+            EventMesh.publish("set_led_state", {"ai_state": ai_state})
+        except Exception:
+            pass
+    def _vc_led_sync_from_fsm(self):
+        """按 FSM/真实 TTS 播放恢复 AI 灯;本地 aud.play 短音不参与。"""
+        try:
+            if getattr(self, "_ptt_led_lock_listening", 0) == 1:
+                if getattr(self, "VC_record_state_flag", 0) == 0:
+                    return
+        except Exception:
+            pass
+        try:
+            if self._vc_led_should_standby_idle():
+                self._vc_publish_led("idle", "fsm_sync/standby_idle")
+                return
+        except Exception:
+            pass
+        try:
+            if getattr(self, "VC_mode", -1) == 1 and getattr(self, "_ptt_await_server", 0) == 1:
+                self._vc_publish_led("thinking", "fsm_sync/ptt_await")
+                return
+        except Exception:
+            pass
+        try:
+            if self._vc_tts_playback_active():
+                self._vc_publish_led("playing", "fsm_sync/tts")
+                return
+        except Exception:
+            pass
+        try:
+            t = getattr(self.fsm, "turn", VCTurnState.Idle)
+            if t in (VCTurnState.Thinking, VCTurnState.TtsReceiving):
+                self._vc_publish_led("thinking", "fsm_sync/thinking")
+            elif t in (VCTurnState.Playing, VCTurnState.AwaitPlayedAck):
+                # 服务端已标记播放态但本地 PCM 尚未起播:仍显示思考,勿误绿灯
+                self._vc_publish_led("thinking", "fsm_sync/await_tts")
+            elif t in (VCTurnState.Listening, VCTurnState.SpeechEnded, VCTurnState.SpeechStreaming):
+                self._vc_publish_led("listening", "fsm_sync/listening")
+            else:
+                self._vc_publish_led("idle", "fsm_sync/idle")
+        except Exception:
+            try:
+                self._vc_publish_led("idle", "fsm_sync/fallback")
+            except Exception:
+                pass
+    def _fsm_apply_actions(self, actions):
+        try:
+            for a in actions:
+                if not a:
+                    continue
+                if a[0] == "uplink_stop":
+                    self.request_uplink_stop(a[1] if len(a) > 1 else "")
+                elif a[0] == "uplink_start":
+                    self.request_uplink_start(a[1] if len(a) > 1 else "")
+                elif a[0] == "play_start":
+                    if getattr(self, "VC_mode", -1) == 1:
+                        if self._ptt_should_skip_remote_playback():
+                            if self._vc_led_state_debug_enabled():
+                                print("[VC][serial] skip play_start (ptt_interrupt)")
+                            continue
+                    try:
+                        self.fsm.on_local_play_start()
+                    except Exception:
+                        pass
+                    EventMesh.publish("publish_AI_btnInfo", {"func": self.VC_Play_Start})
+                elif a[0] == "send_audio_played":
+                    self.request_send_audio_played()
+        except Exception:
+            pass
+    def _print_hw_diag(self):
+        # 仅诊断使用:打印当前分支能看到的麦克/音频相关配置和运行时状态。
+        # JSWM2 老分支没有 device_profile/_PROFILE,这里宽容处理
+        try:
+            profile = _PROFILE if isinstance(_PROFILE, dict) else {}
+        except Exception:
+            profile = {}
+        dev_type_profile = profile.get("dev_type", None)
+        flags = profile.get("flags", {})
+        hardware = profile.get("hardware", {})
+        product_cfg = profile.get("product_cfg", {})
+        vc_cfg = {}
+        try:
+            vc_cfg = product_cfg.get("vc", {})
+        except Exception:
+            vc_cfg = {}
+
+        print("[hw-diag] ===== AI_VC hardware diag =====")
+        print("[hw-diag] profile.dev_type        =", dev_type_profile)
+        print("[hw-diag] profile.flags           =", flags)
+        print("[hw-diag] profile.hardware        =", hardware)
+        print("[hw-diag] product_cfg.vc          =", vc_cfg)
+
+        try:
+            print("[hw-diag] VC_record_samplerate   =", self.VC_record_samplerate)
+            print("[hw-diag] VC_play_samplerate     =", self.VC_play_samplerate)
+        except Exception:
+            pass
+        try:
+            print("[hw-diag] vad_silence_runtime    =", self.vad_silence)
+        except Exception:
+            pass
+        try:
+            print("[hw-diag] record_data_save_flag  =", self.record_data_save_flag)
+            print("[hw-diag] record_max_bytes       =", self.record_max_bytes)
+        except Exception:
+            pass
+
+        try:
+            g = self.record.gain_get()
+        except Exception as e:
+            g = "gain_get_error: {}".format(e)
+        print("[hw-diag] record.gain_get         =", g)
+        try:
+            print("[hw-diag] type(record)            =", type(self.record))
+            print("[hw-diag] type(aud)               =", type(self.aud))
+        except Exception:
+            pass
+        print("[hw-diag] ===== end of hw-diag =====")
+    def __init__(self):
+        # import voiceCall
+        # voiceCall.callStart("10086")
+        # return
+        self.http_get_config_flag = 0
+        if self.http_get_config_flag == 1:
+            try:
+                self.http_get_config()#获取/更新配置参数
+            except Exception as e:
+                usys.print_exception(e)
+        self.VC_30S_timer = osTimer()
+        self.ai_key_press_audio = osTimer()
+        self.VC_30S_timer.start(30000, 1, self.VC_30S_timer_cb)
+        self.online_kws = 0#是否开启线上关键词
+        self.get_config_timer_count = 0
+        self.record_data_save_flag = 0 #是否保存录音文件
+        self.play_data_save_flag = 0#是否保存tts数据到文件
+        self.record_fd = None
+        self.play_fd = None
+        self.is_VC_ACK = 0 #是否开启ack应答模式
+        self.is_long_record = 0 #是否开启长录音模式(语音打断模式)
+        self.ai_OTA = OTA()
+        self.is_auth = 1 #是否开启dmp鉴权
+        #self.VC_mode = 1 #0:短按连续问答模式 1:长按录音模式
+        self.VC_mode = EventMesh.publish("get_storage_config","VC_mode")#
+        if self.VC_mode == None:
+            self.VC_mode = 0
+        try:
+            self.VC_mode = int(self.VC_mode)
+        except Exception:
+            self.VC_mode = 0
+        self.is_up_error= 0 #是否上传错误id
+        self.Close_flag =0 #=1时,连续对话播放完音频就退出
+        # 服务端 VAD 结束标志:收到 vad.silence 后置 1,用于硬闸门禁止继续上行音频
+        # (避免仍持续上行导致服务端推迟/抑制后续 TTS,从而一直停留在思考态蓝绿闪)
+        self._server_vad_silence = 0
+        # 单轮状态标志:用于判定 completed 是否应自动进入下一轮
+        self._turn_has_asr_completed = 0
+        self._turn_has_vad_silence = 0
+        self._turn_has_tts_started = 0
+        # VC 会话是否已进入 started(服务端 session initialized)
+        # 重要:在收到 started 前发送音频帧会触发服务端 "session not initialized" → failed → 端侧重连死循环
+        self._vc_session_started = 0
+        # 开机/提示音:本轮 started 不自动开麦(避免问候 TTS 前误进 listening)
+        self._skip_started_uplink_once = 0
+        self._last_tts_kind = ""
+        self._pending_tts_kind = ""
+        # Push-To-Talk(VAD关闭/单对话)是否已发送 audio.create
+        # 重要:协议要求 disable_vad=true 时必须 audio.create→音频帧→audio.completed
+        self._ptt_audio_created = 0
+        # PTT:松手等服务器期间保持 thinking 勿回蓝闪聆听
+        self._ptt_await_server = 0
+        # 方案甲 btnInfo 串行:打断时先 cancel,收到 canceled(或超时)后再 audio.create
+        self._vc_serial_canceling = 0
+        self._ptt_pending_arm = 0
+        self._vc_cancel_deadline_ms = 0
+        # PTT 打断:轮次号 + 开录前丢弃旧 TTS / 灯只显示聆听
+        self._ptt_arm_epoch = 0
+        self._ptt_interrupt_active = 0
+        self._ptt_led_lock_listening = 0
+        # 端侧主动发 synthesis 后到收到 tts.started 之间的窗口标志,用于阻止误开下一轮录音上行
+        self._tts_pending = 0
+        self._tts_pending_ms = 0
+        # 各类兜底的节流时间戳(避免“兜底过激/连续触发”)
+        self._fb_last_thinking_restart_ms = 0
+        self._fb_last_tts_pending_timeout_ms = 0
+        # thinking 兜底:部分情况下服务端只返回 llm.completed 而不触发 tts.started/completed,
+        # 会导致一直停留在 thinking。此时超时自动回到聆听。
+        self._llm_completed_ms = 0
+        # 语音上行统计(真实发送到 VC 发送队列的开始/结束)
+        self._uplink_active = 0
+        self._uplink_bytes = 0
+        self._uplink_start_ms = 0
+
+        # 状态机骨架(后续逐步把旧逻辑迁移进来)
+        self.fsm = VCStateMachine(self)
+
+        self.play_new_voice_flag =0 #播放文字留言flag
+
+        self.wait_first_flag = 0#是否进入未读判定(只播报一次)
+        self.wait_first_timer = osTimer()#如果播放未读留言的时候未读则不要播放了
+        self.wait_first_cnt = 0
+
+        self.wait_sort = 0#未读留言正在播第几条
+        EventMesh.subscribe("play_tts",self.play_tts)
+        EventMesh.subscribe("play_new_voice",self.play_new_voice)
+        EventMesh.subscribe("play_his_voice",self.play_his_voice)
+        EventMesh.subscribe("play_wait_voice",self.play_wait_voice)
+        EventMesh.subscribe("play_single_voice",self.play_single_voice)#不带tts的播放(闹钟)
+        EventMesh.subscribe("play_music",self.play_music)#播放音乐
+        EventMesh.subscribe("play_trans_tts",self.play_trans_tts)#接收翻译tts
+        EventMesh.subscribe("set_vad_silence",self.set_vad_silence)
+        EventMesh.subscribe("set_wait_first_flag",self.set_wait_first_flag)
+        EventMesh.subscribe("kws_even",self.kws_even)
+        EventMesh.subscribe("change_volume",self.change_volume)    
+        EventMesh.subscribe("find_cmd",self.find_cmd) #查询命令
+        EventMesh.subscribe("NLP_set_vad_silence",self.NLP_set_vad_silence) #设置聆听间隔
+        EventMesh.subscribe("change_voice",self.change_voice) #改变音色
+        EventMesh.subscribe("Close_VC",self.Close_VC) #外部结束对话
+        EventMesh.subscribe("get_recv_voice",self.get_recv_voice) #获取留言播放状态
+        EventMesh.subscribe("set_recv_voice",self.set_recv_voice) #设置留言播放状态
+        self.voice_timer = osTimer()#语音留言10S按下按键确定是否播放的判断定时器
+        self.voice_cnt = 0 #留言定时器计数
+        self.voicerupt_flag = 0#留言选择按键接听标志位
+        self.wait_voicerupt_flag = 0#未读留言选择按键接听标志位
+        self.recv_voice = 0 #是否收到留言
+        self.voice_notice = 0 #收到留言并播放则置为1,接下来要播放提示词
+        self.voice_choose_flag = 0#当tts和语音留言冲突时,为了防止语音地址被误清,做标志位在close做判断
+
+        self.revoice_timer = osTimer()#语音留言10S按下按键确定是否回复的判断定时器
+        self.revoice_cnt = 0 #回复留言定时器计数
+        self.revoicerupt_flag = 0#留言选择按键接听标志位
+
+        self.music_timer = osTimer()#音乐播放定时器,等待tts播完才放音乐
+        self.music_cnt = 0 #留言定时器计数
+
+        self.his_timer = osTimer()#历史留言播放定时器,等待tts播完再播留言
+        self.his_cnt = 0#历史留言定时器技术
+
+        self.wait_timer = osTimer()#未读留言播放定时器,等待tts播完再播留言
+        self.wait_cnt = 0#未读留言定时器技术
+
+        self.error_data = []
+        self.asr_taskid  = ""
+        self.VC_taskid  = ""
+        self.VC_server = 2 #0柒牌正式服务器,1:测试服务器1,2:测试服务器2
+        self.voice_flag = 0
+
+        #会议记录标志
+        self.meet_flag = 0
+        #AI翻译标志
+        self.trans_flag = 0
+
+        #历史留言查看判断标志
+        self.history_voice_flag = 0#公共变量判断 
+        #60S等待留言判断标志
+        self.wait_voice_flag = 0#公共变量判断 
+        EventMesh.publish("AI_alarm_open")#
+        self.server_ip = EventMesh.publish("get_storage_config","server_ip")#
+        if self.server_ip == None:
+            if self.VC_server == 1:# 
+                self.server_ip="118.145.158.207:22083"
+            elif self.VC_server == 2:# 
+                self.server_ip="118.145.158.207:22082"
+            elif self.VC_server == 4:# 
+                self.server_ip="118.145.158.207:22084"
+            elif self.VC_server == 5:# 
+                self.server_ip="amp-wss.raisound.com"
+            elif self.VC_server == 6:# 
+                self.server_ip="118.145.158.207:22080"
+            elif self.VC_server == 7:# 
+                self.server_ip="118.145.158.207:22080"
+            elif self.VC_server == 8:# 
+                self.server_ip="115.190.94.208:22080"
+            elif self.VC_server == 86:# 
+                self.server_ip="118.145.158.207:22086"    
+            else:
+                #self.server_ip="sevenpin.qipai.com.cn"
+                self.server_ip="118.145.158.207:22082"
+            # 默认走 constants.AMP_SERVER(由 default_srv.py 统一管理,可在 usr 覆盖)
+            try:
+                if constants.AMP_SERVER:
+                    self.server_ip = constants.AMP_SERVER
+            except Exception:
+                pass
+
+        #开机提示音
+        self.power_on_tone = EventMesh.publish("get_storage_config","power_on_tone")#
+        try:
+            if "vc_config" in globals() and vc_config:
+                if not vc_config.get_bool("POWER_ON_TONE_ENABLED", True):
+                    self.power_on_tone = None
+        except Exception:
+            pass
+        if self.power_on_tone == None:
+            try:
+                if "vc_config" in globals() and vc_config:
+                    self.power_on_tone = vc_config.get_attr("POWER_ON_TONE_DEFAULT", None)
+            except Exception:
+                pass
+
+        self.VC_record_TYPE = VC_record_TYPE_PCM #0:PCM 1:AMR
+        self.VC_play_TYPE = VC_play_TYPE_PCM #0:PCM 1:mp3 
+        self.VC_record_samplerate = 16000
+        self.VC_play_samplerate = 16000
+        self.VAD_data_len = 640 #10ms数据
+        self.is_VAD = 0#=1打开vad打断
+        # ARG_sample_rate,             // 采样率
+        # ARG_channel,                // 通道数
+        # ARG_frame_size,              // 每帧字节数
+        # ARG_energy_threshold,        // 能量阈值
+        # ARG_voice_time,              // 语音持续时间阈值(ms)
+        # ARG_silence_time,            // 静音持续时间阈值(ms)
+        # ARG_silence_start_threshold, // 静音开始阈值(ms)
+        self.vad = None
+        if self.is_VAD == 1:
+            self.vad = VAD.VAD(self.VC_record_samplerate ,1,self.VAD_data_len,600,100,300,100)
+        self.speech_rate = EventMesh.publish("get_storage_config","speech_rate")#语速配置
+        if self.speech_rate == None:
+            self.speech_rate = 0
+        
+        self.vad_silence = EventMesh.publish("get_storage_config","vad_silence")#语速配置
+        if self.vad_silence == None:
+            try:
+                if "vc_config" in globals() and vc_config:
+                    self.vad_silence = vc_config.get_int("VAD_SILENCE_DEFAULT", 300)
+                else:
+                    self.vad_silence = 300
+            except Exception:
+                self.vad_silence = 300
+
+        self.VC_TTS_voice_type = EventMesh.publish("get_storage_config","VC_TTS_voice_type")#音色配置
+        if self.VC_TTS_voice_type == None :
+            try:
+                if "vc_config" in globals() and vc_config:
+                    cfg = vc_config.get_attr("VC_TTS_VOICE_TYPE_DEFAULT", None)
+                    if isinstance(cfg, dict):
+                        self.VC_TTS_voice_type = cfg
+            except Exception:
+                pass
+        # 若配置未提供,沿用原逻辑内置兜底(保持兼容)
+        if self.VC_TTS_voice_type == None:
+            self.VC_TTS_voice_type = {
+                "女":{"bot_name":"柒妹妹","voice":"zh_female_wanwanxiaohe_moon_bigtts","lang":"zh-cn"},
+                "男":{"bot_name":"柒先生","voice":"zh_male_wennuanahu_moon_bigtts","lang":"zh-cn"},
+                "童":{"bot_name":"柒乐乐","voice":"ICL_zh_female_huoponvhai_tob","lang":"zh-cn"},
+            }
+            
+            # self.VC_TTS_voice_type = {0:"zh_female_xinlingjitang_moon_bigtts",1:"zh_male_wennuanahu_moon_bigtts",2:"ICL_zh_female_huoponvhai_tob",
+            #                           3:"aliyun::longjiaxin",4:"zh_female_daimengchuanmei_moon_bigtts"}
+            #self.VC_TTS_voice_type = ["aliyun::longxiaoxia","aliyun::longnan","aliyun::longling"]
+            
+            #EventMesh.publish("set_storage_config",["VC_TTS_voice_type",self.VC_TTS_voice_type])
+
+        try:
+            self.VC_TTS_voice_key = EventMesh.publish("get_storage_config","VC_TTS_voice_key")#音色配置
+            if self.VC_TTS_voice_key == None :
+                try:
+                    if "vc_config" in globals() and vc_config:
+                        self.VC_TTS_voice_key = vc_config.get_attr("VC_TTS_VOICE_KEY_DEFAULT", None)
+                except Exception:
+                    pass
+                if not self.VC_TTS_voice_key:
+                    self.VC_TTS_voice_key = "男"
+            if self.VC_TTS_voice_key in self.VC_TTS_voice_type:
+                pass
+            else:
+                self.VC_TTS_voice_key = list(self.VC_TTS_voice_type.keys())[0]
+            print("音色配置:{}:{}\r\n\r\n".format(self.VC_TTS_voice_key,self.VC_TTS_voice_type[self.VC_TTS_voice_key]))
+            self.VC_TTS_volume =  EventMesh.publish("get_storage_config","VC_TTS_volume")#音量配置
+        except Exception as e:
+            usys.print_exception(e)
+        #配置词
+        self.commands_mode = 2 #=1用大模型判断命令,=0用服务器本地插件判断命令 =2用设备本地判断方式
+        self.commands = []
+        if self.VC_TTS_volume == None:
+            self.VC_TTS_volume = 100
+        #可ota配置的参数变量
+        self.emotion = EventMesh.publish("get_storage_config","emotion")#情绪
+        if self.emotion == None:
+            self.emotion = ""
+        self.ota_update_notice = EventMesh.publish("get_storage_config","ota_update_notice")#OTA升级提示语
+        if self.ota_update_notice == None:
+            self.ota_update_notice = "" 
+        self.AI_audio_queue_thread_id = 0
+        self.AI_audio_queue_thread_lock = 0
+        self.AI_VC_send_data_queue_thread_id = 0
+        self.AI_VC_send_data_queue_thread_lock = 0
+        self.AI_ASR_send_data_queue_thread_id = 0
+        self.AI_ASR_send_data_queue_thread_lock = 0
+        self.AI_record_tmp_queue = Queue(100)
+        self.AI_ASR_send_data_queue = Queue(200)
+        self.AI_VC_send_data_queue = Queue(100)
+        self.AI_audio_queue = Queue(100)
+        #self.AI_WS_connect_Queue = Queue(100) #释放用
+        self.AI_TTS_str = ""
+        self.AI_ASR_str = ""
+        self.AI_ASR_flag = 0
+        self.AI_ASR_str_tmp = ""
+        self.AI_ASR_send_buf_len = 0
+        self.AI_ASR_text_tmp = ""
+        self.AI_ASR_text_repet_count = 0
+        # #开始线程
+        # if self.AI_audio_queue_thread_id ==0:
+        #     self.AI_audio_queue_thread_id = _thread.start_new_thread(self.AI_audio_queue_thread, ())
+        # else:
+        #     print("AI_VC_start :AI_audio_queue_thread_id != 0 ")
+        # if self.AI_VC_send_data_queue_thread_id == 0:    
+        #     self.AI_VC_send_data_queue_thread_id = _thread.start_new_thread(self.AI_VC_send_data_queue_thread, ())
+            
+        self.VC_dialog_id = ""#
+        self.VC_message_id=None
+        self.VC_task_id =None
+        self.AI_client_VC = None
+        self.AI_client_VC_recv_id = 0
+        self.AI_client_VC_recv_lock = 0
+        self.AI_client_VC_isconnect =0
+        self.AI_client_VC_debug =0
+
+        self.asr_state = 0  # 预留:ASR 侧状态(当前未参与分支)
+        self.key_state = 0
+
+        self.AI_client_asr = None
+        self.AI_client_asr_recv_id = 0
+        self.AI_client_asr_recv_lock = 0
+        self.AI_client_asr_isconnect =0
+        self.AI_ASR_timer =  osTimer()
+        self.AI_ASR_timer_count = 0
+        self.AI_client_asr_debug =0
+        self.AI_ASR_check_timeout = 0
+        self.AI_ASR_error_timeout = 0
+        self.VC_Thinking_timerout = 0
+
+
+        self.record_pcm = None
+        self.play_pcm = None
+        self.VC_record_thread_id = 0
+        self.VC_record_thread_lock = 0
+        self.VC_pingping_timerout = 0
+        self.asr_pingping_timerout = 0
+        self.factory_timer = osTimer()
+        self._1S_timer = osTimer()
+        self.Audio_buf_size = 20*1024
+        self.record = audio.Record()
+        #self.record.end_callback(self.record_callback)
+        self.record.gain_set(4,10)
+        #self.record.gain_set(4,12)
+        self.aud = audio.Audio(0)
+        self.aud.setCallback(self.audio_cb)
+        # 确保 record/aud 已创建后再打印硬件/音频诊断信息(需要 gain_get/type(record))
+        try:
+            self._print_hw_diag()
+        except Exception as e:
+            usys.print_exception(e)
+        #self.aud.setVolume(self.VC_TTS_volume //10)
+        self.audio_play_state = 1 #音频播放完状态
+        self.close_timerout = 0
+        self._idle_deep_sec = 0
+        self._vc_idle_deep_armed_suppress = 0
+        self.LocalRespondingEnded_timerout = 0
+
+        self.VC_Play_flag = 0
+        self.play_flag = 0
+        self.VC_record_state_flag = 0
+        self.AI_key_long_press_flag = 0
+        self._asr_completed_tone_done = 0  # asr.completed 本地 think 提示每轮仅一次
+        self._last_asr_result_text = ""  # 保留最近 asr.result,防 VC_Speech_End 早于 asr.completed 清空
+        self._vc_last_auto_uplink_ms = 0  # 长对话自动开麦防抖
+        self._vc_local_play_active = 0  # aud.play 本地短音占用(与采集互斥)
+        self._vc_local_play_until_ms = 0
+        self._vc_pending_interrupt_uplink = 0  # 长对话短按打断:等 canceled 再开麦
+        self.websocket_error_count = 0
+        self.AI_new_voice_url = "" #未读语音地址/音乐播放地址/闹钟播放标志
+        self.music_play_flag = 0 #音乐播放标志
+        self.alarm_play_flag = 0 #闹钟播放标志
+        self.tts_in_data_len = 0
+        self.tts_out_data_len = 0
+        self._tts_first_write_ms = 0
+        self._tts_completed_wall_ms = 0
+        self._tts_play_check_log_ms = 0
+        self._need_audio_played_ack = 0#tts.completed 后需在本地播报结束后上报 audio.played;勿用 tts_in/out:tts.completed 会再调 VC_Play_Start 把二者清零
+        self.VC_satrt_flag = 0
+        self.VC_stop_play_audio_flag = 0
+        self.VC_satrt_kws_flag = 0
+        self.session_id = ""
+        self.stop_voice_timer=osTimer()
+        self.pwk_cnt_voice = 0 #不听语音后,积攒的次数,需要一起播放
+        self.pwk_notice_flag = 0#是否处于积攒留言状态(用来实现单次提醒)
+        self.is_noread_voice = EventMesh.publish("get_storage_config","is_noread_voice")
+        if self.is_noread_voice == None:
+            self.is_noread_voice = 0#是否还有未读留言(开机判断)
+        self.is_ruppt_tts = 0#是否已经打断tts播放
+        EventMesh.subscribe("AI_key_press", self.AI_key_press)
+        EventMesh.subscribe("BT_AI_key_press", self.BT_AI_key_press)
+        EventMesh.subscribe("stop_AI_record", self.stop_AI_record)
+        EventMesh.subscribe("AI_key_release", self.AI_key_release)
+        EventMesh.subscribe("pwk_key_double_press", self.pwk_key_double_press)
+        EventMesh.subscribe("AI_key_long_press", self.AI_key_long_press)
+        EventMesh.subscribe("pwk_key_release", self.pwk_key_release)
+        EventMesh.subscribe("get_AI_audio_queue", self.get_AI_audio_queue)
+        EventMesh.subscribe("get_VC_satrt_flag", self.get_VC_satrt_flag)
+        EventMesh.subscribe("get_vc_stream_active", self.get_vc_stream_active)
+        EventMesh.subscribe("get_vc_tts_playback_active", self.get_vc_tts_playback_active)
+        EventMesh.subscribe("battery_priority_mute_vc", self.battery_priority_mute_vc)
+        EventMesh.subscribe("VC_error", self.VC_error)
+        EventMesh.subscribe("audio_test", self.audio_test)
+        EventMesh.subscribe("audio_test_close", self.audio_test_close)
+        EventMesh.subscribe("set_record_data_save_flag", self.set_record_data_save_flag)
+        EventMesh.subscribe("set_VC_TTS_voice_type", self.set_VC_TTS_voice_type)
+        EventMesh.subscribe("SET_TTS_VOLUME", self.set_tts_volume)
+        EventMesh.subscribe("set_VC_stop_play_audio_flag", self.set_VC_stop_play_audio_flag)
+        EventMesh.subscribe("set_yuyinaudio_flag",self.set_yuyinaudio_flag)
+        EventMesh.subscribe("get_yuyinaudio_flag",self.get_yuyinaudio_flag)
+        EventMesh.subscribe("set_pwk_cnt_voice",self.set_pwk_cnt_voice)
+        EventMesh.subscribe("get_pwk_cnt_voice",self.get_pwk_cnt_voice)
+        EventMesh.subscribe("exit_voice_message_mode",self.exit_voice_message_mode) #超时未退出语音留言模式
+        EventMesh.subscribe("come_meet_mode",self.come_meet_mode)#进入会议模式
+        EventMesh.subscribe("exit_meet_mode",self.exit_meet_mode)#退出会议模式
+        EventMesh.subscribe("come_trans_mode",self.come_trans_mode)#进入翻译模式
+        EventMesh.subscribe("exit_trans_mode",self.exit_trans_mode)#退出翻译模式
+        EventMesh.subscribe("set_trans_flag",self.set_trans_flag)#设置翻译状态
+        EventMesh.subscribe("get_audio_play_state",self.get_audio_play_state)
+        EventMesh.subscribe("set_voicerupt_flag",self.set_voicerupt_flag)#按键中断触发留言播放
+        EventMesh.subscribe("set_wait_voicerupt_flag",self.set_wait_voicerupt_flag)#按键中断触发未读留言播放 
+        EventMesh.subscribe("get_recv_voice",self.get_recv_voice)#获取是否收到留言
+        EventMesh.subscribe("rate_change",self.rate_change)#直接修改语速
+        EventMesh.subscribe("get_history_voice_flag",self.get_history_voice_flag)#获取查找历史留言状态
+        EventMesh.subscribe("set_history_voice_flag",self.set_history_voice_flag)#修改查找历史留言状态
+        EventMesh.subscribe("clean_unread_vocie",self.clean_unread_vocie)#修改查找历史留言状态
+        EventMesh.subscribe("get_wait_voice_flag",self.get_wait_voice_flag)#获取60S等待留言播放定时器状态
+        EventMesh.subscribe("set_wait_voice_flag",self.set_wait_voice_flag)#修改60S等待留言播放定时器状态
+        EventMesh.subscribe("rate_move",self.rate_move)#NLP修改语速
+        EventMesh.subscribe("find_rate",self.find_rate)#NLP查询语速
+        EventMesh.subscribe("set_pwk_notice_flag",self.set_pwk_notice_flag)#设置未读状态
+        # EventMesh.subscribe("get_voicerupt_flag",self.get_voicerupt_flag)#获取留言播放状态
+        EventMesh.subscribe("chenggong_tone",self.chenggong_tone)
+        
+        pass
+        EventMesh.publish("set_led_state", {"ai_state": "idle"})
+        self.dialog_attributes_provider =  EventMesh.publish("get_storage_config","dialog_attributes_provider")#智能体类型配置
+        if self.dialog_attributes_provider == None:
+            self.dialog_attributes_provider = "coze"
+        self.outbound_attributess_provider = EventMesh.publish("get_storage_config","outbound_attributess_provider")#tts服务选择
+        if self.outbound_attributess_provider == None:
+            self.outbound_attributess_provider = "volcengine"
+        self.inbound_attributess_provider = EventMesh.publish("get_storage_config","inbound_attributess_provider")#tts服务选择
+        if self.inbound_attributess_provider == None:
+            self.inbound_attributess_provider = "volcengine"
+        
+        self.bot_name = "柒先生"
+        try:
+            if "bot_name" in self.VC_TTS_voice_type[self.VC_TTS_voice_key]:
+                self.bot_name = self.VC_TTS_voice_type[self.VC_TTS_voice_key]["bot_name"]
+            else:
+                print("not bot_name!!!!!!!!!!!!!!!!!!!!!!")
+        except Exception as e:
+            usys.print_exception(e)
+        try:
+            bt_ota_flag=0
+            try:
+                if "vc_config" in globals() and vc_config and vc_config.get_bool("BT_OTA_ENABLED", False):
+                    bt_ota_flag = self.ai_OTA.bt_ota()#先检查蓝牙是否要升级
+            except Exception:
+                pass
+
+            cat1_ota_flag = self.ai_OTA.min_sys_ota()
+            if bt_ota_flag==1 or cat1_ota_flag == 1:
+            #if 0:    
+                if self.ota_update_notice != "":
+                    self.AI_TTS_str = self.ota_update_notice
+                else:
+                    self.AI_TTS_str = "检测到有新版本需要升级啦!升级时可千万别断电或乱按哦,升级完成后设备会自动重启,重启就代表升级成功"
+                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) 
+                self.Close_flag = 1
+            # if self.ai_OTA.min_sys_ota()==2:
+            #     self.AI_TTS_str = "检测到有新版本需要升级啦!电量低于40%,无法保证升级正常执行,请充满电后重启设备再次升级!"
+            #     EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) 
+            else:
+                try:
+                    self.ai_OTA.ota_result()
+                    self.ai_OTA.pots_ICCID()
+                except Exception as e:
+                    usys.print_exception(e)
+                #开机提示语,如果联网失败,直接提示信号弱
+                
+                
+                #EventMesh.publish("get_storage_config",["powerRestart",1])
+                powerRestart =  EventMesh.publish("get_storage_config","powerRestart")#=1非正常重启
+                if powerRestart == None:
+                    powerRestart = 0
+                if powerRestart == 1:
+                    EventMesh.publish("set_storage_config",["powerRestart",0])
+                    
+                else:
+                    pass
+                    self.AI_TTS_str = self.power_on_tone.replace("bot_name", self.bot_name)
+                    print("开机提示:{}".format(self.AI_TTS_str))
+                    try:
+                        self._pending_tts_kind = "boot_prompt"
+                        self._last_tts_kind = "boot_prompt"
+                        self._skip_started_uplink_once = 1
+                    except Exception:
+                        pass
+                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start})   
+
+        except Exception as e:
+            usys.print_exception(e)
+    def set_pwk_notice_flag(self,event,msg):#设置未读状态
+        self.pwk_notice_flag =  msg
+    def get_wait_voice_flag(self,event,msg):
+        return self.wait_voice_flag
+    def set_wait_voice_flag(self,event,msg):
+        # print("未读留言标记修改为:{}".format(msg))
+        self.wait_voice_flag = msg
+    def get_history_voice_flag(self,event,msg):
+        return self.history_voice_flag
+    def set_history_voice_flag(self,event,msg):
+        # print("历史留言标记修改为:{}".format(msg))
+        self.history_voice_flag = msg
+    def clean_unread_vocie(self,event,msg):
+        self.pwk_cnt_voice = 0
+        self.pwk_notice_flag = 0
+        EventMesh.publish("set_app_new_voice_flag",0)
+    def audio_test(self,event = None, msg = None):
+        self._uplink_request_start("audio_test")
+        self.VC_Play_Start()      
+    def audio_test_close(self,event = None, msg = None):
+        self._uplink_request_stop("audio_test_close")
+        self.VC_Play_End()    
+        if self.record_data_save_flag == 1:
+            if self.record_fd :
+                self.record_fd.close()
+            self.record_fd = None
+    def post_processor_after_instantiation(self):
+        pass
+    def get_VC_satrt_flag(self, event, msg):
+        return self.VC_satrt_flag
+    def check_vc_start(self):
+        print("check_vc_start:{} {} {} {}".format(self.VC_satrt_flag,self.VC_record_state_flag,self.AI_client_VC_isconnect,self.VC_Play_flag))
+        if self.VC_satrt_flag == 0:
+            if self.VC_record_state_flag != 0 or self.AI_client_VC_isconnect == 1 or  self.VC_Play_flag == 1:
+                self.Close()
+    def VC_30S_timer_cb(self, *args): #
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_30S_timer_cb_}) 
+    def VC_30S_timer_cb_(self):    
+        self.get_config_timer_count += 1
+        if self.get_config_timer_count >= 2*60*2:#2小时更新配置参数
+            self.get_config_timer_count = 0
+            if self.http_get_config_flag == 1:
+                try:
+                    self.http_get_config()#获取/更新配置参数
+                except Exception as e:
+                    usys.print_exception(e)
+        self.check_vc_start()#30秒检查一下有没有关闭
+    def ud_error(self,error_data):
+        response = None
+        if self.is_up_error == 1:
+            try:
+                # currentVersion = versions.Build_Time
+                # currentVersion = "7PIN-"+currentVersion[:10]+"-"+currentVersion[11:-3]
+                url = "http://{}/health?task_id={}".format(self.server_ip,error_data[0]+error_data[1])
+                print(url)
+                response = request.get(url,timeout=5)
+                if response.status_code == 200:
+
+                    print("ud_error:ok")
+                else:
+                    print("ud_error  error!!!!")
+                    for i in response.text:
+                        print(i)
+                response.close()
+            except Exception as e:
+                usys.print_exception(e)
+                if response != None:
+                    response.close()
+    def http_get_config(self, event=None, msg=None):
+        print("http_get_config")
+
+        response = None
+        #http://180.184.206.142/bz-api/dev/config?deviceId=xxx
+        try:
+            url = (constants.DMP_BASE + "/dev/config/{}").format(modem.getDevImei())
+                
+                
+            print(url)
+            response = request.get(url,timeout=5)
+            if response.status_code == 200:
+                data = response.json()
+                print("http_get_config:{}".format(data))
+                
+                if data:
+                    try:
+                        if data["code"] == 20000:#有配置
+                            if data["data"] :
+                                if "version" in data["data"]:
+                                    config_version = EventMesh.publish("get_storage_config","config_version")#音量配置
+                                    if config_version != data["data"]["version"]:
+                                        config_version = data["data"]["version"]
+                                        EventMesh.publish("set_storage_config",["config_version",config_version])
+                                        if "VC_TTS_voice_type" in data["data"]:
+                                            self.VC_TTS_voice_type = data["data"]["VC_TTS_voice_type"]
+                                            EventMesh.publish("set_storage_config",["VC_TTS_voice_type",self.VC_TTS_voice_type])
+                                            print("配置VC_TTS_voice_type成功")
+                                        if "server_ip" in data["data"]:
+                                            self.server_ip = data["data"]["server_ip"]
+                                            EventMesh.publish("set_storage_config",["server_ip",self.server_ip])
+                                            print("配置server_ip成功")
+                                        if "power_on_tone" in data["data"]:
+                                            self.power_on_tone = data["data"]["power_on_tone"]
+                                            EventMesh.publish("set_storage_config",["power_on_tone",self.power_on_tone])
+                                            print("配置power_on_tone成功")
+                                        if "dialog_attributes_provider" in data["data"]:
+                                            self.dialog_attributes_provider = data["data"]["dialog_attributes_provider"]
+                                            EventMesh.publish("set_storage_config",["dialog_attributes_provider",self.dialog_attributes_provider])
+                                        if "outbound_attributess_provider" in data["data"]:
+                                            self.outbound_attributess_provider = data["data"]["outbound_attributess_provider"]
+                                            EventMesh.publish("set_storage_config",["outbound_attributess_provider",self.outbound_attributess_provider])
+                                            print("配置outbound_attributess_provider成功")
+                                        if "inbound_attributess_provider" in data["data"]:
+                                            self.inbound_attributess_provider = data["data"]["inbound_attributess_provider"]
+                                            EventMesh.publish("set_storage_config",["inbound_attributess_provider",self.inbound_attributess_provider])
+                                            print("配置inbound_attributess_provider成功")
+                                        if "speech_rate" in data["data"]:
+                                            self.speech_rate = EventMesh.publish("SET_SPEECH_RATE",float(data["data"]["speech_rate"]))
+                                            EventMesh.publish("set_storage_config",["speech_rate",self.speech_rate])
+                                            print("配置speech_rate成功")
+                                        if "emotion" in data["data"]:
+                                            self.emotion = data["data"]["emotion"]
+                                            EventMesh.publish("set_storage_config",["emotion",self.emotion])
+                                            print("配置emotion成功")
+                                        if "ota_update_notice" in data["data"]:
+                                            self.ota_update_notice =data["data"]["ota_update_notice"]
+                                            EventMesh.publish("set_storage_config",["ota_update_notice",self.ota_update_notice])
+                                            print("配置ota_update_notice成功")
+                                        if "VC_TTS_volume" in data["data"]:
+                                            self.VC_TTS_volume =int(data["data"]["VC_TTS_volume"])
+                                            EventMesh.publish("set_storage_config",["VC_TTS_volume",self.VC_TTS_volume])
+                                            print("配置VC_TTS_volume成功")
+                                            
+                    except Exception as e:
+                            usys.print_exception(e)
+            else:
+                print("status_code:{}".format(response.status_code))
+                print("http_get_config:error!!!!!!!!!!")
+            response.close()
+        except Exception as e:
+            usys.print_exception(e)
+            if response != None:
+                response.close()
+    def get_yuyinaudio_flag(self, event=None, msg = None):
+        return self.voice_flag
+    def set_yuyinaudio_flag(self, event=None, msg = None):
+        self.voice_flag=0
+    def get_pwk_cnt_voice(self, event=None, msg = None):
+        return self.pwk_cnt_voice
+    def set_pwk_cnt_voice(self, event=None, msg = None):
+        self.pwk_cnt_voice=msg
+        print("设置未读次数为:{}".format(self.pwk_cnt_voice))
+    def get_meetaudio_flag(self, event=None, msg = None):
+        return self.meet_flag
+    def set_meetaudio_flag(self, event=None, msg = None):
+        self.meet_flag = 0
+    def come_meet_mode(self, event=None, msg = None):
+        if self.meet_flag == 0: #还是为0就提示进入会议纪要模式
+            EventMesh.publish("send_meet_open_flag_to_iot")#创建会议
+            self.meet_flag=1
+            exit_tts ="开启会议纪要,正在录音"
+            EventMesh.publish("play_tts",[exit_tts,1])
+            utime.sleep_ms(4000)
+            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_key_press})  
+        pass
+    def exit_meet_mode(self, event=None, msg = None):
+        if self.meet_flag == 1: #还是为1就提示已退出会议纪要模式
+            EventMesh.publish("send_meet_end_flag_to_iot")#结束会议
+            self.meet_flag=0
+            EventMesh.publish("stop_voice_record") #关闭语音录音
+            exit_tts ="录音结束,已退出会议纪要模式"
+            EventMesh.publish("play_tts",[exit_tts,1])
+        pass
+    def come_trans_mode(self, event=None, msg = None):
+        if self.trans_flag == 0: #还是为0就提示进入AI翻译模式
+            EventMesh.publish("send_trans_come_flag_to_iot")#进入翻译
+        pass
+    def exit_trans_mode(self, event=None, msg = None):
+        if self.trans_flag == 1: #还是为1就提示已退出AI翻译模式        
+            EventMesh.publish("stop_voice_record") #关闭语音录音
+            exit_tts ="已退出AI翻译模式"
+            EventMesh.publish("play_tts",[exit_tts,1])
+            self.trans_flag=0
+            EventMesh.publish("send_trans_end_flag_to_iot")#退出翻译
+        pass
+    def set_trans_flag(self,event,msg):
+        self.trans_flag = msg
+    def exit_voice_message_mode(self, event=None, msg = None):
+        if self.voice_flag == 1: #还是为1就提示已退出会议纪要模式
+            self.voice_flag=0
+            exit_tts ="已退出语音留言模式"
+            EventMesh.publish("play_tts",[exit_tts,1])
+        pass
+    def ai_key_press_audio_cb1(self, event=None, msg = None):
+        print("播放按键音!!!!!!!!!!")
+        # EventMesh.publish("audio_montage_play",["chenggong.mp3"])  
+        # self.chenggong_tone()
+        self.aud.aud_tone_play(10,400)#播放按键音
+    def ai_key_press_audio_cb2(self, event=None, msg = None):
+        EventMesh.publish("audio_montage_play",["chenggong.mp3"])  
+    def AI_key_press(self, event=None, msg = None):#按键按下
+        # 按键时打印 LED 聚合状态/最终模式(用于定位“绿灯不灭/闪一下”)
+        try:
+            if "vc_config" in globals() and vc_config and vc_config.get_bool("KEY_LED_DEBUG", False):
+                try:
+                    led = EventMesh.publish("get_led_debug_state")
+                except Exception:
+                    led = None
+                try:
+                    t = getattr(self.fsm, "turn", None)
+                except Exception:
+                    t = None
+                print("[KEY] AI_key_press VC_mode={} {} turn={} led={}".format(
+                    getattr(self, "VC_mode", -1), self._vc_fsm_log_snip(), t, led))
+        except Exception:
+            pass
+        # 单对话 PTT / 长对话分支(按键短按提示音已关,避免与开录等提示音叠在一起)
+        try:
+            if self._ai_key_press_ptt_mode():
+                return
+        except Exception as e:
+            usys.print_exception(e)
+        try:
+            if self._ai_key_press_long_dialogue_interrupt():
+                return
+        except Exception as e:
+            usys.print_exception(e)
+        try:
+            if self._ai_key_press_long_dialogue_when_session_ready():
+                return
+        except Exception as e:
+            usys.print_exception(e)
+        try:
+            if self._ai_key_press_long_dialogue_wake():
+                EventMesh.publish(constants.EVENT.SYSTEM_UP)
+                return
+        except Exception as e:
+            usys.print_exception(e)
+
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
+        if EventMesh.publish("get_sos_flag")== 1:
+            return
+        if self.voice_flag == 1: #语音留言模式
+            self.stop_voice_timer.stop() #关闭语音留言模式定时器
+           # EventMesh.publish("stop_AI_record")
+            self.Close()
+            self.revoice_cnt = 0
+            self.revoice_timer.stop()#回复定时器关闭,计数器清零
+            EventMesh.publish("start_voice_record",2)#开始语音录音
+            return
+        elif self.meet_flag == 1:#会议纪要模式
+            self.Close()
+            EventMesh.publish("start_voice_record",3)#开始会议录音
+            return
+        elif self.trans_flag == 1:#AI翻译模式
+            return
+        
+        EventMesh.publish("stop_voice_record") #关闭语音录音
+        if self.VC_mode == 0:
+            print("[KEY] AI_key_press fallback -> AI_VC_start ws={} started={}".format(
+                getattr(self, "AI_client_VC_isconnect", -1),
+                getattr(self, "_vc_session_started", -1)))
+            try:
+                self.Close_flag = 0
+            except Exception:
+                pass
+            self.get_config_timer_count = 0
+            self.key_state = 1
+            self.AI_TTS_str = ""
+            self.AI_ASR_str = ""
+            self.AI_ASR_str_tmp = ""
+            EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_VC_start})
+            self.AI_key_long_press_flag = 0
+            return
+        print("AI_key_press: unhandled mode branch VC_mode={}".format(
+            getattr(self, "VC_mode", -1)))
+        self.AI_key_long_press_flag = 0
+    def BT_AI_key_press(self, event=None, msg = None):#按键按下
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
+        if EventMesh.publish("get_sos_flag")== 1:
+            return
+        if self.voice_flag == 1: #语音留言模式
+            tts_str  = "语音留言模式,长按按钮开始录音,松开按钮结束" 
+            EventMesh.publish("play_tts",[tts_str,1])
+            return
+        
+        EventMesh.publish("stop_voice_record") #开启静默需要关闭
+        print("AI_key_press")
+        self.get_config_timer_count = 0
+        self.key_state = 1
+        # EventMesh.publish("audio_montage_play",["chenggong.mp3"])  
+        # self.chenggong_tone() #播放按键音
+        # 长对话:蓝牙按键同单按
+        # - 对话进行中:打断/续聊(复用 dialog_id)
+        # - 已停止/未 started:走新会话流程
+        try:
+            if self.VC_mode == 0:
+                if getattr(self, "AI_client_VC_isconnect", 0) == 1 and getattr(self, "_vc_session_started", 0) == 1:
+                    EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_RequestToSpeak})
+                    return
+        except Exception:
+            pass
+
+        self.Close()
+        
+        if self.VC_mode == 1: #长按对话模式 接收到蓝牙耳机按键事件
+            
+            tts_str = "请您按住圆形的按钮开始讲话,讲完松开即可 "
+            EventMesh.publish("play_tts",[tts_str,1])
+            return
+        # if dev_type == "7PIN":
+        #     self.chenggong_tone()
+        #self.aud.aud_tone_play(10,200)#播放按键音
+        self.AI_TTS_str = ""#"你好,有什么吩咐"
+        self.AI_ASR_str = ""
+        self.AI_ASR_str_tmp = ""
+        # if self.VC_state != VC_State_None:
+        #     EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) 
+        #     EventMesh.publish("publish_AI_btnInfo",{"func":utime.sleep_ms,'data':(10)})
+        self._uplink_request_stop("BT_AI_key_press/compat2")
+        self._uplink_request_start("BT_AI_key_press/compat2")
+        
+        self.AI_VC_start() 
+        
+        
+        # self.Close()
+        # self.AI_VC_start()
+        self.AI_key_long_press_flag = 0
+    def stop_AI_record(self, event=None, msg=None):#停止AI对话
+        if self.VC_satrt_flag == 1:
+            self.get_config_timer_count = 0
+            self.AI_TTS_str = ""#"你好,有什么吩咐"
+            self.AI_ASR_str = ""
+            self.AI_ASR_str_tmp = ""
+            self.AI_key_long_press_flag = 0
+            EventMesh.publish("publish_AI_btnInfo",{"func":self.Close})
+            self.request_uplink_stop("stop_AI_record")
+            count = 0
+            while True:
+                if count >= 20 or self.VC_satrt_flag == 0:
+                    break
+                count+=1
+                utime.sleep_ms(500)
+    def exit_voice_message_timer(self,*argc):
+        EventMesh.publish("exit_voice_message_mode")
+    def AI_key_release(self, event=None, msg=None):#按键弹起
+        try:
+            if "vc_config" in globals() and vc_config and vc_config.get_bool("KEY_LED_DEBUG", False):
+                try:
+                    led = EventMesh.publish("get_led_debug_state")
+                except Exception:
+                    led = None
+                try:
+                    t = getattr(self.fsm, "turn", None)
+                except Exception:
+                    t = None
+                print("[KEY] AI_key_release VC_mode={} {} turn={} led={}".format(
+                    getattr(self, "VC_mode", -1), self._vc_fsm_log_snip(), t, led))
+        except Exception:
+            pass
+        print("AI_key_release123")
+        self.key_state = 0
+        if self.trans_flag == 1:#AI翻译模式
+            EventMesh.publish("stop_voice_record") #关闭语音录音
+            return
+        if self.voice_flag == 1:
+            self.stop_voice_timer.stop()
+            self.voice_flag=0
+            if EventMesh.publish("GET_USB_STATE") == 0:
+                EventMesh.publish("set_led_state", {"ai_state": "idle"})
+            EventMesh.publish("stop_voice_record")#停止静默录音
+            tts_play_str = "留言已发送"
+            self.voice_flag=0
+            EventMesh.publish("play_tts",[tts_play_str,1])
+            return
+        if self.VC_mode == 1:
+            self._ai_key_release_handle_ptt()
+            return
+    def pwk_key_double_press(self,event,msg):#pwk双击
+        print("come 双击")
+        # 切换模式:必须打断当前对话/录音/播放,回到 idle,等待按键触发新一轮
+        try:
+            self.Close_flag = 1
+        except Exception:
+            pass
+        try:
+            EventMesh.publish("publish_AI_btnInfo", {"func": self.Close})
+        except Exception:
+            pass
+        if self.VC_mode == 0:
+            self.VC_mode = 1
+            tts_str = "已切换到单对话模式" 
+            # 下一次“无 dialog_id 的提示音 TTS”归类为模式切换提示(单对话)
+            try:
+                self._pending_tts_kind = "mode_switch_ptt"
+                self._last_tts_kind = "mode_switch_ptt"
+            except Exception:
+                pass
+        else:
+            self.VC_mode = 0 
+            tts_str = "已切换到长对话模式" 
+            # 下一次“无 dialog_id 的提示音 TTS”归类为模式切换提示(长对话)
+            try:
+                self._pending_tts_kind = "mode_switch_long"
+                self._last_tts_kind = "mode_switch_long"
+            except Exception:
+                pass
+        # 提示音播完即退出(Close_flag=1),保持 idle,等待按键
+        EventMesh.publish("play_tts",[tts_str,1])
+        EventMesh.publish("set_storage_config",["VC_mode",self.VC_mode])
+    def AI_key_long_press(self, event, msg):#按键长按
+        try:
+            if "vc_config" in globals() and vc_config and vc_config.get_bool("KEY_LED_DEBUG", False):
+                try:
+                    led = EventMesh.publish("get_led_debug_state")
+                except Exception:
+                    led = None
+                try:
+                    t = getattr(self.fsm, "turn", None)
+                except Exception:
+                    t = None
+                print("[KEY] AI_key_long_press VC_mode={} {} turn={} led={}".format(
+                    getattr(self, "VC_mode", -1), self._vc_fsm_log_snip(), t, led))
+        except Exception:
+            pass
+        print("AI_key_long_press")
+        self._ai_key_long_press_route(event, msg)
+    def pwk_key_release(self, event, msg):#电源键短按,关闭
+        try:
+            if "vc_config" in globals() and vc_config:
+                _m = bool(vc_config.get_bool("SINGLE_POWER_KEY_AI_MERGED", False))
+                if not _m:
+                    _ai = vc_config.get_attr("EXTINT_AI_GPIO", "GPIO22")
+                    _m = _ai is None or _ai == ""
+            else:
+                _m = False
+        except Exception:
+            _m = False
+        if _m:
+            self._handle_pwk_short_release(event, msg, merged_ai_tail=True)
+            return
+        self._handle_pwk_short_release(event, msg, merged_ai_tail=False)
+    def key_prompt(self):
+        EventMesh.publish("audio_montage_play",["key_prompt.mp3"])
+    def VC_error(self, event, msg):#错误播报
+        self.websocket_error()
+    def set_voicerupt_flag(self,event,msg):#按键控制当前留言播放
+        self.voicerupt_flag = msg
+    def set_wait_voicerupt_flag(self,event,msg):#按键控制未读留言播放
+        self.wait_voicerupt_flag = msg        
+    def get_voicerupt_flag(self,event,msg):#获取按键控制留言播放状态
+        return self.voicerupt_flag
+    def _1S_timer_cb(self,*argc):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self._1S_timer_cb_}) 
+    def _fsm_timeout_table_tick(self, now_ms):
+        # 兜底统一入口:按“状态机状态 + 超时阈值 + 节流”驱动,避免兜底抢跑/多入口打架。
+        # 只在对应 turn/session 状态触发动作。
+        try:
+            turn = getattr(self.fsm, "turn", VCTurnState.Idle)
+        except Exception:
+            turn = VCTurnState.Idle
+
+        # 1) synthesis→tts.started 超时:只在 Thinking 期允许清 pending 并重启会话
+        #    根因:服务端未返回 tts.started 时,继续“回到聆听/开麦”会把本轮状态机拉扯得更乱;
+        #    这里直接重启会话,让协议重新进入 started→下一轮流程。
+        try:
+            if turn == VCTurnState.Thinking and self._tts_pending == 1 and self._tts_pending_ms:
+                try:
+                    if self._fb_last_tts_pending_timeout_ms:
+                        tts_gap_ok = utime.ticks_diff(now_ms, self._fb_last_tts_pending_timeout_ms) > 15000
+                    else:
+                        tts_gap_ok = True
+                except Exception:
+                    tts_gap_ok = True
+                if utime.ticks_diff(now_ms, self._tts_pending_ms) > 8000 and tts_gap_ok:
+                    self._fb_last_tts_pending_timeout_ms = now_ms
+                    print("[VC][tts_pending] timeout, clear pending and restart session")
+                    self._tts_pending = 0
+                    self._tts_pending_ms = 0
+                    if self.VC_mode == 0 and self.Close_flag == 0:
+                        try:
+                            self.fsm.reset_rx_phases()
+                        except Exception:
+                            pass
+                        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_VC_start})
+        except Exception:
+            pass
+
+        # 2) thinking 超时:只在「llm.completed 已收到但 tts.started 长时间未到」时才重启会话
+        #    注意:不能以 llm.created 的时间戳作为超时基准,否则会在 LLM 正常推理期间误判超时(notts 场景)。
+        try:
+            if self.VC_mode == 0 and self.Close_flag == 0:
+                llm_completed_ms = getattr(self, "_llm_completed_ms", 0)
+                # 只有在 llm.completed 之后才允许超时重启
+                if turn == VCTurnState.Thinking and llm_completed_ms and self._turn_has_tts_started == 0:
+                    age_ms = utime.ticks_diff(now_ms, llm_completed_ms)
+                    restart_threshold_ms = 20000
+                    # _fb_last_thinking_restart_ms==0 时 ticks_diff(now,0) 非预期(间隔被误判极大),
+                    # 会在 llm.completed 后首批 tick 就触发 AI_VC_start → notts(无 tts.started);首次仅信任 age 门限
+                    try:
+                        if self._fb_last_thinking_restart_ms:
+                            gap_ok = utime.ticks_diff(now_ms, self._fb_last_thinking_restart_ms) > 15000
+                        else:
+                            gap_ok = True
+                    except Exception:
+                        gap_ok = True
+                    if (age_ms > restart_threshold_ms and gap_ok and
+                        self._tts_pending == 0 and self._need_audio_played_ack == 0 and self.VC_Play_flag == 0):
+                        self._fb_last_thinking_restart_ms = now_ms
+                        self._llm_completed_ms = 0
+                        self._server_vad_silence = 0
+                        try:
+                            self.fsm.reset_rx_phases()
+                        except Exception:
+                            pass
+                        try:
+                            print("[VC][thinking_timeout] AI_VC_start age_ms={} thr={}".format(
+                                age_ms, restart_threshold_ms))
+                        except Exception:
+                            pass
+                        self._vc_led_alert_rb_flash()
+                        # thinking_timeout 策略:重启会话(而不是抢开麦),避免干扰服务端 LLM/TTS 流程
+                        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_VC_start})
+        except Exception:
+            pass
+    def _1S_timer_cb_(self):
+        try:
+            self._vc_tick_serial_cancel_timeout()
+        except Exception:
+            pass
+
+        EventMesh.publish("system_sleep_rest")#防止黑屏
+        # --- Level 1:仅长对话(VC_mode==0),无业务下行 idle_sec 后结束会话(需再次按键进入)
+        # close_timerout:保活清除策略见 _vc_rx_resets_long_idle_timer(排除文本 ping/pong、heartbeat 等)
+        # 与浅休眠/灭屏解耦;单对话不走本计时
+        idle_sec = 20
+        try:
+            if "vc_config" in globals() and vc_config:
+                idle_sec = int(vc_config.get_int("VC_IDLE_AUTO_CLOSE_SEC", 20))
+        except Exception:
+            idle_sec = 20
+        if idle_sec < 5:
+            idle_sec = 5
+        if self.VC_mode == 0 and self.VC_satrt_flag == 1 and getattr(self, "AI_client_VC_isconnect", 0) == 1:
+            self.close_timerout += 1
+        else:
+            self.close_timerout = 0
+        if self.VC_mode == 0 and idle_sec > 0 and self.close_timerout > idle_sec:
+            try:
+                print("[VC][idle_L1] 长对话无业务下行超时: elapsed={}s thr={}s VC_mode={} VC_satrt={} ws_conn={} {}".format(
+                    self.close_timerout, idle_sec, self.VC_mode,
+                    getattr(self, "VC_satrt_flag", -1), getattr(self, "AI_client_VC_isconnect", -1),
+                    self._vc_fsm_log_snip()))
+            except Exception:
+                pass
+            if self._vc_long_idle_active_speech_or_later():
+                try:
+                    print("[VC][idle_L1] 处理=websocket_error(对话中无服务端响应)")
+                except Exception:
+                    pass
+                print("[VC][long_idle] branch=speech_no_rsp -> websocket_error + ud_error")
+                print("接口无响应,超时退出")
+                EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":"接口超时了。请重试"}) 
+                self.ud_error(["VC",self.VC_taskid])
+            else:
+                try:
+                    print("[VC][idle_L1] 处理=Close(结束对话,需再次按键进入)")
+                except Exception:
+                    pass
+                print("[VC][long_idle] branch=idle_timeout -> Close")
+                EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) 
+                #EventMesh.publish("sleep_BT")#休眠蓝牙
+
+        # --- Level 2:长对话+单对话,业务 idle 累计 VC_IDLE_DEEP_SLEEP_SEC → 断开(若仍连)+ 深睡事件
+        deep_sec = 60
+        deep_en = 1
+        try:
+            if "vc_config" in globals() and vc_config:
+                deep_sec = int(vc_config.get_int("VC_IDLE_DEEP_SLEEP_SEC", 60))
+                deep_en = int(vc_config.get_int("VC_IDLE_DEEP_SLEEP_ENABLE", 1))
+        except Exception:
+            deep_sec, deep_en = 60, 1
+        active_deep = bool(deep_en) and deep_sec > 0
+        if active_deep and deep_sec < 10:
+            deep_sec = 10
+        if not active_deep:
+            self._idle_deep_sec = 0
+        elif not self._vc_is_business_idle():
+            self._idle_deep_sec = 0
+            try:
+                self._vc_idle_deep_armed_suppress = 0
+            except Exception:
+                pass
+        elif getattr(self, "_vc_idle_deep_armed_suppress", 0):
+            pass
+        else:
+            self._idle_deep_sec += 1
+        if active_deep and self._idle_deep_sec > deep_sec:
+            try:
+                print("[VC][idle_L2] 业务idle累计超时: elapsed={}s thr={}s VC_mode={} VC_satrt={} ws_conn={}".format(
+                    self._idle_deep_sec, deep_sec, self.VC_mode,
+                    getattr(self, "VC_satrt_flag", -1), getattr(self, "AI_client_VC_isconnect", -1)))
+            except Exception:
+                pass
+            try:
+                self._vc_idle_deep_armed_suppress = 1
+            except Exception:
+                pass
+            self._idle_deep_sec = 0
+            EventMesh.publish("publish_AI_btnInfo", {"func": self._vc_idle_deep_sleep_impl})
+
+        # 统一兜底表(按状态机状态触发)
+        try:
+            self._fsm_timeout_table_tick(utime.ticks_ms())
+        except Exception:
+            pass
+
+        # (不做额外兜底)动态切换依赖 FSM 状态正确清理后,由 server.tts.completed→local.play_end 驱动自动开麦
+
+        # print("get_audio_state !!!!!{} {} {}\r\n".format(self.play_end,EventMesh.publish("get_audio_state"),self.play_flag))
+        # ===== 播放结束检测:用 FSM 驱动 play_end 动作链 =====
+        try:
+            self._vc_try_play_end_tick()
+        except Exception:
+            pass
+        if self.online_kws == 1:
+            if self.VC_satrt_kws_flag == 1 and self.VC_satrt_flag == 0:#要打开录音上传数据实现kws
+                pass
+
+        # thinking/llm→tts 等兜底已在 _fsm_timeout_table_tick 内统一处理
+        # VC WebSocket 文本 ping:间隔由 default_config 的 VC_WS_PING_INTERVAL_SEC 控制(秒),<=0 表示关闭 ping
+        try:
+            _ping_iv = int(vc_config.get_int("VC_WS_PING_INTERVAL_SEC", 4))
+        except Exception:
+            _ping_iv = 4
+        if _ping_iv > 0:
+            self.VC_pingping_timerout += 1
+            if self.VC_pingping_timerout >= _ping_iv:
+                self.VC_pingping_timerout = 0
+                self.AI_client_VC_ping()
+        # self.asr_pingping_timerout += 1
+        # if self.asr_pingping_timerout>3:
+        #     self.asr_pingping_timerout=0
+        #     self.AI_client_asr_ping()    
+        # 历史遗留:VC_Thinking_timerout 的重启通道会在 llm.created 后抢跑触发 AI_VC_start,
+        # 直接打断本轮 LLM→TTS(notts 日志:llm.created 后立刻 AI_VC_start,导致无 tts.started)。
+        # 现在统一由 _fsm_timeout_table_tick(Thinking 超时) 负责重启会话,因此禁用该旧通道。
+        if self.VC_Thinking_timerout > 0 and self.VC_Thinking_timerout < 100:
+            self.VC_Thinking_timerout = 100
+    def AI_VC_start(self):
+        print("AI_VC_start1")
+        
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
+        self.VC_satrt_flag = 1
+        self.VC_stop_play_audio_flag = 1
+        self.VC_satrt_kws_flag = 1
+        if self.VC_Thinking_timerout < 100:
+            self.VC_Thinking_timerout = 0
+        # 不在这里提前切灯态:播放/聆听灯只在“播放/录音真正开始/停止”处更新
+        EventMesh.publish("wakeup_BT")
+        #self.VC_Play_End()#关闭音频播放
+        print("AI_VC_start2")
+        #self.aud.aud_tone_play(16,200)#播放按键音
+        self.LocalRespondingEnded_timerout = 0
+        self.close_timerout = 0
+        self._idle_deep_sec = 0
+        self._vc_idle_deep_armed_suppress = 0
+        
+        #
+        if self.AI_client_VC_isconnect == 0:#没连接的时候请求开始
+            self.websocket_connect_VC()#连接服务器
+            
+            #utime.sleep_ms(50)
+        else:
+            print("AI_VC_start2 (already connected) {}".format(self._vc_fsm_log_snip()))
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
+        if self.AI_client_VC_isconnect == 0:
+            #self.Close()
+            return
+        # 单对话 PTT:会话已 started 时勿重复发 Start(否则第二轮起上行/ASR 易乱)
+        _ptt_skip_start = False
+        try:
+            if self.VC_mode == 1 and getattr(self, "_vc_session_started", 0) == 1:
+                _ptt_skip_start = True
+                print("[PTT] AI_VC_start skip duplicate Start (session active)")
+        except Exception:
+            pass
+        if not _ptt_skip_start:
+            self.AI_client_VC_start_requset()
+        print("AI_VC_start3")
+         
+        if (not _ptt_skip_start) and self.AI_TTS_str == "" and self.AI_new_voice_url == "":
+            self._uplink_request_start("AI_VC_start")
+           
+                
+        #     pass
+        print("AI_VC_start4")
+
+        #self.VC_Play_Start()
+        #EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Play_Start})
+        #开始线程
+        
+        # if self.AI_VC_send_data_queue_thread_id == 0:    
+        #     _thread.stack_size(self.Audio_buf_size+8*1024)
+        #     self.AI_VC_send_data_queue_thread_id = _thread.start_new_thread(self.AI_VC_send_data_queue_thread, ())
+        #     _thread.stack_size(8*1024)
+        #     print("AI_VC_start :AI_VC_send_data_queue_thread_id :{} ".format(self.AI_VC_send_data_queue_thread_id))
+        
+        if self.AI_audio_queue_thread_id ==0:
+            _thread.stack_size(24*1024)
+            self.AI_audio_queue_thread_id = _thread.start_new_thread(self.AI_audio_queue_thread, ())
+            _thread.stack_size(8*1024)
+            print("AI_VC_start :AI_audio_queue_thread_id :{} ".format(self.AI_audio_queue_thread_id))
+        else:
+            print("AI_VC_start :AI_audio_queue_thread_id != 0 ")
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
+        self._1S_timer.start(1000,1,self._1S_timer_cb)
+        # self.VC_pingping_timerout = 0
+        # self.asr_pingping_timerout = 0
+        print("AI_VC_start5")
+    def kws_even(self,event = None,msg= None):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.kws_even_cb}) 
+    def kws_even_cb(self):
+        if EventMesh.publish("get_sos_flag") == 1:
+            return
+        print("kws_even_cb")
+        if self.VC_mode == 1: 
+            try:
+                tts_str = vc_config.get_attr("KWS_PTT_HINT", "请您按住圆形的按钮开始讲话,讲完松开即可 ")
+            except Exception:
+                tts_str = "请您按住圆形的按钮开始讲话,讲完松开即可 "
+            EventMesh.publish("play_tts",[tts_str,1]) 
+            return
+        if self.VC_mode == 0:
+            if EventMesh.publish("get_voice_record_flag") == 1:#停止静默录音
+                if EventMesh.publish("get_voice_record_mode") == 2:#语音留言不能被打断
+                    return
+                elif  EventMesh.publish("get_voice_record_mode") == 1: #静默录音需要打断
+                    EventMesh.publish("stop_voice_record")#停止静默录音
+            self.aud.aud_tone_play(10,400)
+            # EventMesh.publish("audio_montage_play",["chenggong.mp3"])  
+            self.Close()
+            # if dev_type == "7PIN" :
+            #     self.aud.aud_tone_play(10,400)#播放按键音
+            #self.aud.aud_tone_play(10,200)#播放按键音
+            self.AI_TTS_str = ""#"你好,有什么吩咐"
+            self.AI_ASR_str = ""
+            self.AI_ASR_str_tmp = ""
+            self._uplink_request_stop("kws_even_cb")
+            self._uplink_request_start("kws_even_cb")
+            self.AI_VC_start() 
+    def set_wait_first_flag(self,event = None,msg= None):#设置未读语音播报初次记录flag
+        self.wait_first_flag = msg
+    def find_cmd(self,event ,msg):
+        if "电量" in msg:
+            battery = EventMesh.publish("get_battery")
+            print("电量查询:{}".format(battery))
+            tts_play_str = "电量剩余百分之{}".format(battery)
+            EventMesh.publish("play_tts",[tts_play_str,0])
+            # print("电量!!!!!!!!!!!")
+        elif "信号" in msg:
+            signal = net.csqQueryPoll()
+            tts_play_str = "当前信号{}".format(signal)
+            EventMesh.publish("play_tts",[tts_play_str,0])
+            # print("信号!!!!!!!!!!!")
+        elif "卡号" in msg:
+            # print("卡号!!!!!!!!!!!")
+            iccid = sim.getIccid()
+            iccid = num_str_to_chinese(iccid)#转为中文
+            if iccid != -1 and len(iccid) > 5:
+                print("iccid{}".format(iccid))
+                tts_play_str = "当前卡号为:{}".format(iccid)
+            else:
+                tts_play_str = "卡号查询失败"
+            EventMesh.publish("play_tts",[tts_play_str,0])
+        elif "设备号" in msg:
+            imei = modem.getDevImei()
+            if imei != -1 and len(imei) > 5:
+                print("IMEI:{}".format(imei))
+                tts_play_str = "当前设备号为:{}".format(imei)
+            else:
+                tts_play_str = "设备号查询失败"
+            EventMesh.publish("play_tts",[tts_play_str,0])
+        elif "音量" in msg :
+            tts_play_str = "当前音量是{}".format(self.VC_TTS_volume)
+            EventMesh.publish("play_tts",[tts_play_str,0])
+        elif "语速" in msg :
+            tts_play_str = "当前语速是{}".format(EventMesh.publish("find_rate"))
+            EventMesh.publish("play_tts",[tts_play_str,0])
+    def chenggong_tone(self):
+        EventMesh.publish("audio_montage_play",["chenggong.mp3"])    
+    def concurrency_limit_exceeded_tone(self):
+        self.Close()
+        EventMesh.publish("audio_montage_play",["chaobingfatis.mp3"])    #播放超并发提醒
+    def factory_mode(self):
+        print("factory_mode")
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) 
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.factory_start}) 
+    def factory_start(self):
+        self.factory_timer.start(3000, 1, self.factory_timer_cb)
+    def factory_timer_cb(self, *args, **kwargs):
+        print("factory_timer_cb play......")
+        #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#切换成功
+        EventMesh.publish("audio_montage_play",["xinhaoruo1.mp3"])
+    def Close_VC(self,event,msg):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) 
+    def Close(self):
+        try:
+            if getattr(self, "VC_mode", 0) != 1:
+                print("[VC][long_idle] Close() enter VC_mode={} {} VC_satrt_flag={} VC_record_state_flag={}".format(
+                    getattr(self, "VC_mode", -1), self._vc_fsm_log_snip(),
+                    getattr(self, "VC_satrt_flag", -1), getattr(self, "VC_record_state_flag", -1)))
+        except Exception:
+            pass
+        self.VC_stop_play_audio_flag = 0
+        try:
+            self.AI_key_long_press_flag = 0
+        except Exception:
+            pass
+        self.VC_Thinking_timerout = 0
+        self.AI_ASR_timer.stop()
+        self._1S_timer.stop()
+        self.close_timerout = 0
+        self._idle_deep_sec = 0
+        self.factory_timer.stop()
+        self.AI_TTS_str = ""
+        self.AI_ASR_str = ""
+        self.AI_ASR_str_tmp = ""
+        self.AI_ASR_flag  = 0
+        self.AI_ASR_check_timeout = 0
+        self.AI_ASR_error_timeout = 0
+        self.LocalRespondingEnded_timerout = 0
+        self._need_audio_played_ack = 0
+        self._tts_pending = 0
+        self._tts_pending_ms = 0
+        self._vc_session_started = 0
+        self._ptt_audio_created = 0
+        self._vc_pending_interrupt_uplink = 0
+        try:
+            self._skip_started_uplink_once = 0
+        except Exception:
+            pass
+        try:
+            self._ptt_abort_interrupt_arm()
+        except Exception:
+            pass
+        # Power 短按停止对话后,再次按 AI 需要新建 dialog_id:清理本轮 dialog/session 标识
+        try:
+            self._cur_dialog_id = ""
+        except Exception:
+            pass
+        try:
+            self._cur_sid = ""
+        except Exception:
+            pass
+        self.is_ruppt_tts = 0#打断标志位清零
+        print("ai_VC Close1!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+        try:
+            if getattr(self, "VC_mode", 0) != 1:
+                print("[VC][long_idle] step=set_led_state_idle (first)")
+        except Exception:
+            pass
+        EventMesh.publish("set_led_state", {"ai_state": "idle"})
+        self.tts_in_data_len = 0
+        self.tts_out_data_len = 0
+        #必须先销毁线程,不然线程会容易报错
+        
+        try:
+            self.AI_audio_queue_thread_close()#销毁音频线程
+        except Exception as e:
+            usys.print_exception(e)
+        try:
+            self.AI_VC_send_data_queue_thread_close()#销毁数据发送线程
+        except Exception as e: 
+            usys.print_exception(e)
+        
+        # Close 可能被不同线程触发:统一切回主线程执行 stop
+        try:
+            if getattr(self, "VC_mode", 0) != 1:
+                print("[VC][long_idle] step=schedule_uplink_stop reason=Close (async publish_AI_btnInfo)")
+        except Exception:
+            pass
+        EventMesh.publish("publish_AI_btnInfo",{"func":self._uplink_request_stop, "data":"Close"})
+        
+        print("ai_VC Close1.1!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+        
+        print("ai_VC Close1.2!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+        
+        try:
+            if getattr(self, "VC_mode", 0) != 1:
+                print("[VC][long_idle] step=websocket_deconnect_VC")
+        except Exception:
+            pass
+        try:
+            self.websocket_deconnect_VC()
+        except Exception as e:
+            usys.print_exception(e)
+        
+
+        
+
+
+        print("ai_VC Close1.3!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+        
+        print("ai_VC Close2!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+        
+        
+        
+        
+        if self.record_data_save_flag == 1:
+            if self.record_fd :
+                self.record_fd.close()
+            self.record_fd = None
+        if self.play_data_save_flag == 1:
+            if self.play_fd !=None:
+                self.play_fd.close()
+                self.play_fd = None
+        print("ai_VC Close3!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n")
+        
+        if self.AI_VC_send_data_queue.empty()!=True:
+            self.AI_VC_send_data_queue.clear()
+        self.play_new_voice_flag = 0 #语音消息播放状态
+        if self.voice_choose_flag == 0:  # 如果没有未读语音
+            self.AI_new_voice_url = ""  # 语音播放地址
+            print("关闭并重置语音标志位")
+        else:
+            print("关闭但是没有重置语音标志位")
+        self.AI_ASR_send_buf_len = 0
+        #self.AI_WS_connect_Queue.put(self.AI_client_VC)
+        try:
+            if getattr(self, "VC_mode", 0) != 1:
+                print("[VC][long_idle] step=set_led_state_idle (second) -> VC_init VC_satrt_flag=0")
+        except Exception:
+            pass
+        EventMesh.publish("set_led_state", {"ai_state": "idle"})
+        self.VC_init()
+        self.VC_satrt_flag = 0
+        # Level2:会话结束后仍可继续 1s 节拍累计 idle 直至深睡(依赖 VC_IDLE_DEEP_SLEEP_SEC)
+        try:
+            _dsec = 60
+            _den = 1
+            if "vc_config" in globals() and vc_config:
+                _dsec = int(vc_config.get_int("VC_IDLE_DEEP_SLEEP_SEC", 60))
+                _den = int(vc_config.get_int("VC_IDLE_DEEP_SLEEP_ENABLE", 1))
+            if _den and _dsec > 0:
+                self._1S_timer.start(1000, 1, self._1S_timer_cb)
+        except Exception:
+            pass
+    def VC_init2(self):
+        self.VC_init()
+        
+        print("ai_VC VC_init2!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+        try:
+            self.websocket_deconnect_VC()
+        except Exception as e:
+            usys.print_exception(e)
+        print("ai_VC Close3!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n")
+        self._1S_timer.stop()
+        if self.AI_VC_send_data_queue.empty()!=True:
+            self.AI_VC_send_data_queue.clear()
+
+        self.AI_ASR_send_buf_len = 0
+        if self.AI_audio_queue.empty()!=True:
+            self.AI_audio_queue.clear()
+    def VC_init(self):
+        _q0 = self.AI_audio_queue.size()
+        self.VC_Play_End()
+        if self.AI_audio_queue.empty()!=True:
+            self.AI_audio_queue.clear()
+        print("[VC] init audio_queue {} -> {}".format(_q0, self.AI_audio_queue.size()))
diff --git a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_session_imports.py b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_session_imports.py
new file mode 100644 (file)
index 0000000..7a97060
--- /dev/null
@@ -0,0 +1,77 @@
+"""vc_session 子模块共享 import(io / media / core 显式引用,禁止 import * 扩散到业务外)。"""\r
+# -*- coding: utf-8 -*-\r
+"""vc_session 子模块共享 import(io / media / core 显式引用,禁止 import * 扩散到业务外)。"""\r
+from common import Abstract\r
+try:\r
+    from qp_log import qp_print as print\r
+except Exception:\r
+    pass\r
+import EventMesh\r
+import uwebsocket\r
+import usys\r
+import gc\r
+import urandom\r
+import uhashlib\r
+import ubinascii\r
+import ujson\r
+import osTimer\r
+import _thread\r
+from queue import Queue\r
+import utime\r
+import audio\r
+import uos\r
+from machine import Pin\r
+import modem\r
+import sim\r
+import net\r
+import base64\r
+import request\r
+import ql_fs\r
+from misc import Power\r
+import ntptime\r
+import machine\r
+import dataCall\r
+import constants\r
+import versions\r
+import version_util\r
+try:\r
+    import vc_config\r
+except Exception:\r
+    vc_config = None\r
+try:\r
+    import vc_text_intent\r
+except Exception:\r
+    vc_text_intent = None\r
+try:\r
+    import VAD\r
+except Exception:\r
+    pass\r
+try:\r
+    import usr.ai_alarm as _ai_alarm\r
+except Exception:\r
+    import ai_alarm as _ai_alarm\r
+from vc_common import (\r
+    dev_type,\r
+    VC_HEARTBEAT_LOG,\r
+    VC_PROTO_GUARDS,\r
+    VC_WS_IO_LOG,\r
+    _VC_KEEPALIVE_RX_EVENTS,\r
+    _vc_out_json_event_meta,\r
+    PRESS_AUDIO_MODE,\r
+)\r
+from vc_fsm import (\r
+    VCSessionState,\r
+    VCTurnState,\r
+    VC_TtsRxPhase,\r
+    VCMode,\r
+    VCStateMachine,\r
+    VC_record_TYPE_PCM,\r
+    VC_record_TYPE_AMR,\r
+    VC_play_TYPE_PCM,\r
+    VC_play_TYPE_MP3,\r
+    AI_client_VC_num,\r
+    AI_client_asr_num,\r
+)\r
+from vc_ota import OTA\r
+from vc_utils import num_str_to_chinese\r
+\r
diff --git a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_session_io.py b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_session_io.py
new file mode 100644 (file)
index 0000000..185997d
--- /dev/null
@@ -0,0 +1,1740 @@
+# -*- coding: utf-8 -*-
+"""AI_VC mixin:VC WebSocket 建链、收发线程、协议 JSON 分发。"""
+from vc_session_imports import *  # noqa: F401,F403
+# import * 不导入以下划线开头的符号;_thread 必须显式引入(否则 start_new_thread NameError)
+import _thread
+
+class VCSessionIoMixin:
+    def _vc_fsm_dispatch_cb(self, data):
+        """btn 线程:处理服务端 FSM 事件(单对话串行)。"""
+        try:
+            ev, jsload = data
+        except Exception:
+            return
+        try:
+            actions = self.fsm.on_server_event(ev, jsload)
+            self._fsm_apply_actions(actions)
+        except Exception as e:
+            usys.print_exception(e)
+        if ev == "canceled":
+            self._vc_on_ptt_cancel_settled(via="canceled")
+    def _vc_dispatch_server_event(self, ev, jsload_data=None):
+        try:
+            if ev == "canceled":
+                self._vc_stop_downlink_for_interrupt("canceled")
+        except Exception:
+            pass
+        if getattr(self, "VC_mode", -1) == 1:
+            self._vc_enqueue_btn(self._vc_fsm_dispatch_cb, (ev, jsload_data))
+        else:
+            try:
+                self._fsm_apply_actions(self.fsm.on_server_event(ev, jsload_data))
+            except Exception:
+                pass
+    def request_send_start(self):
+        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_start_requset})
+    def request_send_audio_create(self):
+        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_send_speech})
+    def request_send_audio_completed(self):
+        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_end_speech})
+    def request_send_audio_played(self):
+        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_audio_played})
+    def request_send_synthesis(self, text):
+        try:
+            self.AI_TTS_str = text
+        except Exception:
+            pass
+        EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_client_VC_synthesis})
+    def websocket_connect_VC(self):
+        print("websocket_connect_VC:{} e:{}!!!!!!!!!!!!!!!!!!!!!".format(self.AI_client_VC_isconnect,self.AI_client_VC_debug))
+        if self.AI_client_VC_isconnect == 1:
+            return
+        # 创建VC客户端
+        try:
+            signal = net.csqQueryPoll()
+            if signal == -1 or sim.getStatus() != 1:#无蜂窝/无卡等:弱网提示
+                self.websocket_error(play_weak_signal_tone=True)
+                return
+            
+            req_str = ""
+            ack = ""
+            if self.is_VC_ACK == 1:
+                ack = "ack=1"
+            lang = "lang=zh-cn"#普通话测试环境
+            try:
+                if "lang" in self.VC_TTS_voice_type[self.VC_TTS_voice_key]:
+                    lang = self.VC_TTS_voice_type[self.VC_TTS_voice_key]["lang"]
+                else:
+                    print("not lang!!!!!!!!!!!!!!!!!!!!!!")
+            except Exception as e:
+                usys.print_exception(e)
+            
+            #女,男,童,粤,川
+            self.bot_name = "柒先生"
+            try:
+                if "bot_name" in self.VC_TTS_voice_type[self.VC_TTS_voice_key]:
+                    self.bot_name = self.VC_TTS_voice_type[self.VC_TTS_voice_key]["bot_name"]
+                else:
+                    print("not bot_name!!!!!!!!!!!!!!!!!!!!!!")
+            except Exception as e:
+                usys.print_exception(e)
+            
+            # Headers = {'X-NLS-Token':'0c8b8f05a46f423096be64a9c4c46310'}
+            # self.AI_client_VC = uwebsocket.Client.connect('wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1',headers=Headers, debug=False)
+            token = EventMesh.publish("get_dev_token")
+            print("token:{}".format(token))
+            
+            req_str = "vc/chat?is_auth={}".format(self.is_auth)
+            try:
+                if "vc_config" in globals() and vc_config:
+                    base = vc_config.get_attr("VC_WS_PATH", "vc/chat") or "vc/chat"
+                    req_str = "{}?is_auth={}".format(base, self.is_auth)
+            except Exception:
+                pass
+            if(token):
+                url = 'ws://{}/{}&dt={}'.format(self.server_ip,req_str,token)
+            else:
+                url = 'ws://{}/{}'.format(self.server_ip,req_str)
+            #url = 'ws://115.190.94.208:22080/vc/coze?dt={}'.format(token)
+            
+            if VC_WS_IO_LOG:
+                print("[VC] ws url {}".format(url))
+            self.AI_client_VC = uwebsocket.Client.connect(url, debug=False)
+            #self.AI_WS_connect_Queue.put(self.AI_client_VC)
+
+       
+            print("[VC] websocket_connect ok")
+        except Exception as e: #网络连接异常(已通过 CSQ/SIM,多为 URL/握手/服务端问题,勿播「信号弱」)
+            usys.print_exception(e)
+            print("websocket_connect_VC error!!!\r\n")
+            try:
+                print("[VC] ws connect exc: {}".format(e))
+            except Exception:
+                pass
+            str_error = str(e)
+            if str_error.find("[Errno 22] EINVAL")!= -1: 
+                print("[VC] websocket EINVAL: {}".format(str_error))
+                self.websocket_error(play_weak_signal_tone=False)
+                EventMesh.publish("set_storage_config",["powerRestart",1])#调试临时关闭
+                utime.sleep_ms(1000)
+                self.aud.stopPlayStream()
+                self.aud.stopAll()
+                Power.powerRestart()
+                
+            self.websocket_error(play_weak_signal_tone=False)
+            return
+        #print("AI_client_VC:{}".format(self.AI_client_VC))
+        if self.AI_client_VC == None:
+            EventMesh.publish("update_dev_token")#获取token
+            self.websocket_error(play_weak_signal_tone=False)
+            return
+        self.AI_client_VC_debug+=1
+        utime.sleep_ms(10)
+        self.AI_client_VC.settimeout(5)
+        try:
+            if self.AI_VC_send_data_queue_thread_id == 0:
+                _thread.stack_size(self.Audio_buf_size+8*1024)
+                self.AI_VC_send_data_queue_thread_id = _thread.start_new_thread(
+                    self.AI_VC_send_data_queue_thread, ())
+                _thread.stack_size(8*1024)
+                print("AI_VC_start :AI_VC_send_data_queue_thread_id :{} ".format(
+                    self.AI_VC_send_data_queue_thread_id))
+            self.AI_client_VC_recv_thread_close()
+            _thread.stack_size(self.Audio_buf_size+8*1024)
+            self.AI_client_VC_recv_id = _thread.start_new_thread(
+                self.AI_client_VC_recv, (self.AI_client_VC,))
+            _thread.stack_size(8*1024)
+            if not self.AI_client_VC_recv_id:
+                raise OSError("VC recv thread start failed")
+            self.AI_client_VC_isconnect = 1
+            print("websocket_connect_VC end")
+        except Exception as e:
+            usys.print_exception(e)
+            try:
+                print("[VC] ws thread start failed: {}".format(e))
+            except Exception:
+                pass
+            self.AI_client_VC_isconnect = 0
+            try:
+                self.websocket_deconnect_VC()
+            except Exception:
+                pass
+    def websocket_error(self, TTS_str=None, play_weak_signal_tone=False):
+        """play_weak_signal_tone=True 时播 xinhaoruo1(弱网/无卡类);连接/协议失败请传 False,避免误报「信号弱」。"""
+        try:
+            if getattr(self, "VC_mode", 0) != 1:
+                print("[VC][long_idle] websocket_error enter VC_mode={} TTS_str={} weak_hint={} -> Close then uplink_stop(async)".format(
+                    getattr(self, "VC_mode", -1), TTS_str, play_weak_signal_tone))
+        except Exception:
+            pass
+        print("websocket_error1!!!!")
+        utime.sleep_ms(800)
+        self.Close()
+        print("websocket_error1.1!!!!")
+        print("websocket_error!!!!")
+        print("websocket_error!!!!")
+        self.VC_Thinking_timerout = 0
+        self.VC_satrt_flag = 0
+        self.AI_TTS_str = ""
+        self.AI_ASR_str = ""
+        self.AI_ASR_str_tmp = ""
+        self.AI_ASR_flag  = 0
+        # 可能由收包线程/其它线程触发:统一切回主线程执行 stop,避免“异常→重试→再异常”的循环
+        try:
+            if getattr(self, "VC_mode", 0) != 1:
+                print("[VC][long_idle] websocket_error schedule uplink_stop reason=websocket_error + set_led_idle")
+        except Exception:
+            pass
+        EventMesh.publish("publish_AI_btnInfo",{"func":self._uplink_request_stop, "data":"websocket_error"})
+        EventMesh.publish("set_led_state", {"ai_state": "idle"})
+        if TTS_str == None:
+            self.websocket_error_count+=1
+            # if self.websocket_error_count <=3:
+            #     EventMesh.publish("audio_montage_play",["xinhaoruo1.mp3"])
+            if play_weak_signal_tone and self.websocket_error_count < 10:
+                EventMesh.publish("audio_montage_play", ["xinhaoruo1.mp3"])
+            elif self.websocket_error_count >=10:
+                # EventMesh.publish("paly_iccid_imei")    
+                EventMesh.publish("audio_montage_play", ["xinhaoruo1.mp3"])
+            # else:
+            #     EventMesh.publish("audio_montage_play",["R_SIM_FAILE.mp3"])
+            #self.aud.play(1,1,'U:/static/R_SIM_FAILE.mp3')
+            if self.websocket_error_count <3 :
+                utime.sleep_ms(3000)
+                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start})
+        else:
+            print("websocket_error:tts:{}".format(TTS_str))
+            self.AI_TTS_str = TTS_str
+            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start})
+            self.Close_flag = 1
+        print("websocket_error2{}".format(self.websocket_error_count))
+       
+        
+        print("websocket_error4")
+    def websocket_deconnect_VC(self):
+        #print("websocket_deconnect_VC {}!!!!!!!".format(self.AI_client_VC_isconnect))
+        print("websocket_deconnect_VC!!!!!!!")
+        self._vc_session_started = 0
+        # 断链/模式切换时必须重置“上行录音状态”,否则会被 _uplink_request_start 的幂等门控挡住,
+        # 表现为:切回长对话后无法自动开麦,按键 cancel/canceled 也无法恢复。
+        try:
+            self.VC_record_state_flag = 0
+        except Exception:
+            pass
+        try:
+            self._server_vad_silence = 0
+        except Exception:
+            pass
+        try:
+            self._ptt_audio_created = 0
+        except Exception:
+            pass
+        try:
+            # 若录音线程/录音设备仍在占用,先停掉
+            self.VC_Speech_End()
+        except Exception:
+            try:
+                self.VC_record_thread_close()
+            except Exception:
+                pass
+        # 断链必须同步重置 FSM,否则 await_played/turn 残留会导致动态切换后“无法自动开麦/按键无效”
+        try:
+            self.fsm.session = VCSessionState.Disconnected
+            self.fsm.turn = VCTurnState.Idle
+            self.fsm.await_played = False
+            self.fsm.last_tts_completed_ms = 0
+            self.fsm.last_llm_ms = 0
+            self.fsm.reset_rx_phases()
+        except Exception:
+            pass
+        self.AI_client_VC_recv_thread_close()#关闭读线程
+        self.AI_client_VC_recv_id = 0
+        if self.AI_client_VC_isconnect == 1 or self.AI_client_VC!=None:
+            try:
+                self.AI_client_VC_isconnect = 0
+                count = 500
+                while True:#等待发完数据
+                    count-=1
+                    if count <=0 or self.AI_VC_send_data_queue_thread_lock == 0:
+                        break
+                    utime.sleep_ms(1)
+                
+                try:
+                    self.AI_client_VC.close()
+                    self.AI_client_VC_debug -=1
+                    #self.AI_client_VC=None
+                except Exception as e:
+                    usys.print_exception(e)
+                    try :
+                        self.AI_client_VC.close()
+                        self.AI_client_VC_debug -=1
+                        #self.AI_client_VC=None
+                    except Exception as e:
+                        usys.print_exception(e)
+                try :
+                    self.AI_client_VC._close()
+                except Exception as e:
+                    usys.print_exception(e)
+                self.AI_client_VC = None
+            except Exception as e:
+                usys.print_exception(e)
+        print("websocket_deconnect_VC {}!!!!!!!".format(self.AI_client_VC_isconnect))
+    def AI_client_VC_ping(self):
+        pass
+        if self.AI_client_VC_isconnect == 1:
+            #self.AI_client_VC.write_frame(0x09,b'ping ping')
+            self.AI_client_VC_send("ping")
+            if VC_HEARTBEAT_LOG:
+                print("VC: ping ping")
+    def AI_client_VC_send(self,data_str):
+        # if isinstance(data_str, str):#是字符串
+        #     print("AI_client_VC_send:{}".format(data_str))
+        #     pass
+        # else:
+        #     print("AI_client_VC_send:len{}".format(len(data_str)))
+        # 统计“语音上行”真实开始/结束:这里只统计二进制音频(非 ping/字符串)
+        try:
+            if not isinstance(data_str, str):
+                l = len(data_str)
+                if l > 0:
+                    self._uplink_mark_start(l)
+                    self._uplink_bytes += l
+        except Exception:
+            pass
+        self.AI_VC_send_data_queue.put([AI_client_VC_num,data_str]) 
+    def AI_VC_send_data_queue_thread_close(self):
+        if self.AI_VC_send_data_queue_thread_id != 0:
+            # 先清空待发队列,避免关闭过程持续堆积/刷屏
+            if self.AI_VC_send_data_queue.empty() != True:
+                self.AI_VC_send_data_queue.clear()
+            # 通知发送线程退出;若发送线程正阻塞在底层 send,则只能等超时或强制 stop_thread
+            self.AI_VC_send_data_queue_thread_lock = 2
+            count = 500
+            while True:
+                count -= 1
+                if self.AI_VC_send_data_queue_thread_lock == 0 or count <=0:
+                    break
+                else:
+                    # 该打印只是“等待线程停下来”的调试心跳,默认关闭以免影响系统实时性
+                    if VC_HEARTBEAT_LOG and (count % 50 == 0 or count > 490):
+                        print("AI_VC_send_data_queue_thread going:{}...".format(count))
+                    utime.sleep_ms(5)
+            self.AI_VC_send_data_queue_thread_lock = 0
+            if VC_HEARTBEAT_LOG:
+                print("AI_VC_send_data_queue_thread_close")
+            if self.AI_VC_send_data_queue_thread_id != 0:
+                try:
+                    # 线程可能已自然退出/被复用,stop_thread 会抛 ValueError
+                    try:
+                        running = _thread.threadIsRunning(self.AI_VC_send_data_queue_thread_id)
+                    except Exception:
+                        running = True
+                    if running:
+                        _thread.stop_thread(self.AI_VC_send_data_queue_thread_id)
+                except Exception as e:
+                    usys.print_exception(e)
+        self.AI_VC_send_data_queue_thread_id = 0
+    def AI_VC_send_data_queue_thread(self):
+        print("AI_VC_send_data_queue_thread:start")
+        count_t = 0
+        while True:
+            if self.AI_VC_send_data_queue_thread_lock == 2:
+                print("AI_VC_send_data_queue_thread,该退出了")
+                break  
+            if self.AI_VC_send_data_queue.empty() :#不能去掉,去掉会卡死
+                self.AI_VC_send_data_queue_thread_lock = 0  
+                count_t+=1
+                # if count_t%200 == 0:
+                #     print("self.AI_VC_send_data_queue.empty")
+                utime.sleep_ms(1)
+            else:
+                #data[0]是websocket连接对象编号,data[1]是数据
+                #print("AI_VC_send_data_queue_thread:get")
+                
+                self.AI_VC_send_data_queue_thread_lock = 1
+                data = self.AI_VC_send_data_queue.get()
+                
+                
+                #print("AI_VC_send_data_queue_thread:num{} len{}".format(data[0],len(data[1])))
+                if data != None:
+                    try:
+                        #data["func"](data["data"])
+                        # 发送数据
+                        #bytes_data = bytes(data[1])
+                        #data[0].send(data[1])
+                        #print("AI_VC_send_data_queue_thread:len:{}".format(len(data[1])))
+                        #print("AI_VC_send_data_queue_thread on line asr recv :{} vc recv:{}".format(_thread.threadIsRunning(self.AI_client_asr_recv_id),_thread.threadIsRunning(self.AI_client_VC_recv_id)))
+                        if data[0] == AI_client_VC_num:
+                            
+                            if isinstance(data[1], str):#是字符串
+                                self.VC_pingping_timerout = 0
+                                s = data[1]
+                                if s is None:
+                                    s = ""
+                                # 保活 ping 不落 WS IO 日志(避免周期性刷屏)
+                                if VC_WS_IO_LOG and s != "ping":
+                                    try:
+                                        if len(s) > 240:
+                                            preview = s[:160] + " ... " + s[-60:]
+                                        else:
+                                            preview = s
+                                        print("[VC] WS send_txt len={} txt={}".format(len(s), preview))
+                                    except Exception:
+                                        try:
+                                            print("[VC] WS send_txt {}".format(data[1]))
+                                        except Exception:
+                                            pass
+                                elif not VC_WS_IO_LOG:
+                                    if s == "ping":
+                                        pass
+                                    else:
+                                        try:
+                                            ev, tid = _vc_out_json_event_meta(s)
+                                            if ev:
+                                                print("[VC][proto] tx event={} trace_id={} len={} ws_conn={}".format(
+                                                    ev, tid or "-", len(s), self.AI_client_VC_isconnect))
+                                            else:
+                                                print("[VC][proto] tx unknown_json len={} ws_conn={}".format(
+                                                    len(s), self.AI_client_VC_isconnect))
+                                        except Exception:
+                                            print("[VC][proto] tx parse_fail len={}".format(len(s)))
+                            else:
+                                pass
+                                # if VC_AUDIO_FRAME_LOG:
+                                #     print("VC: send_data len:{}".format(len(data[1])))
+                            # print("self.AI_client_VC = {} , self.AI_client_VC_isconnect = {}".format(self.AI_client_VC,self.AI_client_VC_isconnect))    
+                            if self.AI_client_VC != None and self.AI_client_VC_isconnect == 1:
+                                self.AI_client_VC.send(data[1])
+                            else:
+                                try:
+                                    if isinstance(data[1], str) and data[1] != "ping":
+                                        _ev, _tid = _vc_out_json_event_meta(data[1])
+                                        print("VC VC: send_data:ERROR len={} event={} trace_id={}".format(
+                                            len(data[1]), _ev or "?", _tid or "-"))
+                                    else:
+                                        print("VC VC: send_data:ERROR:{}".format(len(data[1])))
+                                except Exception:
+                                    print("VC VC: send_data:ERROR:{}".format(len(data[1])))
+                            #print("VC: send_data end")
+    
+                        #print("VC: send_data end")
+                    except Exception as e:
+                        print("DeviceInfo_queue_thread:ERROR:{}".format(e))
+                        usys.print_exception(e)
+                        if data[0] == AI_client_VC_num:
+                            #self.websocket_error()
+                            pass
+
+                            
+                            try:
+                                if self.AI_client_VC_isconnect != 0:
+                                    print("VC发送数据错误,退出")
+                                    EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":"VC发送出错了,请重试"}) 
+                                    self.ud_error(["VC",self.VC_taskid])
+                            except Exception as e: #网络连接异常
+                                usys.print_exception(e)
+
+                    utime.sleep_ms(1)
+        self.AI_VC_send_data_queue_thread_lock = 0              
+    def AI_client_VC_recv_thread_close(self):
+        if self.AI_client_VC_recv_id != 0:
+            count =500
+            while True:
+                count -= 1
+                if self.AI_client_VC_recv_lock == 1:
+                    self.AI_client_VC_recv_lock = 2
+                if self.AI_client_VC_recv_lock == 0 or count <=0:
+                    break
+                else:
+                    print("AI_client_VC_recv_thread going:{}...".format(count))
+                    utime.sleep_ms(1)
+            self.AI_client_VC_recv_lock = 0
+            print("AI_client_VC_recv_thread_close")
+            if self.AI_client_VC_recv_id!= 0:
+                try:
+                    _thread.stop_thread(self.AI_client_VC_recv_id)
+                except Exception as e:
+                    usys.print_exception(e)
+        self.AI_client_VC_recv_id = 0
+    def AI_client_VC_recv(self,cli):
+        count = 0
+        asr_led_flag = 0
+        tts_led_flag = 0
+        while True:
+            try:
+                # 死循环接收数据
+                self.AI_client_VC_recv_lock = 0
+
+                count+=1
+                if self._vc_tts_pipeline_busy() and (self.tts_in_data_len - self.tts_out_data_len > 64*1024 or count>5):
+                    count = 0
+                    utime.sleep_ms(1)
+                if not self._vc_tts_pipeline_busy():
+                    utime.sleep_ms(1)
+                recv_data = cli.recv()
+                if self.AI_client_VC_recv_lock == 2:
+                    print("AI_client_VC_recv 该退出了")
+                    break
+                self.AI_client_VC_recv_lock = 1
+                if self._vc_rx_resets_long_idle_timer(recv_data):
+                    self.close_timerout = 0
+                if recv_data == "websocket close":
+                    # 服务器关闭连接
+                    print("VC 服务器关闭连接!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+                    #self.AI_client_VC_isconnect = 0
+                    #self.websocket_deconnect_VC()
+                    #EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_asr}) 
+                    # #EventMesh.publish("publish_AI_btnInfo",{"func":utime.sleep_ms,'data':(1500)})
+                    #EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_connect_asr}) 
+                    EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":"VC连接断开了,请重试"}) 
+                    self.ud_error(["VC",self.VC_taskid])
+                    break
+                elif recv_data == "":#客户端关闭连接
+                    print("VC VC :客户端关闭连接")
+                    EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":"VC连接断开了,请重试"}) 
+                    self.ud_error(["VC",self.VC_taskid])
+                    break
+                try:
+                    self.websocket_error_count = 0
+                    if isinstance(recv_data, str):#返回的是字符串
+                        jsload_data = ujson.loads(recv_data)
+                        if VC_WS_IO_LOG:
+                            print("[VC] recv raw:{}".format(recv_data))
+                        else:
+                            try:
+                                _rev = jsload_data.get("event", "")
+                                if _rev != "llm.delta":
+                                    print("[VC] rx event={} sid={}".format(
+                                        _rev, jsload_data.get("sid", "") or "-"))
+                            except Exception:
+                                pass
+                        #print("AI_client_VC_recv jsload_data = {}".format(jsload_data))
+                        try:
+                            self._cur_sid = jsload_data.get("sid", "") or ""
+                        except Exception:
+                            self._cur_sid = ""
+                        try:
+                            ev = jsload_data.get("event", "")
+                            if ev:
+                                self._vc_dispatch_server_event(ev, jsload_data)
+                        except Exception:
+                            pass
+                        
+                        if jsload_data["event"] == "started" :#会话开始
+                            #payload = jsload_data["payload"]
+                            # self.VC_dialog_id = payload["dialog_id"] 
+                            # self.VC_task_id = jsload_data["task_id"] 
+                            # started 仅表示链路就绪:不在这里提前切灯态;
+                            # 聆听灯只在“录音真正开始/停止”处更新(VC_Speech_Start/End)。
+                            # 会话开始:允许上行;连续对话模式下直接进入“可发送音频”状态
+                            self._vc_session_started = 1
+                            # started 时不要强制清 Close_flag:
+                            # - Close_flag==1:提示音/模式切换播完退出
+                            # - Close_flag==0:正常对话流程
+                            self._ptt_audio_created = 0
+                            self._server_vad_silence = 0
+                            self._turn_has_asr_completed = 0
+                            self._turn_has_vad_silence = 0
+                            self._turn_has_tts_started = 0
+                            self._llm_completed_ms = 0
+                            if self.VC_mode == 0 and getattr(self, "Close_flag", 0) == 0:
+                                _skip_uplink = 0
+                                try:
+                                    if getattr(self, "_skip_started_uplink_once", 0) == 1:
+                                        self._skip_started_uplink_once = 0
+                                        _skip_uplink = 1
+                                        print("[VC][uplink_ctrl] Deny START by server.started cause=boot_prompt")
+                                except Exception:
+                                    pass
+                                if not _skip_uplink:
+                                    # 会话已 started:若此时上行未启动,则在这里补一次开麦,避免“Start 前开麦被 Close 停掉”后
+                                    # 出现首轮无音频上行导致“第一次对话没有”的情况。
+                                    if self.record_data_save_flag == 0:
+                                        if self.VC_record_state_flag == 0:
+                                            self.request_uplink_start("server.started")
+                                        else:
+                                            self._vc_clear_stale_record_state("server.started")
+                                            self.request_uplink_start("server.started/retry")
+                            
+                            print("VC:Started!!!!!!!!\r\n")
+                            print("TTS音频为:{},语音下发地址为:{}".format(self.AI_TTS_str,self.AI_new_voice_url))
+                            if self.AI_new_voice_url != "" and self.alarm_play_flag == 1 :#有闹钟,先响闹钟
+                                self.AI_VC_start()
+                                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放铃声
+                                self.alarm_play_flag = 0
+                                self.voice_choose_flag = 1
+                            elif self.AI_TTS_str == "" and  self.AI_new_voice_url != "" and self.music_play_flag == 0:
+                                # if EventMesh.publish("get_history_voice_flag") == 1:
+                                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言
+                                # self.recv_voice = 0#当留言列表为空,留言播放标志清空
+                                # else:
+                                #     self.voice_cnt = 0
+                                #     print("定时器开启222222222")
+                                #     self.voice_timer.start(1000,1,self.voice_judge)
+                            elif  self.AI_TTS_str != "" and  self.AI_new_voice_url == "":
+                                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})
+                            
+                            elif self.AI_TTS_str != "" and  self.AI_new_voice_url != "" and self.music_play_flag == 0:#遇到tts和语音留言冲突时(并且没有音乐播放)
+                                if EventMesh.publish("get_history_voice_flag") == 1:#播放历史留言
+                                    if EventMesh.publish("get_wait_voice_flag") == 0:
+                                        EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})
+                                        self.his_cnt = 0
+                                        self.his_timer.start(1000,1,self.history_play) 
+                                        self.voice_choose_flag = 1
+                                    else:#等待留言
+                                        if self.pwk_notice_flag == 0:
+                                            self.wait_cnt = 0
+                                            print("未听播放1")
+                                            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})
+                                            self.wait_timer.start(1000,1,self.wait_play) 
+                                            self.voice_choose_flag = 1
+                            
+                                        else:#已确定播放
+                                            print("未听播放2")
+                                            self.wait_cnt = 0
+                                            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})
+                                            self.wait_timer.start(1000,1,self.wait_single_play) 
+                                            self.voice_choose_flag = 1
+                                else:#播放正常留言
+                                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})
+                                    self.voice_cnt = 0
+                                    print("定时器开启111111111")
+                                    self.voice_timer.start(1000,1,self.voice_judge) 
+                                    self.voice_choose_flag = 1
+                                # self.AI_TTS_str = ""    
+                            elif self.AI_TTS_str != "" and  self.AI_new_voice_url != "" and self.music_play_flag == 1 :#遇到tts和语音留言冲突时(并且有音乐播放)
+                                self.music_play_flag = 0
+                                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_synthesis})
+                                self.music_timer.start(1000,1,self.music_play) 
+                                self.voice_choose_flag = 1
+                            else:
+                                #开始上传语音
+                                try:
+                                    if "vc_config" in globals() and vc_config and vc_config.get_bool("UPLOAD_START_TONE_ENABLED", False):
+                                        self.chenggong_tone()
+                                except Exception:
+                                    pass
+                                if self.VC_mode == 0:
+                                    self.request_uplink_start("server.started/idle")
+                                else:
+                                    # Push-To-Talk:必须先同步发出 audio.create,再开麦。
+                                    # request_send_audio_create() 走 EventMesh 异步队列,会导致录音线程先于
+                                    # audio.create 上行,深度睡眠唤醒后首轮 ASR 空结果、第二轮才正常。
+                                    try:
+                                        self.AI_client_VC_send_speech()
+                                    except Exception:
+                                        self.request_send_audio_create()
+                                    # 若长按发生在“未 started”窗口,收到 started 后立即开麦进入新一轮 PTT
+                                    try:
+                                        if getattr(self, "_ptt_force_record_on_started", 0) == 1:
+                                            self._ptt_force_record_on_started = 0
+                                            self.VC_Speech_Start_now()
+                                    except Exception:
+                                        pass
+                                
+                            
+                            # if self.AI_new_voice_url != "":
+                            #     EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})
+
+                        elif jsload_data["event"] == "config.update" :#配置更新中,需要停止录音
+                            try:
+                                self.fsm.reset_rx_phases()
+                            except Exception:
+                                pass
+                            self.request_uplink_stop("config.update")
+
+                        elif jsload_data["event"] == "asr.started" :#asr开始 
+                            try:
+                                self.fsm.on_server_asr_started()
+                            except Exception:
+                                pass
+                            self._server_vad_silence = 0
+                            self._turn_has_asr_completed = 0
+                            self._turn_has_vad_silence = 0
+                            self._turn_has_tts_started = 0
+                            self._llm_completed_ms = 0
+                            try:
+                                self._asr_completed_tone_done = 0
+                            except Exception:
+                                pass
+                            # 单对话:_drop_tts_audio 在 VC_Speech_Start 真开录后再清 0,避免 canceled 后仍播旧 TTS
+                            try:
+                                if getattr(self, "VC_mode", 0) != 1:
+                                    self._drop_tts_audio = 0
+                            except Exception:
+                                pass
+                            # 单对话:勿在 asr.started 置 flag=2(未真开麦会僵死);由 VC_Speech_Start 维护
+                            self.LocalRespondingEnded_timerout = 0
+                        elif jsload_data["event"] == "asr.result" :#asr结果
+                            try:
+                                self.AI_ASR_str = jsload_data["payload"]["content"]
+                            except Exception:
+                                try:
+                                    self.AI_ASR_str = jsload_data.get("payload", {}).get("text", "") or ""
+                                except Exception:
+                                    self.AI_ASR_str = ""
+                            print("[VC][ASR] {}".format(self.AI_ASR_str))
+                            try:
+                                if (self.AI_ASR_str or "").strip():
+                                    self._last_asr_result_text = self.AI_ASR_str
+                            except Exception:
+                                pass
+                            if self.AI_ASR_str == "":#没识别到
+                                self.AI_TTS_str = "我没听清,请再说一遍"
+                                self._vc_led_alert_rb_flash()
+                        elif jsload_data["event"] == "asr.completed" :#asr 终止的标识 
+                            self._turn_has_asr_completed = 1
+                            try:
+                                if not (self.AI_ASR_str or "").strip():
+                                    self.AI_ASR_str = getattr(self, "_last_asr_result_text", "") or ""
+                            except Exception:
+                                pass
+                            # 本地 think 提示:仅在有有效 ASR 文本时播,避免空轮次连播「滴」
+                            try:
+                                if getattr(self, "_asr_completed_tone_done", 0) == 1:
+                                    pass
+                                elif not self._vc_effective_asr_text():
+                                    try:
+                                        print("[VC][ASR] completed empty, skip think tone")
+                                    except Exception:
+                                        pass
+                                else:
+                                    stream_on = False
+                                    try:
+                                        stream_on = EventMesh.publish("get_vc_tts_playback_active") == 1
+                                    except Exception:
+                                        stream_on = False
+                                    pcm_busy = getattr(self, "play_pcm", None) is not None
+                                    playing = getattr(self, "VC_Play_flag", 0) == 1
+                                    try:
+                                        thinking = getattr(self.fsm, "turn", VCTurnState.Idle) == VCTurnState.Thinking
+                                    except Exception:
+                                        thinking = False
+                                    if (not stream_on) and (not pcm_busy) and (not playing) and (not thinking):
+                                        self._vc_local_play('U:/static/think.mp3', 1, 1, 650)
+                                        self._asr_completed_tone_done = 1
+                            except Exception:
+                                pass
+                            print("self.commands_mode:{}".format(self.commands_mode))
+                            if self.commands_mode ==2:
+                                ret= self.check_text(self._vc_effective_asr_text())
+                                if ret == 0:#配置指令
+                                            
+                                    if self.AI_VC_send_data_queue.empty()!=True:
+                                        self.AI_VC_send_data_queue.clear()
+                                    if self.AI_ASR_send_data_queue.empty()!=True:
+                                        self.AI_ASR_send_data_queue.clear()
+                                    self.AI_ASR_send_buf_len = 0
+                                    EventMesh.publish("set_led_state", {"ai_state": "thinking"})
+                                    try:
+                                        
+                                        self.request_uplink_stop("asr.completed/config_cmd")
+                                        EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) 
+                                        EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) 
+                                    except Exception as e:
+                                        usys.print_exception(e)
+                                    continue
+                                elif ret == 1:#不处理
+                                    pass    
+                            #self.VC_Speech_End()
+                            #EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})
+
+                            # if self.record_data_save_flag == 1:
+                            #     if self.record_fd :
+                            #         self.record_fd.close()
+                            #     self.record_fd = None
+
+                            # if dev_type != "7PIN" :
+                            #EventMesh.publish("publish_AI_btnInfo",{"func":self.send_tone}) 
+                        elif jsload_data["event"] == "vad.active" :#VAD 检测到说话→聆听
+                            try:
+                                if "vc_config" in globals() and vc_config and vc_config.get_bool("VAD_ACTIVE_BLUE_LED", True):
+                                    self._server_vad_silence = 0
+                            except Exception:
+                                pass
+                        elif jsload_data["event"] == "vad.silence" :#VAD 终止的标识 
+                            # 说明服务端已判定“本轮说话结束”;此时继续上行只会干扰后续(例如推迟 TTS)。
+                            # 仅在“正在录音上行”的语音阶段才停止,避免误触发。
+                            self._server_vad_silence = 1
+                            self._turn_has_vad_silence = 1
+                            self._uplink_mark_end("vad.silence")
+                            if self.record_data_save_flag == 0:
+                                if self.VC_record_state_flag != 0 and self._vc_vad_uplink_stop_eligible():
+                                    self.request_uplink_stop("vad.silence")
+                        elif jsload_data["event"] == "llm.created" :#思考(LLM)
+                            try:
+                                self._ptt_await_server = 0
+                            except Exception:
+                                pass
+                            try:
+                                EventMesh.publish("set_led_state", {"ai_state": "thinking"})
+                            except Exception:
+                                pass
+                            self._llm_completed_ms = 0
+                            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Play_Start})
+                            pass
+                        elif jsload_data["event"] == "llm.completed" :#llm完成(可能无 tts)
+                            try:
+                                self._llm_completed_ms = utime.ticks_ms()
+                            except Exception:
+                                # 勿用 1:ticks_diff(now,1)≈开机至今,会瞬间满足 thinking 超时误以为该重启
+                                self._llm_completed_ms = 0
+                            try:
+                                self._cur_dialog_id = jsload_data.get("payload", {}).get("dialog_id", "") or ""
+                            except Exception:
+                                self._cur_dialog_id = ""
+                            try:
+                                self.fsm.log("server.llm.completed")
+                            except Exception:
+                                pass
+                            pass
+                        elif jsload_data["event"] == "llm.delta" :#llm返回数据
+                            if self.is_VAD == 0 and self.record_data_save_flag == 0:
+                                if self.VC_record_state_flag == 1:#llm返回数据还没停止,就先停止
+                                    self.request_uplink_stop("llm.delta")
+                        elif jsload_data["event"] == "tts.started" :#tts开始
+                            try:
+                                self._ptt_await_server = 0
+                            except Exception:
+                                pass
+                            self._server_vad_silence = 1
+                            self._turn_has_tts_started = 1
+                            self._tts_pending = 0
+                            self._tts_pending_ms = 0
+                            self._uplink_mark_end("tts.started")
+                            # stop uplink + play_start 由 FSM action list 统一触发,避免多处重复调用
+                            # 标记本轮 TTS 来源:
+                            # - 有 payload.dialog_id:认为是“对话回复 TTS”
+                            # - 无 dialog_id:认为是“提示/提醒 TTS”(模式切换可通过 _pending_tts_kind 标记)
+                            try:
+                                did = jsload_data.get("payload", {}).get("dialog_id", "") or ""
+                            except Exception:
+                                did = ""
+                            # 注意:无 dialog_id 的 tts.started 很可能是提示音/提醒音;
+                            # 但“模式切换提示音”的 kind 可能已在 synthesis 时写入 _last_tts_kind,
+                            # 这里不应无条件覆盖为 prompt,否则播完无法触发下一轮开麦。
+                            try:
+                                if did:
+                                    self._last_tts_kind = "dialog"
+                                else:
+                                    k = getattr(self, "_pending_tts_kind", "") or ""
+                                    if k:
+                                        self._last_tts_kind = k
+                                    else:
+                                        cur = getattr(self, "_last_tts_kind", "") or ""
+                                        if cur in (
+                                            "mode_switch_long",
+                                            "mode_switch_ptt",
+                                            "boot_prompt",
+                                        ):
+                                            pass
+                                        elif not cur:
+                                            self._last_tts_kind = "prompt"
+                            except Exception:
+                                pass
+                            try:
+                                self._pending_tts_kind = ""
+                            except Exception:
+                                pass
+                            # 播放灯由阶段事件控制
+                            try:
+                                self._tts_completed_wall_ms = 0
+                            except Exception:
+                                pass
+                        elif jsload_data["event"] == "tts.completed" :#tts结束
+                            # tts.completed 仅表示下发结束,本地可能仍在播放;绿灯关闭在本地播放结束时处理
+                            try:
+                                # server event 已在统一入口 on_server_event 触发 FSM,这里避免重复驱动
+                                pass
+                            except Exception:
+                                pass
+                            try:
+                                try:
+                                    sid = getattr(self, "_cur_sid", "")
+                                    did = getattr(self, "_cur_dialog_id", "")
+                                except Exception:
+                                    sid, did = "", ""
+                                print("[TTS] completed in_len={} out_len={} q_empty={} play_pcm={} sid={} did={}".format(
+                                    getattr(self, "tts_in_data_len", 0),
+                                    getattr(self, "tts_out_data_len", 0),
+                                    1 if self.AI_audio_queue.empty() else 0,
+                                    1 if self.play_pcm != None else 0,
+                                    sid,
+                                    did,
+                                ))
+                            except Exception:
+                                pass
+                            try:
+                                self._tts_completed_wall_ms = utime.ticks_ms()
+                            except Exception:
+                                self._tts_completed_wall_ms = 0
+                            # 播完上报完全由 FSM await_played + play_end 驱动,这里不再使用 LocalRespondingEnded_timerout/_need_audio_played_ack
+                            pass
+
+                        elif jsload_data["event"] == "canceled" :#打断
+                            # 停播/清队列已在 _vc_dispatch_server_event(canceled) 完成;此处仅收尾标志
+                            self.is_ruppt_tts = 0#设备上发中断tts,收到服务器返回的cancel后置为0
+                            self._need_audio_played_ack = 0
+                            self._tts_pending = 0
+                            self._tts_pending_ms = 0
+                            # 开麦由 FSM action list 统一触发(uplink_start:canceled),避免重复调用
+                            if self.AI_audio_queue_thread_id ==0:
+                                _thread.stack_size(24*1024)
+                                self.AI_audio_queue_thread_id = _thread.start_new_thread(self.AI_audio_queue_thread, ())
+                                _thread.stack_size(8*1024)
+                                print("AI_VC_start :AI_audio_queue_thread_id :{} ".format(self.AI_audio_queue_thread_id))
+                            else:
+                                print("AI_VC_start :AI_audio_queue_thread_id != 0 ")
+                        elif jsload_data["event"] == "completed" :#会话结束(可能无 TTS,如 ASR 空)
+                            # 重要:服务端常在 tts.completed 后紧跟 completed。
+                            # 若本轮存在 TTS/待发 audio.played,则不能在此清零 LocalRespondingEnded_timerout/_need_audio_played_ack,
+                            # 否则会掐断「播完→audio.played→下一轮录音」链路,表现为最多 1~2 轮就停。
+                            if self._need_audio_played_ack == 1 or self._vc_tts_pipeline_busy() or self.VC_Play_flag == 1:
+                                pass
+                            else:
+                                # 无 TTS 的 completed:清状态并按需进入下一轮(见下方条件)
+                                self.LocalRespondingEnded_timerout = 0
+                                self._need_audio_played_ack = 0
+                                try:
+                                    self.fsm.reset_rx_phases()
+                                except Exception:
+                                    pass
+                            # 连续对话:若本轮没有 TTS(或服务端直接 completed),需要立刻回到聆听状态
+                            # 否则会出现“有时 1 轮/2 轮后就停住”的现象(服务端 completed 但端侧未重新开录音上行)。
+                            if self.VC_mode == 0 and self.Close_flag == 0:
+                                # 仅当“本轮已结束一次语句(vad.silence 或 asr.completed)且从未进入 tts.started”
+                                # 才自动进入下一轮;避免把真正的“会话结束/异常结束”误当成下一轮入口。
+                                if (self._turn_has_tts_started == 0) and (self._turn_has_vad_silence == 1 or self._turn_has_asr_completed == 1):
+                                    self._server_vad_silence = 0
+                                    # 开麦由 FSM action list 统一触发(uplink_start:completed),这里不再重复调用
+                            pass
+
+                        elif jsload_data["event"] == "command" :#命令匹配
+                            ret = -1
+                            try:
+                                if self.commands_mode ==1:
+                                    if len(jsload_data["payload"]["functions"])>0:
+                                        
+                                        cmd = jsload_data["payload"]["functions"][0]["function"]["arguments"]
+                                        print("llm:cmd:{}".format(cmd))
+                                        cmd = ujson.loads(cmd)
+                                        ret = self.check_text_llm(cmd)
+                                elif self.commands_mode ==0:
+
+                                    cmd = jsload_data["payload"]
+                                    print("server:cmd:{}".format(cmd))
+                                    ret = self.check_text_server(cmd)
+                                else:
+                                    cmd = jsload_data["payload"]["functions"][0]["function"]["arguments"]
+                                    print("llm:cmd:{}".format(cmd))
+                                    cmd = ujson.loads(cmd)
+                                    ret = self.check_text_llm(cmd)
+                            except Exception as e:
+                                usys.print_exception(e)       
+                            if ret == -1:
+                                if self.AI_TTS_str  =="":
+                                    self.AI_TTS_str = "抱歉,没找到对应操作,可以换个说法试试" 
+                            #EventMesh.publish("publish_AI_btnInfo",{"func":utime.sleep_ms,'data':(10)})
+                            self.request_uplink_stop("command")
+                            EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) 
+                            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) 
+                        elif jsload_data["event"] == "failed":#接口返回错误
+                            if jsload_data["payload"]["error"] == "concurrency_limit_exceeded":
+                                EventMesh.publish("publish_AI_btnInfo",{"func":self.concurrency_limit_exceeded_tone}) 
+                                
+                                #utime.sleep_ms(4000)
+                                
+                            else:
+                                EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":jsload_data["payload"]["message"]}) 
+                            pass
+                           
+                        elif jsload_data["event"] == "audio.convert.completed":#音频转成完成没有出错 接口 
+                            # self.VC_state = VC_State_End
+                            print("未读留言初始数值:{}".format(self.pwk_cnt_voice))
+                            if self.pwk_notice_flag == 1:#只有在播放的时候才会去计数
+                                if self.pwk_cnt_voice > 0:
+                                    self.pwk_cnt_voice -= 1#减1
+                                    print("未读留言又-1,当前留言剩余数量为:{}".format(self.pwk_cnt_voice))
+                                else:
+                                    print("未读列表为空")
+                            if self.pwk_cnt_voice == 0:#积累的留言已经全部播完
+                                self.pwk_notice_flag = 0
+                                self.wait_sort = 0
+                                self.wait_first_flag = 0#让下次未读播放提示恢复
+                                self.voicerupt_flag = 0 #标志位置0
+                                self.wait_voicerupt_flag = 0
+                                self.voice_notice = 1#开启回复
+                                if self.history_voice_flag == 1 and self.wait_voice_flag == 0:
+                                    self.voice_notice = 0
+                                EventMesh.publish("set_storage_config",["is_noread_voice",0])
+                                
+                            if self.voice_choose_flag == 1:#播放完语音后,有冲突的时候关闭
+                                self.voice_choose_flag = 0
+                                self.Close_flag = 1
+                                self.LocalRespondingEnded_timerout = 8   
+                                # EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) 
+                            if self.voice_notice == 1:#已收到留言,进行该留言回复逻辑处理
+                                tts_play_str = "如需回复,长按确认键开始录制,松开发送。十秒内无操作自动退出"
+                                EventMesh.publish("play_tts",[tts_play_str,1])
+                                self.voice_notice = 0#标志位清空
+                                self.voice_timer.stop()
+                                self.revoice_cnt = 0
+                                self.voice_flag = 1
+                                print("语音留言回复停止")
+                                self.revoice_timer.start(1000,1,self.revoice_judge)
+                            print("是否未读:{},是否播完:{}".format(EventMesh.publish("get_wait_voice_flag"),len(EventMesh.publish("get_play_new_voice_list"))))
+                            # if EventMesh.publish("get_wait_voice_flag") == 1 and len(EventMesh.publish("get_play_new_voice_list")) ==0:#还在未读并且列表为空
+                            #     self.wait_first_flag = 0
+                            #     print("未读留言播完,清空标志")
+                            #     EventMesh.publish("set_wait_voice_flag",0)
+                            #     EventMesh.publish("set_history_voice_flag",0)
+                            EventMesh.publish("update_play_new_voice_info")#更新已读状态
+                            self.recv_voice = 0
+                            print("不开启定时器标志位置0")
+                            self.LocalRespondingEnded_timerout = 8
+                            try:
+                                self.fsm.reset_rx_phases()
+                            except Exception:
+                                pass
+                                
+                            pass
+                        elif jsload_data["event"] == "audio.convert.failed": #音频转换失败了
+                            pass
+                                
+                        
+                    else:#下发音频数据
+                        #self.AI_ASR_str = ""#下发音频后清掉asr结果
+                        
+                        
+                        # PTT 打断后丢弃残留 TTS 音频帧,避免“断断续续继续播”
+                        try:
+                            if self._ptt_should_drop_inbound_tts():
+                                continue
+                        except Exception:
+                            pass
+                        if self.VC_stop_play_audio_flag == 0:
+                            print("新的AI对话不允许以前的播放数据:")
+                            break
+
+                        # if VC_AUDIO_FRAME_LOG:
+                        #     print("下发音频数据1:{}".format(len(recv_data)))
+
+                        if self.play_data_save_flag == 1:
+                            if self.play_fd ==None:
+                                try:
+                                    uos.remove('/usr/play_data.pcm')
+                                except Exception as e:
+                                    usys.print_exception(e)
+                                self.play_fd = open('/usr/play_data.pcm','w')
+                            if self.play_fd !=None:
+                                self.play_fd.write(recv_data)
+                        # print("下发音频数据2:{}".format(len(recv_data)))
+                        #if self.VC_state >= VC_State_TTSStart:
+                            #self.put_record_data([recv_data,""])
+                        if self.is_ruppt_tts == 0:#被设备主动打断打断则不播放,知道收到服务器返回的cancel才修改标志位
+                            if self.VC_play_TYPE == VC_play_TYPE_PCM:
+                                if self.play_pcm != None:
+                                    try:
+                                        #print("play_pcm.stream_write !!!")
+                                        buf_len = self.play_pcm.stream_get_buf_len()
+                                        count_tmp = 1000
+                                        while buf_len < len(recv_data):
+                                            utime.sleep_ms(1)
+                                            count_tmp-=1
+                                            if count_tmp<=0 or self.play_pcm == None:
+                                                break
+                                            buf_len = self.play_pcm.stream_get_buf_len()
+                                        #print("buf已满,等待释放:buf_len:{},count_tmp:{}".format(buf_len,count_tmp))
+                                    except Exception as e:
+                                        usys.print_exception(e)
+                            EventMesh.publish("publish_AI_btnInfo",{"func":self.put_record_data,"data":[recv_data,""]}) 
+                        else:
+                            print("被打断不播放") 
+                        utime.sleep_ms(1)
+                except Exception as e:
+                    usys.print_exception(e)
+                
+            except Exception as e:
+                    error_str = str(e)
+                    #print("VC VC:e:{}".format(error_str))
+                    if error_str.find("[Errno 110] ETIMEDOUT")!= -1 or error_str.find("[Errno 115] ")!= -1:
+                        if VC_HEARTBEAT_LOG:
+                            print("VC VC:recv 正常报错:{}".format(error_str))
+                        # recv 超时仅表示本周期无帧,链路仍可能在等 LLM/TTS;清零避免误触发「接口无响应」
+                        self.close_timerout = 0
+                        pass
+                    else:
+                        usys.print_exception(e)
+                        #self.websocket_error()
+                        print("VC VC :读数据报错,退出")
+                        EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error,"data":"VC出错了,请重试"}) 
+                        self.ud_error(["VC",self.VC_taskid])
+                        break
+        self.AI_client_VC_isconnect = 0
+        self.AI_client_VC_recv_id = 0 
+        self.AI_client_VC_recv_lock = 0
+        print("[VC] recv_thread exit")
+    def get_recv_voice(self,event,msg):
+        return self.recv_voice
+    def set_recv_voice(self,event,msg):
+        return self.recv_voice
+    def check_text_server(self,cmd):
+        return -1
+    def _apply_vc_text_intent(self, text, cmd, llm_style_tts):
+        """表驱动文本意图(vc_text_intent)。未命中返回 None;命中返回 0/1/-1。"""
+        if vc_text_intent is None:
+            return None
+        try:
+            try:
+                _btv = bool("vc_config" in globals() and vc_config and vc_config.get_bool("BT_VOICE_FEATURES_ENABLED", False))
+            except Exception:
+                _btv = False
+            r = vc_text_intent.match_vc_command(text, {"bt_voice": _btv})
+        except Exception as e:
+            usys.print_exception(e)
+            return None
+        if not r:
+            return None
+        return self._apply_vc_text_intent_matched(r, cmd, llm_style_tts)
+    def _apply_vc_text_intent_matched(self, r, cmd, llm_style_tts):
+        if r.get("needs_payload") and cmd is None:
+            return None
+        it = r["intent"]
+        if r.get("tts"):
+            self.AI_TTS_str = r["tts"]
+        if r.get("close_flag"):
+            self.Close_flag = 1
+        if it == "goodbye":
+            return 0
+        if it == "shutdown":
+            if llm_style_tts:
+                EventMesh.publish("publish_AI_btnInfo", {"func": self.Close})
+                EventMesh.publish("Power_off")
+                return 1
+            try:
+                close_only = ("vc_config" in globals() and vc_config and vc_config.get_bool("POWEROFF_CMD_CLOSE_ONLY", True))
+            except Exception:
+                close_only = True
+            if close_only:
+                EventMesh.publish("publish_AI_btnInfo", {"func": self.Close})
+                return 1
+            EventMesh.publish("publish_AI_btnInfo", {"func": self.Close})
+            EventMesh.publish("Power_off")
+            return 1
+        if it == "toggle_dialog_mode":
+            if self.VC_mode == 0:
+                self.VC_mode = 1
+                if llm_style_tts:
+                    self.AI_TTS_str = "了解,已切换到单对话模式"
+                else:
+                    try:
+                        self.AI_TTS_str = vc_config.get_attr("CMD_TOGGLE_MODE_TO_PTT_TTS", "已切换到单对话模式")
+                    except Exception:
+                        self.AI_TTS_str = "已切换到单对话模式"
+            else:
+                self.VC_mode = 0
+                if llm_style_tts:
+                    self.AI_TTS_str = "了解,已切换到长对话模式"
+                else:
+                    try:
+                        self.AI_TTS_str = vc_config.get_attr("CMD_TOGGLE_MODE_TO_LONG_TTS", "已切换到长对话模式")
+                    except Exception:
+                        self.AI_TTS_str = "已切换到长对话模式"
+            EventMesh.publish("set_storage_config", ["VC_mode", self.VC_mode])
+            return 0
+        if it == "volume_set":
+            try:
+                pj = ujson.loads(cmd["command"]["params_json"])
+                volume = pj["n"]
+            except Exception as e:
+                usys.print_exception(e)
+                try:
+                    self.AI_TTS_str = cmd["command"]["fail_prompt"]
+                except Exception:
+                    self.AI_TTS_str = ""
+                return -1
+            if volume > 100 or volume < 0:
+                self.AI_TTS_str = "音量调整超出范围了,只能0-100哦"
+            else:
+                self.VC_TTS_volume = volume
+                self.aud.setVolume(self.VC_TTS_volume // 10)
+                EventMesh.publish("set_storage_config", ["VC_TTS_volume", self.VC_TTS_volume])
+                self.AI_TTS_str = "了解,音量已经调整,当前音量是{}".format(self.VC_TTS_volume)
+                EventMesh.publish("post_device_state")
+                EventMesh.publish("adjust_volume_toBle", self.VC_TTS_volume)
+            return 0
+        if it == "volume_up":
+            if llm_style_tts:
+                try:
+                    print("self.VC_TTS_volume add:{}".format(self.VC_TTS_volume))
+                except Exception:
+                    pass
+            self.VC_TTS_volume += 10
+            if self.VC_TTS_volume > 100:
+                self.AI_TTS_str = "音量已经是最大啦" if llm_style_tts else "音量已最大"
+                self.VC_TTS_volume = 100
+            else:
+                self.aud.setVolume(self.VC_TTS_volume // 10)
+                EventMesh.publish("set_storage_config", ["VC_TTS_volume", self.VC_TTS_volume])
+                EventMesh.publish("post_device_state")
+                EventMesh.publish("adjust_volume_toBle", self.VC_TTS_volume)
+                self.AI_TTS_str = (
+                    "了解,音量已经调大了,当前音量是{}".format(self.VC_TTS_volume)
+                    if llm_style_tts
+                    else "配置成功,当前音量 {}".format(self.VC_TTS_volume)
+                )
+            return 0
+        if it == "volume_down":
+            if llm_style_tts:
+                try:
+                    print("self.VC_TTS_volume end:{}".format(self.VC_TTS_volume))
+                except Exception:
+                    pass
+            self.VC_TTS_volume -= 10
+            if self.VC_TTS_volume < 0:
+                self.VC_TTS_volume = 0
+                self.AI_TTS_str = "音量已经是最小啦" if llm_style_tts else "音量已最小"
+            else:
+                self.aud.setVolume(self.VC_TTS_volume // 10)
+                EventMesh.publish("set_storage_config", ["VC_TTS_volume", self.VC_TTS_volume])
+                EventMesh.publish("post_device_state")
+                EventMesh.publish("adjust_volume_toBle", self.VC_TTS_volume)
+                self.AI_TTS_str = (
+                    "好嘞,音量已经调小了,当前音量是{}".format(self.VC_TTS_volume)
+                    if llm_style_tts
+                    else "配置成功,当前音量 {}".format(self.VC_TTS_volume)
+                )
+            return 0
+        if it in ("speech_faster", "speech_rate_up"):
+            self.speech_rate += 10
+            if self.speech_rate > 50:
+                self.AI_TTS_str = "语速已经是最快了"
+                self.speech_rate = 50
+            else:
+                EventMesh.publish("set_storage_config", ["speech_rate", self.speech_rate])
+                self.AI_TTS_str = "明白,语速已经调快了,当前语速是{}".format(self.speech_rate)
+            return 0
+        if it in ("speech_slower", "speech_rate_down"):
+            self.speech_rate -= 10
+            if self.speech_rate < -50:
+                self.speech_rate = -50
+                self.AI_TTS_str = "语速已经是最慢了"
+            else:
+                EventMesh.publish("set_storage_config", ["speech_rate", self.speech_rate])
+                self.AI_TTS_str = "明白,语速已经调慢了,当前语速是{}".format(self.speech_rate)
+            return 0
+        if it == "vad_faster":
+            self.vad_silence -= 200
+            if self.vad_silence < 100:
+                self.AI_TTS_str = "已经是最快了"
+                self.vad_silence = 100
+            else:
+                self.AI_TTS_str = "配置成功,当前语音断句速度是{}毫秒".format(self.vad_silence)
+            EventMesh.publish("set_storage_config", ["vad_silence", self.vad_silence])
+            return 0
+        if it == "vad_slower":
+            self.vad_silence += 200
+            if self.vad_silence > 2000:
+                self.vad_silence = 2000
+                self.AI_TTS_str = "已经是最慢了"
+            else:
+                self.AI_TTS_str = "配置成功,当前语音断句速度是{}毫秒".format(self.vad_silence)
+            EventMesh.publish("set_storage_config", ["vad_silence", self.vad_silence])
+            return 0
+        if it == "query_signal":
+            signal = net.csqQueryPoll()
+            self.AI_TTS_str = "当前信号{}".format(signal)
+            return 0
+        if it == "query_battery":
+            battery = EventMesh.publish("get_battery")
+            try:
+                print("电量查询:{}".format(battery))
+            except Exception:
+                pass
+            self.AI_TTS_str = "电量剩余百分之{}".format(battery)
+            return 0
+        if it == "query_voltage":
+            Vbatt = Power.getVbatt()
+            self.AI_TTS_str = "当前电池电压{}".format(Vbatt)
+            return 0
+        if it == "query_iccid":
+            iccid = sim.getIccid()
+            if iccid != -1 and len(iccid) > 5:
+                try:
+                    print("iccid{}".format(iccid))
+                except Exception:
+                    pass
+                self.AI_TTS_str = "当前卡号为:{}".format(iccid)
+            else:
+                self.AI_TTS_str = "卡号查询失败"
+            return 0
+        if it == "query_imei":
+            imei = modem.getDevImei()
+            if imei != -1 and len(imei) > 5:
+                try:
+                    print("IMEI:{}".format(imei))
+                except Exception:
+                    pass
+                self.AI_TTS_str = "当前设备号为:{}".format(imei)
+            else:
+                self.AI_TTS_str = "设备号查询失败"
+            return 0
+        if it == "query_version":
+            bt_versions = EventMesh.publish("get_bt_versions")
+            if bt_versions is None or bt_versions == "":
+                self.AI_TTS_str = "当前版本号:" + version_util.get_tts_version()
+            else:
+                self.AI_TTS_str = "当前版本号:" + version_util.get_tts_version(bt_versions)
+            return 0
+        if it == "query_server":
+            self.AI_TTS_str = "服务器地址" + self.server_ip
+            return 0
+        if it == "ota_check":
+            if self.ai_OTA.min_sys_ota() == 1:
+                self.AI_TTS_str = "有新版本,准备开始升级,升级过程会多次重启设备,请保证流量充足及电量充足"
+            else:
+                self.AI_TTS_str = "当前已是最新版本"
+            return 0
+        if it == "factory_mode":
+            self.factory_mode()
+            return 1
+        if it == "alarm_set":
+            try:
+                alarm = ujson.loads(cmd["command"]["params_json"])
+                try:
+                    print("params_json:{}".format(cmd["command"]["params_json"]))
+                except Exception:
+                    pass
+                add_alarm_tmp = EventMesh.publish("AI_add_alarm", alarm["alarm"])
+                if add_alarm_tmp[0] > 0:
+                    self.AI_TTS_str = add_alarm_tmp[1]
+                    return 1
+            except Exception as e:
+                usys.print_exception(e)
+            try:
+                self.AI_TTS_str = cmd["command"]["fail_prompt"]
+            except Exception:
+                self.AI_TTS_str = ""
+            return -1
+        if it == "alarm_clear":
+            try:
+                self.AI_TTS_str = EventMesh.publish("AI_delete_alarm")
+            except Exception as e:
+                usys.print_exception(e)
+            return 0
+        if it == "alarm_list":
+            EventMesh.publish("read_alarm_list")
+            return 0
+        if it == "voice_message_mode":
+            self.voice_flag = 1
+            self.AI_TTS_str = "已进入语音留言模式,长按按钮开始录音,松开按钮结束"
+            self.Close_flag = 1
+            self.stop_voice_timer.stop()
+            self.stop_voice_timer.start(30 * 1000, 0, self.exit_voice_message_timer)
+            try:
+                print("语音留言!!!!!!!!!!")
+            except Exception:
+                pass
+            return 0
+        if it == "bt_ble_adv_on":
+            self.AI_TTS_str = "正在开蓝牙广播"
+            EventMesh.publish("AI_send_bt_ble_switch_toble", 1)
+            try:
+                print("正在开蓝牙广播")
+            except Exception:
+                pass
+            return 0
+        if it == "bt_ble_adv_off":
+            self.AI_TTS_str = "正在关蓝牙广播"
+            EventMesh.publish("AI_send_bt_ble_switch_toble", 2)
+            try:
+                print("正在关蓝牙广播")
+            except Exception:
+                pass
+            return 0
+        if it == "bt_classic_on":
+            self.AI_TTS_str = "正在开经典蓝牙"
+            EventMesh.publish("AI_send_bt_ble_switch_toble", 3)
+            try:
+                print("正在开经典蓝牙")
+            except Exception:
+                pass
+            return 0
+        if it == "bt_classic_off":
+            self.AI_TTS_str = "正在关经典蓝牙"
+            EventMesh.publish("AI_send_bt_ble_switch_toble", 4)
+            try:
+                print("正在关经典蓝牙")
+            except Exception:
+                pass
+            return 0
+        if it == "unbind_confirm":
+            self.AI_TTS_str = "设备解绑中...."
+            self.Close_flag = 1
+            EventMesh.publish("send_unbind_dev_to_iot")
+            try:
+                print("解绑设备!!!!!!!!!!!!!!")
+            except Exception:
+                pass
+            return 0
+        if it == "clear_earphone":
+            self.AI_TTS_str = "正在删除"
+            EventMesh.publish("clear_earphone_record")
+            try:
+                print("正在删除")
+            except Exception:
+                pass
+            return 0
+        return None
+    def check_text_llm(self,cmd):
+        try:
+            desc = cmd["command"]["chinese_description"]
+            if desc.find("切换音色") != -1:
+                text = cmd["command"]["params_json"]
+                try:
+                    for key in self.VC_TTS_voice_type:
+                        if text.find(key) != -1:
+                            self.VC_TTS_voice_key = key
+                            EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])
+                            self.AI_TTS_str = "了解,声音已切换"
+                            return 0
+                except Exception as e:
+                    usys.print_exception(e)
+                if text.find("男") != -1:
+                    if "男" in self.VC_TTS_voice_type:
+                        self.VC_TTS_voice_key = "男"
+                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])
+                        self.AI_TTS_str = "了解,声音已切换"
+                        return 0
+                if text.find("女") != -1:
+                    if "女" in self.VC_TTS_voice_type:
+                        self.VC_TTS_voice_key = "女"
+                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])
+                        self.AI_TTS_str = "了解,声音已切换"
+                        return 0
+                if text.find("同声") != -1 or text.find("童") != -1:
+                    if "童" in self.VC_TTS_voice_type:
+                        self.VC_TTS_voice_key = "童"
+                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])
+                        self.AI_TTS_str = "了解,声音已切换"
+                        return 0
+                if text.find("粤") != -1 or text.find("越野") != -1 or text.find("广东话") != -1:
+                    if "粤" in self.VC_TTS_voice_type:
+                        self.VC_TTS_voice_key = "粤"
+                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])
+                        self.AI_TTS_str = "了解,声音已切换"
+                        return 0
+                if text.find("四川") != -1:
+                    if "四川" in self.VC_TTS_voice_type:
+                        self.VC_TTS_voice_key = "四川"
+                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])
+                        self.AI_TTS_str = "了解,声音已切换"
+                        return 0
+                if text.find("个性") != -1:
+                    if "个性" in self.VC_TTS_voice_type:
+                        self.VC_TTS_voice_key = "个性"
+                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])
+                        self.AI_TTS_str = "了解,声音已切换"
+                        return 0
+                if text.find("英语") != -1:
+                    if "英语" in self.VC_TTS_voice_type:
+                        self.VC_TTS_voice_key = "英语"
+                        EventMesh.publish("set_storage_config", ["VC_TTS_voice_key", self.VC_TTS_voice_key])
+                        self.AI_TTS_str = "了解,声音已切换"
+                        return 0
+                self.AI_TTS_str = "很抱歉,没找到对应声音"
+                return 0
+            ret = self._apply_vc_text_intent(desc, cmd, True)
+            if ret is not None:
+                return ret
+        except Exception as e:
+            usys.print_exception(e)
+        try:
+            self.AI_TTS_str = cmd["command"]["fail_prompt"]
+        except Exception:
+            self.AI_TTS_str = ""
+        return -1
+    def check_text(self, text):
+        print("check_text:len:{}".format(len(text)))
+        if len(text) > 60:
+            return -1
+        try:
+            if not isinstance(text, str):
+                return -1
+            ret = self._apply_vc_text_intent(text, None, False)
+            if ret is not None:
+                return ret
+        except Exception as e:
+            usys.print_exception(e)
+        return -1
+    def get_trace_id(self):
+        t = utime.mktime(utime.localtime())
+        t = t*1000+urandom.randint(0, 1000)
+        md5_data = "{}".format(t)#md5签名
+        sign_data = ""
+        hash_obj = uhashlib.md5(md5_data)
+        res = hash_obj.digest()
+
+        # 转成十六进制表示
+        sign_data = ubinascii.hexlify(res)
+        sign_data = sign_data.decode('ascii')
+        return sign_data
+    def get_session_id(self,data = "session_id"):
+        t = utime.mktime(utime.localtime())
+        t = t*1000+urandom.randint(0, 1000)
+        md5_data = "{}{}".format(data,t)#md5签名
+        sign_data = ""
+        hash_obj = uhashlib.md5(md5_data)
+        res = hash_obj.digest()
+
+        # 转成十六进制表示
+        sign_data = ubinascii.hexlify(res)
+        sign_data = sign_data.decode('ascii')
+        return sign_data
+    def get_new_message_id(self):
+        message_id = self.get_trace_id()+"aaa"+self.get_trace_id()
+        self.VC_message_id=message_id[:32]
+        return self.VC_message_id 
+    def get_new_task_id(self):
+        task_id = self.get_trace_id()+"aaa"+self.get_trace_id()
+        self.VC_task_id=task_id[:32]
+        return self.VC_task_id
+    def AI_client_VC_start_requset(self):
+        voice="zh_male_wennuanahu_moon_bigtts"
+        try:
+            voice = self.VC_TTS_voice_type[self.VC_TTS_voice_key]["voice"]
+        except Exception as e:
+            usys.print_exception(e)    
+        #voice="zh_female_dongmanxiaoyang_moon_bigtts"
+        token = EventMesh.publish("get_dev_token")
+        if token ==None:
+            token = ""
+        # bot_option:优先从 default_config(usr 可覆盖)读取 VC_BOT_ID / VC_WORKFLOW_ID,允许空字符串
+        _def_bot_id = "7462617881076351002"
+        _def_workflow_id = "7462621757327196187"
+        try:
+            if "vc_config" in globals() and vc_config:
+                _vc_bot_id = vc_config.get_attr("VC_BOT_ID", _def_bot_id)
+                _vc_workflow_id = vc_config.get_attr("VC_WORKFLOW_ID", _def_workflow_id)
+                if _vc_bot_id is None:
+                    _vc_bot_id = ""
+                if _vc_workflow_id is None:
+                    _vc_workflow_id = ""
+            else:
+                _vc_bot_id, _vc_workflow_id = _def_bot_id, _def_workflow_id
+        except Exception:
+            _vc_bot_id, _vc_workflow_id = _def_bot_id, _def_workflow_id
+        json_data = \
+        {
+            "header": {
+                "event": "Start",
+                "trace_id": self.get_trace_id(),
+            },
+            "payload": {
+                "simple_response": True,#不返回llm过程文本
+                "device_id": modem.getDevImei(),
+                
+                "user_agent": "AudioClient/1.0",
+                "dialog_attributes": {
+                    #"provider": "doubao",
+                    # "provider": "coze",
+
+                    "cfg": {
+                        "disable_empty_prompt" : True,#拦截空字符请求大模型
+                        "custom_variables": {
+                            "name": self.bot_name,
+                            "devToken":token,
+                            },
+                        "greeting":{
+                            "enabled":False,
+                            "content":""
+                        },
+                        "openai_option":{
+                           },
+
+                        "bot_option": {
+                            "use_workflow": True,
+                            #"input_format": "text",
+                            "bot_id": _vc_bot_id,
+                            "workflow_id": _vc_workflow_id,
+                           
+                        }
+                    }
+                },
+                "outbound_attributes": {
+                    "provider": "volcengine",
+                    "cfg": {
+                        "format": "pcm",
+                        "sample_rate": self.VC_play_samplerate,
+                        "bit_rate": 16,
+                        "channel": 1,
+                        "volume": self.VC_TTS_volume,
+                        "emotion": self.emotion,
+                        #"voice": "7426720361732980745",
+                        "voice": voice,#音色
+                        "speech_rate": self.speech_rate,#语速
+                        "chunk_interval_ms": 10,  # 配置每段音频入队间隔时间 100ms
+                    }
+                },
+                "inbound_attributes": {
+                    # "provider": "xun-fei",#
+                    "provider": "volcengine",
+                    "disable_vad":True,
+                    "vad_silence":self.vad_silence,
+                    "interrupt_options":{
+                        "concatenate_results":True,
+                        "thinking_time_only": True,
+                        #"keyword_interrupt": false, // 启用打断词功能 (打断词只有在思考以及之后才能打断)
+                        #"interrupt_keywords": ["停止", "打断","打断一下", "等一下", "别说了", "打住"] // 配置打断词列表
+                    },
+                    "cfg": {
+                        "format": "pcm",
+                        "sample_rate": self.VC_record_samplerate,
+                        "bit_rate": 16,
+                        "channel": 1,
+                        "enable_punctuation": True,
+                        "enable_itn":True,
+                        "keywords":[]
+                    }
+                },
+                "commands":self.commands,
+            }
+        }
+        # "bot_id":"7481164663863459850",#鸭子
+        # "workflow_id":"7480485093845991443",
+        # "bot_id":"7462617881076351002",#语言助手
+        # "workflow_id":"7462621757327196187",
+        # "bot_id":"7469964258592931852",#深圳百事通
+        # "workflow_id":"7469968083063603241",
+        # "bot_id":"7411071327182438412",#柒牌
+        # "workflow_id":"7469049680291610636",
+        # if self.online_kws == 1:#打开
+        #     json_data["payload"]["inbound_attributes"]["interrupt_options"]["keyword_interrupt"] = True
+        #     json_data["payload"]["inbound_attributes"]["interrupt_options"]["interrupt_keywords"] = ["你好,柒先生","你好"]
+        #如果
+        json_data["payload"]["outbound_attributes"]["provider"]=self.outbound_attributess_provider#TTS音色服务选择,火山
+        json_data["payload"]["inbound_attributes"]["provider"]=self.inbound_attributess_provider#ASR服务选择,火山
+        #优先选择self.VC_TTS_voice_type[self.VC_TTS_voice_key]里得参数
+        # if "inbound_attributess_provider" in self.VC_TTS_voice_type[self.VC_TTS_voice_key]:
+        #     if len(self.VC_TTS_voice_type[self.VC_TTS_voice_key]["inbound_attributess_provider"])>0:
+        #         json_data["payload"]["inbound_attributes"]["provider"]=self.VC_TTS_voice_type[self.VC_TTS_voice_key]["inbound_attributess_provider"]
+        #         print("inbound_attributess_provider是VC_TTS_voice_type里的:{}".format(self.VC_TTS_voice_type[self.VC_TTS_voice_key]["inbound_attributess_provider"]))
+        if "outbound_attributess_provider" in self.VC_TTS_voice_type[self.VC_TTS_voice_key]:
+            if len(self.VC_TTS_voice_type[self.VC_TTS_voice_key]["outbound_attributess_provider"])>0:
+                json_data["payload"]["outbound_attributes"]["provider"]=self.VC_TTS_voice_type[self.VC_TTS_voice_key]["outbound_attributess_provider"]
+                print("outbound_attributess_provider是VC_TTS_voice_type里的:{}".format(self.VC_TTS_voice_type[self.VC_TTS_voice_key]["outbound_attributess_provider"]))
+
+        # 根据配置设置默认 bot/workflow,并允许 VC_TTS_voice_type 覆盖
+        try:
+            if "vc_config" in globals() and vc_config:
+                opt = vc_config.get_attr("DIALOG_BOT_OPTION", None)
+                if isinstance(opt, dict):
+                    if "bot_id" in opt:
+                        json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]["bot_id"] = opt.get("bot_id") or ""
+                    if "workflow_id" in opt:
+                        json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]["workflow_id"] = opt.get("workflow_id") or ""
+                    if "use_workflow" in opt:
+                        json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]["use_workflow"] = bool(opt.get("use_workflow"))
+        except Exception:
+            pass
+
+        try:
+            # voice 配置可覆盖 bot_id/workflow_id/openai_option
+            cur = self.VC_TTS_voice_type[self.VC_TTS_voice_key]
+            if isinstance(cur, dict):
+                if cur.get("bot_id"):
+                    json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]["bot_id"] = cur.get("bot_id")
+                if "workflow_id" in cur:
+                    json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]["workflow_id"] = cur.get("workflow_id") or ""
+                if cur.get("openai_option"):
+                    json_data["payload"]["dialog_attributes"]["cfg"]["openai_option"] = cur.get("openai_option")
+        except Exception:
+            pass
+
+        try:
+            _bo = json_data["payload"]["dialog_attributes"]["cfg"]["bot_option"]
+            if not _bo.get("bot_id", "") or not _bo.get("workflow_id", ""):
+                _bo["use_workflow"] = False
+        except Exception:
+            pass
+        try:
+            if "vc_config" in globals() and vc_config:
+                p = vc_config.get_attr("DIALOG_PROVIDER_DEFAULT", None)
+                if p:
+                    json_data["payload"]["dialog_attributes"]["provider"] = p
+        except Exception:
+            pass
+
+        try:
+            if "vc_config" in globals() and vc_config and vc_config.get_bool("KEYWORDS_FROM_VOICE_TYPE", False):
+                keywords = []
+                for key in self.VC_TTS_voice_type:
+                    keywords.append(key)
+                json_data["payload"]["inbound_attributes"]["cfg"]["keywords"] = keywords
+        except Exception:
+            pass
+        if self.dialog_attributes_provider != None:
+            json_data["payload"]["dialog_attributes"]["provider"] = self.dialog_attributes_provider
+        if self.commands_mode !=0 :
+            json_data["payload"]["commands"] = []
+        if self.VC_mode == 0 :#and dev_type != "7PIN_BK_coze":#长对话模式打开vad
+            json_data["payload"]["inbound_attributes"]["disable_vad"] = False
+        # 新会话开始:在收到服务端 started 前,禁止发送音频帧(避免 session not initialized)
+        self._vc_session_started = 0
+        try:
+            self.fsm.on_session_start_request()
+        except Exception:
+            pass
+        self.AI_client_VC_send(ujson.dumps(json_data))
+        try:
+            _tid = json_data.get("header", {}).get("trace_id", "")
+            print("[VC][proto] Start enqueued trace_id={} VC_mode={} disable_vad={} ws_conn={}".format(
+                _tid or "-",
+                self.VC_mode,
+                json_data.get("payload", {}).get("inbound_attributes", {}).get("disable_vad", "?"),
+                self.AI_client_VC_isconnect))
+        except Exception:
+            pass
+    def AI_client_VC_RequestToSpeak(self):
+        json_data = \
+        {
+            "event": "cancel",
+            "payload": {}
+        }
+        print("AI_client_VC_RequestToSpeak!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+        # 模式切换等场景 play_tts(..., is_close_VC=1) 会置 Close_flag=1;用户再按 AI 键走 cancel→canceled→
+        # uplink_start 时,FSM 与 _uplink_request_start 均会因 Close_flag 拒绝开麦,表现为“切回长对话后按键无上行”。
+        try:
+            if self.VC_mode == 0:
+                self.Close_flag = 0
+                self._vc_pending_interrupt_uplink = 1
+                self._vc_clear_stale_record_state("RequestToSpeak")
+        except Exception:
+            pass
+        # 只做“打断+清播放队列”,不要在这里关音频队列线程(会导致后续 TTS/提示音不稳定,且触发竞态)
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_init})
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_send,"data":ujson.dumps(json_data)})
+        utime.sleep_ms(1)
+        try:
+            self.fsm.reset_rx_phases()
+        except Exception:
+            pass
+    def AI_client_new_voice(self): #上传未读的语音地址/包括音频地址也可以
+        json_data = \
+        {
+            "event": "audio.convert",
+            "payload": {
+                "audio_convert_request": {
+                    "audio_url": self.AI_new_voice_url,
+                    "bit_depth": 16,
+                    "channels": 1,
+                    "sample_rate": 16000,
+                    "target_format": "pcm"
+                }
+            }
+        }
+        self.AI_new_voice_url = ""
+        print("AI_client_new_voice!!!!!!!!!!!!!!!!!!!!!!!!!!!!!{}".format(json_data))
+        self.AI_client_VC_send(ujson.dumps(json_data))
+    def send_tone(self):
+        EventMesh.publish("audio_montage_play",["send_tone.mp3"])
diff --git a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_session_media.py b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_session_media.py
new file mode 100644 (file)
index 0000000..b5d2038
--- /dev/null
@@ -0,0 +1,1695 @@
+# -*- coding: utf-8 -*-
+"""AI_VC mixin:ASR 上行/录音、TTS 下行、播放队列与本地采播。"""
+from vc_session_imports import *  # noqa: F401,F403
+import _thread
+
+class VCSessionMediaMixin:
+    def get_vc_tts_playback_active(self, *args, **kwargs):
+        try:
+            return 1 if self._vc_tts_playback_active() else 0
+        except Exception:
+            return 0
+    def get_vc_stream_active(self, *args, **kwargs):
+        # 供本地提示音侧查询:TTS 流播放期间本地不可抢占。
+        try:
+            if self._vc_tts_playback_active():
+                return 1
+        except Exception:
+            pass
+        try:
+            if getattr(self.fsm, "await_played", False):
+                return 1
+        except Exception:
+            pass
+        try:
+            if getattr(self.fsm, "tts_rx_phase", VC_TtsRxPhase.Idle) != VC_TtsRxPhase.Idle:
+                return 1
+        except Exception:
+            pass
+        try:
+            t = getattr(self.fsm, "turn", VCTurnState.Idle)
+            if t in (VCTurnState.TtsReceiving, VCTurnState.Playing, VCTurnState.AwaitPlayedAck):
+                return 1
+        except Exception:
+            pass
+        try:
+            if getattr(self, "_tts_pending", 0) == 1:
+                return 1
+        except Exception:
+            pass
+        return 0
+    def _vc_tts_pipeline_busy(self):
+        try:
+            if getattr(self.fsm, "await_played", False):
+                return True
+            if getattr(self.fsm, "tts_rx_phase", VC_TtsRxPhase.Idle) != VC_TtsRxPhase.Idle:
+                return True
+            t = getattr(self.fsm, "turn", VCTurnState.Idle)
+            if t in (VCTurnState.TtsReceiving, VCTurnState.Playing, VCTurnState.AwaitPlayedAck):
+                return True
+            if getattr(self, "_tts_pending", 0) == 1:
+                return True
+            if getattr(self, "VC_Play_flag", 0) == 1:
+                return True
+            if self._vc_local_play_busy():
+                return True
+        except Exception:
+            pass
+        return False
+    def _vc_local_play_busy(self):
+        try:
+            if getattr(self, "_vc_local_play_active", 0) != 1:
+                return False
+            until = getattr(self, "_vc_local_play_until_ms", 0)
+            if until and utime.ticks_diff(until, utime.ticks_ms()) > 0:
+                return True
+            self._vc_local_play_active = 0
+            self._vc_local_play_until_ms = 0
+        except Exception:
+            pass
+        return False
+    def _vc_local_play(self, path, play_mode=1, loop=1, hold_ms=650):
+        """本地 MP3 短音:播前停录/停流式 TTS,避免与麦克风采集叠用。"""
+        try:
+            if getattr(self, "record_data_save_flag", 0) == 0 and getattr(self, "VC_record_state_flag", 0) != 0:
+                self._uplink_request_stop("local_play")
+                utime.sleep_ms(30)
+        except Exception:
+            pass
+        try:
+            if self._vc_tts_playback_active():
+                self._ptt_hard_mute_local_playback()
+                try:
+                    self.VC_Play_End()
+                except Exception:
+                    pass
+        except Exception:
+            pass
+        try:
+            hold = int(hold_ms)
+            if hold < 200:
+                hold = 200
+            self._vc_local_play_active = 1
+            self._vc_local_play_until_ms = utime.ticks_add(utime.ticks_ms(), hold)
+        except Exception:
+            pass
+        try:
+            self.aud.play(play_mode, loop, path)
+        except Exception as e:
+            try:
+                self._vc_local_play_active = 0
+                self._vc_local_play_until_ms = 0
+            except Exception:
+                pass
+            usys.print_exception(e)
+    def _vc_tts_playback_active(self):
+        """仅服务端 TTS/PCM 流在播(VC_Play_Start);aud.play 本地短音不算。"""
+        try:
+            if getattr(self, "VC_Play_flag", 0) == 1:
+                return True
+        except Exception:
+            pass
+        try:
+            if getattr(self, "play_pcm", None) is not None:
+                return True
+        except Exception:
+            pass
+        return False
+    def _vc_vad_uplink_stop_eligible(self):
+        # 对应旧 VC_state >= SpeechStart
+        try:
+            if getattr(self.fsm, "speech_uplink_open", 0):
+                return True
+            t = getattr(self.fsm, "turn", VCTurnState.Idle)
+            return t not in (VCTurnState.Idle, VCTurnState.Listening)
+        except Exception:
+            return False
+    def _vc_clear_stale_record_state(self, reason=""):
+        """record_state_flag 非 0 但可能无真实录音时先停录复位,避免 uplink 门控拒开麦。"""
+        try:
+            if getattr(self, "VC_record_state_flag", 0) != 0:
+                self.VC_Speech_End()
+        except Exception:
+            try:
+                self.VC_record_state_flag = 0
+            except Exception:
+                pass
+    def _vc_effective_asr_text(self):
+        try:
+            t = (getattr(self, "AI_ASR_str", "") or "").strip()
+            if t:
+                return t
+            return (getattr(self, "_last_asr_result_text", "") or "").strip()
+        except Exception:
+            return ""
+    def _vc_long_dialogue_auto_uplink_ok(self, reason):
+        """长对话 canceled/completed 自动开麦:须有有效 ASR,且防抖/勿在 LLM 无 TTS 后抢开。"""
+        try:
+            if reason not in ("canceled", "completed", "local.play_end", "audio.played"):
+                return True
+            if getattr(self, "Close_flag", 0) == 1:
+                return False
+            if reason == "canceled" and getattr(self, "_vc_pending_interrupt_uplink", 0) == 1:
+                pass
+            elif not self._vc_effective_asr_text():
+                k = getattr(self, "_last_tts_kind", "") or ""
+                if reason == "local.play_end" and k == "boot_prompt":
+                    pass
+                else:
+                    try:
+                        print("[VC][uplink_ctrl] Deny START by {} cause=empty_asr".format(reason))
+                    except Exception:
+                        pass
+                    return False
+            if reason == "completed":
+                if getattr(self, "_turn_has_tts_started", 0) == 0:
+                    try:
+                        if getattr(self, "_llm_completed_ms", 0) or getattr(self.fsm, "turn", None) == VCTurnState.Thinking:
+                            print("[VC][uplink_ctrl] Deny START by completed cause=llm_without_tts")
+                            return False
+                    except Exception:
+                        pass
+            try:
+                now = utime.ticks_ms()
+                last = getattr(self, "_vc_last_auto_uplink_ms", 0)
+                if last and utime.ticks_diff(now, last) < 1200:
+                    print("[VC][uplink_ctrl] Deny START by {} cause=auto_uplink_debounce".format(reason))
+                    return False
+                self._vc_last_auto_uplink_ms = now
+            except Exception:
+                pass
+        except Exception:
+            pass
+        return True
+    def _uplink_request_start(self, reason="", *args, **kwargs):
+        # 统一入口:避免多处同时 start 造成冲突(尤其是 TTS/播放阶段误开麦导致回声触发与状态紊乱)
+        try:
+            if self.Close_flag == 1:
+                try:
+                    if reason in ("canceled", "local.play_end", "audio.played", "key.press/continue"):
+                        print("[VC][uplink_ctrl] Deny START by {} cause=Close_flag".format(reason))
+                except Exception:
+                    pass
+                return
+        except Exception:
+            return
+
+        # 连续对话才允许“自动开新一轮上行”
+        try:
+            if self.VC_mode != 0:
+                try:
+                    if reason in ("canceled", "local.play_end", "audio.played"):
+                        print("[VC][uplink_ctrl] Deny START by {} cause=VC_mode({})".format(reason, self.VC_mode))
+                except Exception:
+                    pass
+                return
+        except Exception:
+            return
+
+        try:
+            if self.VC_mode == 0 and not self._vc_long_dialogue_auto_uplink_ok(reason):
+                return
+        except Exception:
+            pass
+
+        try:
+            if not self._vc_capture_allowed(reason):
+                try:
+                    print("[VC][uplink_ctrl] Deny START by {} cause=playback_busy".format(reason))
+                except Exception:
+                    pass
+                return
+        except Exception:
+            return
+
+        # ===== 业务门控(收敛到 FSM turn/session)=====
+        try:
+            if getattr(self.fsm, "session", VCSessionState.Disconnected) != VCSessionState.Started:
+                try:
+                    if reason in ("canceled", "local.play_end", "audio.played"):
+                        print("[VC][uplink_ctrl] Deny START by {} cause=fsm.session".format(reason))
+                except Exception:
+                    pass
+                return
+            turn = getattr(self.fsm, "turn", VCTurnState.Idle)
+            # 仅 Listening/SpeechEnded 允许开麦进入下一轮;其它阶段一律拒绝(避免抢跑打乱链路)
+            if turn not in (VCTurnState.Listening, VCTurnState.SpeechEnded):
+                try:
+                    if reason in ("canceled", "local.play_end", "audio.played"):
+                        print("[VC][uplink_ctrl] Deny START by {} cause=fsm.turn({})".format(reason, turn))
+                except Exception:
+                    pass
+                return
+        except Exception:
+            return
+
+        # 若当前不在录音上行,才允许开新一轮
+        if getattr(self, "VC_record_state_flag", 0) != 0:
+            # canceled 打断后:可能是僵死 flag=2(started 未真开麦),先停录复位再开麦
+            if reason == "canceled":
+                try:
+                    self._vc_clear_stale_record_state("uplink_start/canceled")
+                except Exception:
+                    pass
+            if getattr(self, "VC_record_state_flag", 0) != 0:
+                try:
+                    if reason in ("canceled", "local.play_end", "audio.played"):
+                        print("[VC][uplink_ctrl] Deny START by {} cause=VC_record_state_flag({})".format(
+                            reason, getattr(self, "VC_record_state_flag", 0)))
+                except Exception:
+                    pass
+                return
+
+        # 新一轮开始录音:允许上行(上一轮可能已置 silence 闸门)
+        if getattr(self, "record_data_save_flag", 0) == 0:
+            self._server_vad_silence = 0
+
+        print("[VC][uplink_ctrl] Request START by {}".format(reason))
+        try:
+            if reason == "canceled" and getattr(self, "_vc_pending_interrupt_uplink", 0) == 1:
+                self._vc_pending_interrupt_uplink = 0
+                print("[VC][uplink_ctrl] interrupt_uplink armed -> start (canceled)")
+        except Exception:
+            pass
+        self.VC_Speech_Start_now()
+    def _uplink_request_stop(self, reason="", *args, **kwargs):
+        # 统一入口:避免多处同时 stop 造成冲突
+        if getattr(self, "record_data_save_flag", 0) == 0:
+            self._server_vad_silence = 1
+
+        if getattr(self, "VC_record_state_flag", 0) == 0:
+            try:
+                if getattr(self, "VC_mode", 0) != 1:
+                    print("[VC][long_idle] _uplink_request_stop skip (record_state==0) reason={}".format(reason))
+            except Exception:
+                pass
+            return
+
+        try:
+            if getattr(self, "VC_mode", 0) != 1:
+                print("[VC][long_idle] step=uplink_stop_exec reason={} VC_record_state_flag={}".format(
+                    reason, getattr(self, "VC_record_state_flag", -1)))
+        except Exception:
+            pass
+        print("[VC][uplink_ctrl] Request STOP by {}".format(reason))
+        self.VC_Speech_End()
+    def _uplink_mark_start(self, first_len=0):
+        try:
+            if self._uplink_active == 1:
+                return
+            self._uplink_active = 1
+            self._uplink_bytes = 0
+            self._uplink_start_ms = utime.ticks_ms()
+            print("[VC][uplink] START len={} {} VC_record_state_flag={}".format(
+                first_len, self._vc_fsm_log_snip(), self.VC_record_state_flag))
+        except Exception:
+            pass
+    def _uplink_mark_end(self, reason=""):
+        try:
+            if self._uplink_active != 1:
+                return
+            dur = utime.ticks_diff(utime.ticks_ms(), self._uplink_start_ms) if self._uplink_start_ms else -1
+            print("[VC][uplink] END reason={} bytes={} dur_ms={} {} VC_record_state_flag={}".format(
+                reason, self._uplink_bytes, dur, self._vc_fsm_log_snip(), self.VC_record_state_flag))
+        except Exception:
+            pass
+        self._uplink_active = 0
+        self._uplink_bytes = 0
+        self._uplink_start_ms = 0
+
+        # 最近一次 TTS 的语义来源(dialog/prompt/mode_switch_long/mode_switch_ptt/...)
+        self._last_tts_kind = ""
+        # 下一个“无 dialog_id 的提示音 TTS”在 tts.started 时采用该 kind
+        self._pending_tts_kind = ""
+    def request_uplink_start(self, reason=""):
+        EventMesh.publish("publish_AI_btnInfo", {"func": self._uplink_request_start, "data": reason})
+    def request_uplink_stop(self, reason=""):
+        EventMesh.publish("publish_AI_btnInfo", {"func": self._uplink_request_stop, "data": reason})
+    def set_VC_stop_play_audio_flag(self,event = None, msg = None):
+        self.VC_stop_play_audio_flag = msg
+        print("self.VC_stop_play_audio_flag:{}".format(self.VC_stop_play_audio_flag))
+    def rate_change(self,event,msg):#直接设置语速(数字)
+        self.speech_rate = msg
+        EventMesh.publish("set_storage_config",["speech_rate",self.speech_rate])
+        print("set_speech_rate:{}".format(self.speech_rate))
+    def rate_move(self,event,msg):#设置语速(模糊(快/慢一点))
+        if msg == 1 and self.speech_rate<50:#增大
+            self.speech_rate += 10
+        else:#减小
+            if self.speech_rate >-50:
+                self.speech_rate -= 10
+        EventMesh.publish("set_storage_config",["speech_rate",self.speech_rate])
+        print("set_speech_rate:{}".format(self.speech_rate))
+    def find_rate(self,event,msg):#查询APP语速
+        print("查询语速处理:原始数据为:{}".format(self.speech_rate))
+        y = 1
+        if self.speech_rate >= -50 and self.speech_rate < 0:#负数
+            y = 1 - (-0.01 * self.speech_rate) 
+        elif self.speech_rate <= 50 and self.speech_rate >= 0:#正数
+            y = 0.02 * self.speech_rate + 1.0
+        print("处理后数据为{}".format(y))
+        rate = str(y) + "倍"
+        return rate
+    def set_record_data_save_flag(self,event = None, msg = None):
+        self.record_data_save_flag = msg
+    def get_AI_audio_queue(self, event, msg):
+        return self.AI_audio_queue
+    def audio_cb(self,event):
+        if event == 0:
+            EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
+            self.audio_play_state =1
+            #EventMesh.publish("set_audio_state", 1)
+            print('audio-play start.')
+            # 本地 aud.play(开录/思考短音等)不改动 AI 灯,仅 TTS 流由 VC_Play_Start 控绿灯
+            if self._vc_led_state_debug_enabled():
+                print("[LED] audio_cb start skip (local_play)")
+            return
+        elif event == 7:
+            self.audio_play_state = 0
+            #EventMesh.publish("set_audio_state", 0)   
+            print('audio-play finish.!!!!!!')
+            try:
+                if getattr(self, "VC_mode", -1) == 1 and getattr(self, "_ptt_await_server", 0) == 1:
+                    self._vc_publish_led("thinking", "audio_cb/finish/ptt_await")
+                    return
+            except Exception:
+                pass
+            try:
+                if getattr(self, "VC_record_state_flag", 0) != 0:
+                    self._vc_publish_led("listening", "audio_cb/finish/recording")
+                    return
+            except Exception:
+                pass
+            try:
+                if self._vc_tts_playback_active():
+                    if self._vc_led_state_debug_enabled():
+                        print("[LED] audio_cb finish skip (tts_playback)")
+                    return
+            except Exception:
+                pass
+            try:
+                if getattr(self, "_ptt_led_lock_listening", 0) == 1:
+                    return
+            except Exception:
+                pass
+            self._vc_led_sync_from_fsm()
+    def set_tts_volume(self,event,msg):
+        EventMesh.publish("set_storage_config",["VC_TTS_volume",msg])#音量配置
+        self.VC_TTS_volume = msg
+        print("set_tts_volume:{} {}".format(self.VC_TTS_volume,msg))
+        if int(msg) <= 10:  #最小一定要设置1要不然没有声音
+            volume = 10 //10
+            self.aud.setVolume(volume)
+        else:
+            self.aud.setVolume(self.VC_TTS_volume //10)
+        pass
+    def get_audio_play_state(self, event=None, msg = None):
+        return self.audio_play_state
+    def set_VC_record_state_flag(self,data):
+        
+        self.VC_record_state_flag = data
+        print("[VC] record_state_flag={}".format(self.VC_record_state_flag))
+    def VC_Speech_Start_now(self):
+        # 新一轮开始录音:允许上行(上一轮可能已置 silence 闸门)
+        self._server_vad_silence = 0
+        self.VC_Speech_Start()
+        if self.VC_record_state_flag  == 1:
+            self.VC_record_state_flag =2
+        print("[VC] Speech_Start_now record_state={}".format(self.VC_record_state_flag))
+    def VC_Speech_Start(self):
+        try:
+            
+            #self.VC_state =  VC_State_SpeechStart
+            print("[VC] Speech_Start record_state={} AI_ASR_flag={}".format(self.VC_record_state_flag,self.AI_ASR_flag))
+            if self.AI_ASR_flag  != 0:
+                return
+            
+            if self.VC_record_state_flag  == 0:
+                # 新一轮开始录音:允许上行(上一轮可能已置 silence 闸门)
+                self._server_vad_silence = 0
+                self.AI_ASR_str = ""
+                self.AI_ASR_str_tmp = ""
+                self._last_asr_result_text = ""
+                try:
+                    self._asr_completed_tone_done = 0
+                except Exception:
+                    pass
+                
+                self.VC_record_thread_close()#关闭录音线程
+                _thread.stack_size(self.Audio_buf_size+8*1024)
+                self.VC_record_thread_id = _thread.start_new_thread(self.VC_record_thread,())
+                _thread.stack_size(8*1024)
+                print("self.VC_record_thread_id:{}".format(self.VC_record_thread_id))
+                try:
+                    
+                    if self.VC_record_TYPE == VC_record_TYPE_AMR :
+                        try :
+                            self.record.stream_start(audio.Record.AMRNB, self.VC_record_samplerate, 0)
+                        except Exception as e:
+                                usys.print_exception(e)
+                    elif self.VC_record_TYPE == VC_record_TYPE_PCM:
+                        print("record_pcm.stream_read_start()!!!!!!!!!!!!")
+                        self.record.stream_start(audio.Record.WAV, self.VC_record_samplerate, 0)
+                        # if self.record_pcm != None:
+                        #     try:
+                        #         self.record_pcm.stream_stop()
+                        #         self.record_pcm.close()
+                        #         self.record_pcm = None
+                        #     except Exception as e:
+                        #         usys.print_exception(e)
+                        # if self.record_pcm == None:        
+                        #     ret = self.record_pcm = audio.Audio.PCM(0,1,16000,0,1,2)
+                        #     print("audio.Audio.PCM:ret:{}".format(ret))
+                        #     ret = self.record_pcm.stream_read_start()
+                        #     print("self.record_pcm.stream_read_start:ret:{}".format(ret))
+                except Exception as e:
+                    usys.print_exception(e) 
+                
+                self.VC_record_state_flag = 1
+                # 录音硬件已起:协议侧应对应 audio.create(PTT)或长对话直传 PCM
+                try:
+                    print("[VC][proto] record_start sr={} pcm_type={} VC_mode={} session_started={} "
+                          "ptt_audio_created={} thread_id={}".format(
+                              self.VC_record_samplerate, self.VC_record_TYPE, self.VC_mode,
+                              getattr(self, "_vc_session_started", -1),
+                              getattr(self, "_ptt_audio_created", -1),
+                              self.VC_record_thread_id))
+                except Exception:
+                    pass
+                # 录音开始:聆听灯(PTT 蓝快闪 / 长对话蓝慢闪)
+                try:
+                    self._vc_publish_led("listening", "VC_Speech_Start")
+                except Exception:
+                    pass
+                try:
+                    if getattr(self, "VC_mode", -1) == 1:
+                        self._ptt_on_record_hardware_started()
+                except Exception:
+                    pass
+                
+        except Exception as e:
+            self.websocket_error()
+            self.VC_record_state_flag = 1
+            self.VC_Speech_End()#关闭录音
+            usys.print_exception(e)   
+    def VC_Speech_End(self):
+        try:
+            self._uplink_mark_end("VC_Speech_End")
+            self.AI_ASR_flag = 0
+            self.AI_ASR_str_tmp = ""
+            # 勿在此清空 AI_ASR_str:vad.silence 停麦常早于 asr.completed,清空会导致 check_text:len:0 连播 think 音
+            #EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_asr})
+              
+            if self.VC_record_state_flag == 0:
+                return
+            #self.AI_client_asr_StopTranscription_requset()#请求停止
+            if self.VC_record_TYPE == VC_record_TYPE_AMR:
+                self.record.stream_stop()
+                self.record.stop()#关闭录音
+                Record_read_buf = bytearray(self.Audio_buf_size)
+                for i in range(10):
+                    Record_len = self.record.stream_read(Record_read_buf, self.Audio_buf_size)
+                    # if VC_AUDIO_FRAME_LOG:
+                    #     print("Record_len:!!!!!!{}".format(Record_len))
+                    if Record_len <= 0:
+                        break
+            elif self.VC_record_TYPE == VC_record_TYPE_PCM:
+                self.record.stream_stop()
+                self.record.stop()#关闭录音
+                Record_read_buf = bytearray(self.Audio_buf_size)
+                for i in range(10):
+                    Record_len = self.record.stream_read(Record_read_buf, self.Audio_buf_size)
+                    # if VC_AUDIO_FRAME_LOG:
+                    #     print("Record_len:!!!!!!{}".format(Record_len))
+                    if Record_len <= 0:
+                        break
+                # if self.record_pcm != None:
+                #     try:
+                #         self.record_pcm.stream_stop()
+                #         self.record_pcm.close()
+                #         self.record_pcm = None
+                #     except Exception as e:
+                #         usys.print_exception(e)
+        except Exception as e:
+            usys.print_exception(e)
+        self.VC_record_thread_close()#关闭录音线程
+        if self.AI_record_tmp_queue.empty() != True:
+            self.AI_record_tmp_queue.clear()
+        
+        self.VC_record_state_flag = 0
+        # PTT 一轮录音结束:清长按语义,避免后续短按松手误走 end_speech 而不播提示音
+        try:
+            if getattr(self, "VC_mode", -1) == 1:
+                self.AI_key_long_press_flag = 0
+        except Exception:
+            pass
+        # 录音停止:单对话进入思考态(等服务器),勿继续蓝闪聆听
+        try:
+            if getattr(self, "VC_mode", -1) == 1:
+                self._ptt_await_server = 1
+                self._vc_publish_led("thinking", "VC_Speech_End/ptt")
+            else:
+                self._ptt_await_server = 0
+                self._vc_publish_led("idle", "VC_Speech_End")
+        except Exception:
+            pass
+        print("[VC] Speech_End record_state={}".format(self.VC_record_state_flag))
+    def VC_record_thread_close(self): 
+        if self.VC_record_thread_id != 0:
+            count =50
+            while True:
+                count -= 1
+                if self.VC_record_thread_lock == 1:
+                    self.VC_record_thread_lock = 2
+                if self.VC_record_thread_lock == 0 or count <=0:
+                    break
+                else:
+                    print("VC_record_thread_thread going:{}...".format(count))
+                    utime.sleep_ms(1)
+            self.VC_record_thread_lock = 0
+            print("VC_record_thread_thread_close")
+            if self.VC_record_thread_id != 0:
+                try:
+                    _thread.stop_thread(self.VC_record_thread_id)
+                except Exception as e:
+                    usys.print_exception(e)
+        self.VC_record_thread_id = 0
+    def VC_record_thread(self):
+        ##return
+        print("[VC] record_thread start")
+        count = 0
+        RequestToSpeak_flag = 0
+        # try:
+        #     uos.remove('/usr/test.pcm')
+        # except Exception as e:
+        #     usys.print_exception(e)
+        #record_data_queue = Queue(100)
+        #fd = open('/usr/test.pcm','w')
+        # if self.VC_record_TYPE == 1:
+        #     h_data= [0x23,0x21 ,0x41 ,0x4D ,0x52 ,0x0A ]
+        #     h_data_b = bytes(h_data)
+        #     self.AI_client_VC_send(h_data_b)
+        AI_record_tmp_queue_len = 0
+        vad_buf = bytearray()
+        VAD_data_count = 0
+        vad_state = 0
+        self.VAD_count = 0
+        while True:
+            try:
+                if self.VC_record_thread_lock == 2:
+                    break
+                self.VC_record_thread_lock = 1
+                # 死循环接收数据
+                #if self.VC_state ==  VC_State_SpeechStart:
+                Record_read_buf = bytearray(self.Audio_buf_size)
+                
+                Record_len = 0
+                if self.VC_record_TYPE == VC_record_TYPE_PCM:#pcm
+                    Record_len = self.record.stream_read(Record_read_buf, self.Audio_buf_size)
+                    #utime.sleep_ms(60)
+                    
+                else :
+                    
+                    Record_len = self.record.stream_read(Record_read_buf, self.Audio_buf_size)
+                    utime.sleep_ms(600)
+
+                #Record_len = 1024*2
+                #print("VC_record_thread:{}".format(Record_len))
+                if Record_len > 0:
+                    read_buf = bytes(Record_read_buf[:Record_len])
+                    # if VC_AUDIO_FRAME_LOG:
+                    #     print("Record_len:{}".format(Record_len))
+                    if self.record_data_save_flag == 1:
+                        if self.record_fd == None:
+                            try:
+                                uos.remove('/usr/record_data.pcm')
+                            except Exception as e:
+                                pass
+                            # 二进制写入,避免文本模式导致数据异常
+                            self.record_fd = open('/usr/record_data.pcm', 'wb')
+                            self.record_fd_len = 0
+                        if self.record_fd != None:
+                            try:
+                                self.record_fd.write(read_buf)
+                                print("write record:{}".format(len(read_buf)))
+                                self.record_fd_len = self.record_fd_len+len(read_buf)
+                                if self.record_fd_len >=500*1024:
+                                    EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) 
+                            except Exception as e:
+                                usys.print_exception(e)
+                                EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) 
+                            # count+=Record_len
+                            # if count >150*1024:
+                            #     self.record_fd.close()
+                                #self.record_fd = None
+                    #VAD检测
+                    if self.is_VAD == 1:
+                        vad_state = 0
+                        tmp_c = 1000
+                        while True:
+                            tmp_c -=1
+                            if tmp_c <=0:
+                                break
+                            #vad_buf_len = len(vad_buf)
+                            if VAD_data_count == 0:#没数据的时候
+                                vad_buf = Record_read_buf[:self.VAD_data_len]
+                                VAD_data_count = self.VAD_data_len
+                                self.vad.custom_vad_reset()#重置vad
+                            else:#有数据的时候
+                                if Record_len - VAD_data_count < self.VAD_data_len :#剩余长度不够一次填充,先保留,但不填算法
+                                    vad_buf = Record_read_buf[VAD_data_count:Record_len]
+                                    VAD_data_count = 0
+                                    break
+                                else:#剩余长度够填一次数据
+                                    vad_buf = Record_read_buf[VAD_data_count:VAD_data_count+self.VAD_data_len]
+                                    VAD_data_count += len(vad_buf)
+                            #print("vad_buf_len:{},{}".format(len(vad_buf),VAD_data_count))
+                            
+                            if len(vad_buf) >= self.VAD_data_len:
+                                vad_state = self.vad.custom_vad_process(vad_buf,len(vad_buf))#给vad数据
+                                #print("vad_state:{}".format(vad_state))
+                                
+                            if VAD_data_count >= Record_len:
+                                VAD_data_count = 0
+                                break
+                            if vad_state == 1:
+                                print("检测到VAD=1!!!!!!!!!!!!!")
+   
+                                # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_RequestToSpeak})#打断
+                                # RequestToSpeak_flag = 1
+                                
+                                self.VAD_count+=1
+                                if self.VAD_count >= 20: 
+                                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_VC_RequestToSpeak})#打断
+                                        
+                            else:
+                                if self.VAD_count != 0:
+                                    EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Play_Pause,"data":0})#恢复音频播放
+                                self.VAD_count  = 0
+                    if self._vc_tts_pipeline_busy():
+                        pass
+                        if vad_state == 1:
+                            self.AI_record_tmp_queue.put(read_buf)#入缓存队列
+                            AI_record_tmp_queue_len += len(read_buf)
+                            
+                       
+                    else:
+                        VAD_data_count = 0
+                        if self.VC_record_state_flag <=1 :#没发送前先存起来
+                            self.AI_record_tmp_queue.put(read_buf)
+                            AI_record_tmp_queue_len += len(read_buf)
+                        else:
+                            AI_record_tmp_queue_len = 0
+                            while True:
+                                if self.AI_record_tmp_queue.empty() == True:
+                                    break
+                                else:
+                                    pass
+                                    # 协议门控:会话未 started 前禁止上行音频(否则 session not initialized)
+                                    if VC_PROTO_GUARDS and self._vc_session_started != 1 and self.record_data_save_flag == 0:
+                                        _ = self.AI_record_tmp_queue.get()
+                                        AI_record_tmp_queue_len = max(0, AI_record_tmp_queue_len - len(_))
+                                        continue
+                                    if not (self._server_vad_silence == 1 and self.record_data_save_flag == 0):
+                                        self.AI_client_VC_send(self.AI_record_tmp_queue.get())  #发送之前存的数据
+                                    else:
+                                        self._uplink_mark_end("silence_gate(drop_queue)")
+                                        _ = self.AI_record_tmp_queue.get()
+                            # 当前帧发送前协议门控(可用 VC_PROTO_GUARDS 临时关闭):
+                            # - session started 前禁止发帧
+                            # - Push-To-Talk 在 audio.create 前禁止发帧
+                            if VC_PROTO_GUARDS and (
+                                (self._vc_session_started != 1 and self.record_data_save_flag == 0) or
+                                (self.VC_mode == 1 and self._ptt_audio_created != 1 and self.record_data_save_flag == 0)
+                            ):
+                                # 未 ready:转入缓存,等待 started 再发(并做容量控制)
+                                try:
+                                    self.AI_record_tmp_queue.put(read_buf)
+                                    AI_record_tmp_queue_len += len(read_buf)
+                                    while AI_record_tmp_queue_len > 128*1024 and self.AI_record_tmp_queue.empty() != True:
+                                        d = self.AI_record_tmp_queue.get()
+                                        AI_record_tmp_queue_len = max(0, AI_record_tmp_queue_len - len(d))
+                                except Exception:
+                                    pass
+                            else:
+                                if not (self._server_vad_silence == 1 and self.record_data_save_flag == 0):
+                                    self.AI_client_VC_send(read_buf)
+                                else:
+                                    self._uplink_mark_end("silence_gate(drop_buf)")
+                    
+                    
+
+                    
+                    
+
+                    #print("VC_record_thread:{}".format(len(read_buf)))
+                    #count+=len(read_buf)
+                # if count>=500*1024:#结束
+                #     self.AI_client_VC_end_speech()
+                #     break
+                # count+=1
+                # if count %10 == 0:
+                #     print("VC_record_thread on line {} asr recv :{} send:{}".format(self.VC_record_state_flag,_thread.threadIsRunning(self.AI_client_asr_recv_id),_thread.threadIsRunning(self.AI_VC_send_data_queue_thread_id)))
+                
+                # if self.VC_state >=  VC_State_SpeechEnd:
+                #     break
+                
+            except Exception as e:
+                    usys.print_exception(e)
+                    #break
+            self.VC_record_thread_lock = 0
+            if self.record_data_save_flag == 1:
+                utime.sleep_ms(1)
+            else:
+                utime.sleep_ms(100)
+        #fd.close()
+        print("VC_record_thread:exit")
+        self.VC_record_thread_id = 0#线程退出
+        self.VC_record_thread_lock = 0
+    def voice_play_10S(self,event,msg):#按键开启留言播放等待10S定时器判断
+        self.voice_timer.start(1000,1,self.voice_judge)
+    def voice_judge(self, *args, **kwargs):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.voice_judger_cb})  
+    def voice_judger_cb(self):
+        self.voice_cnt += 1
+        # print("语音留言定时器cnt = {}".format(self.voice_cnt))
+        if self.voicerupt_flag == 1:#按键被按下
+           self.pwk_notice_flag = 1#表示进入收听未听留言
+           self.AI_VC_start()
+           EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言
+           self.voice_cnt = 0
+        #    self.recv_voice = 0#进入留言定时器,留言清空
+           print("进入留言定时器1,留言清空!!!!!")
+           EventMesh.publish("update_play_new_voice_info")#更新已读状态
+           self.voice_timer.stop()
+           if self.pwk_cnt_voice > 0:
+               EventMesh.publish("get_new_voice_msg_S")
+        if self.voice_cnt > 15:#10S后退出播放,5S是为了给语音提示留时间
+           self.voice_cnt = 0
+           self.recv_voice = 0#进入留言定时器,留言清空
+           if self.voice_choose_flag == 1:#播放完语音后,有冲突的时候关闭
+              self.voice_choose_flag = 0
+           print("进入留言定时器2,留言清空!!!!!")
+           self.voice_timer.stop()
+    def revoice_judge(self, *args, **kwargs):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.revoice_judger_cb})  
+    def revoice_judger_cb(self):
+        self.revoice_cnt += 1
+        # print("语音留言回复定时器cnt = {}".format(self.revoice_cnt))
+        if self.revoice_cnt > 20:#10S后退出播放,10S是为了给语音提示留时间
+           self.revoice_cnt = 0
+           self.voice_flag=0
+           EventMesh.publish("set_1M_nowplay_flag",0)
+           print("语音留言回复停止")
+           self.revoice_timer.stop()
+    def music_play(self, *args, **kwargs):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.music_play_cb})  
+    def music_play_cb(self):
+        self.music_cnt += 1
+        if self.music_cnt > 4:#4S后播放音乐,4S是为了给语音提示留时间
+           self.AI_VC_start()
+           EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言
+           self.music_cnt = 0
+           self.music_timer.stop() 
+    def history_play(self, *args, **kwargs):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.history_play_cb})  
+    def history_play_cb(self):
+        self.his_cnt += 1
+        if self.his_cnt > 4:#4S后播放,4S是为了给语音提示留时间
+           self.AI_VC_start()
+           EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言
+           self.his_cnt = 0
+           self.his_timer.stop() 
+    def wait_play(self, *args, **kwargs):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.wait_play_cb})  
+    def wait_play_cb(self):
+        self.wait_cnt += 1
+        if self.wait_voicerupt_flag == 1:#按键被按下
+           EventMesh.publish("set_is_listen_wait",1)#已听
+           self.pwk_notice_flag = 1#表示进入收听未听留言
+           self.AI_VC_start()
+           self.wait_sort = 1
+           self.AI_TTS_str = "第"+str(self.wait_sort)+"条" 
+        #    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言
+        #    self.wait_cnt = 0
+        #    print("进入留言定时器1,留言清空!!!!!")
+        #    EventMesh.publish("update_play_new_voice_info")#更新已读状态
+           self.wait_timer.stop()
+        if self.wait_cnt > 10:#10S后退出播放,5S是为了给语音提示留时间
+           self.wait_cnt = 0
+           self.recv_voice = 0#进入留言定时器,留言清空
+           EventMesh.publish("set_history_voice_flag",0)
+           EventMesh.publish("set_wait_voice_flag",0)
+           EventMesh.publish("set_open_device",0)#开机不听
+           self.wait_first_flag = 0
+           if self.voice_choose_flag == 1:#播放完语音后,有冲突的时候关闭
+              self.voice_choose_flag = 0
+           print("进入留言定时器2,留言清空!!!!!")
+           self.wait_timer.stop()
+    def wait_single_play(self, *args, **kwargs):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.wait_single_play_cb})  
+    def wait_single_play_cb(self):
+        self.wait_cnt += 1
+        if self.wait_cnt > 2:#2S后播放,2S是为了给语音提示留时间
+           self.AI_VC_start()
+           EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言
+           self.wait_cnt = 0
+           self.wait_timer.stop() 
+    def put_record_data(self,data):
+        try:
+            if self._ptt_should_drop_inbound_tts():
+                return
+        except Exception:
+            pass
+        #print("下发音频数据1:{}".format(len(data[0])))
+        ret=self.AI_audio_queue.put(data)
+        try:
+            if self.tts_in_data_len == 0 and data and len(data[0]) > 0:
+                try:
+                    sid = getattr(self, "_cur_sid", "")
+                    did = getattr(self, "_cur_dialog_id", "")
+                except Exception:
+                    sid, did = "", ""
+                print("[TTS] first_chunk len={} play_pcm={} VC_Play_flag={} sid={} did={}".format(
+                    len(data[0]),
+                    1 if self.play_pcm != None else 0,
+                    getattr(self, "VC_Play_flag", 0),
+                    sid,
+                    did,
+                ))
+        except Exception:
+            pass
+        self.tts_in_data_len += len(data[0])
+    def play_tts(self,event = None,msg= None):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_tts_cb,"data":msg}) 
+    def play_tts_cb(self,data):
+        try:
+            tts_str = data[0]
+            is_close_VC = data[1]
+            if tts_str != "":
+                self.AI_TTS_str = tts_str
+            if is_close_VC != None:
+                self.Close_flag = is_close_VC
+            if self.AI_VC_send_data_queue.empty()!=True:
+                self.AI_VC_send_data_queue.clear()
+            if self.AI_ASR_send_data_queue.empty()!=True:
+                self.AI_ASR_send_data_queue.clear()
+            try:
+                if is_close_VC:
+                    EventMesh.publish("set_led_state", {"ai_state": "idle"})
+                else:
+                    EventMesh.publish("set_led_state", {"ai_state": "thinking"})
+            except Exception:
+                pass
+            self.AI_ASR_send_buf_len = 0
+            self._uplink_request_stop("play_tts_cb")
+            self.websocket_deconnect_VC()
+            self.AI_VC_start()
+            
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) 
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) 
+        except Exception as e:
+            usys.print_exception(e)
+        pass
+    def VC_Play_Start(self):
+        try:
+            if self._ptt_should_skip_remote_playback():
+                if self._vc_led_state_debug_enabled():
+                    print("[VC] VC_Play_Start skip (ptt_interrupt)")
+                return
+        except Exception:
+            pass
+        self.tts_in_data_len = 0
+        self.tts_out_data_len = 0
+        self._tts_first_write_ms = 0
+        try:
+            self._tts_completed_wall_ms = 0
+        except Exception:
+            pass
+        if self.VC_Play_flag == 0:
+            try:
+                _ptt_fast_open = False
+                try:
+                    if getattr(self, "VC_mode", -1) == 1:
+                        _ptt_fast_open = True
+                except Exception:
+                    pass
+                # 本地提示音/文件播放可能占用同一音频通道,导致 TTS 流播放“写入成功但无声”;
+                # 在开启 TTS PCM 前先停掉本地播放,避免资源冲突。
+                try:
+                    self.aud.stopPlayStream()
+                    self.aud.stopAll()
+                except Exception:
+                    pass
+                # 资源互斥:播放前确保录音上行已停止(无 AEC 场景)
+                try:
+                    if self.record_data_save_flag == 0 and self.VC_record_state_flag != 0:
+                        self._uplink_request_stop("VC_Play_Start")
+                        utime.sleep_ms(30)
+                except Exception:
+                    pass
+                if self.VC_play_TYPE == VC_play_TYPE_PCM:
+                    if self.play_pcm == None:
+                        # 刚停采集时延后打开播音 PCM,且失败时重试,降低「pcm open write only fail」
+                        last_err = None
+                        _open_retries = 1 if _ptt_fast_open else 3
+                        for i in range(_open_retries):
+                            try:
+                                if not _ptt_fast_open:
+                                    utime.sleep_ms(80 + i * 60)
+                                self.play_pcm = audio.Audio.PCM(0, 1, self.VC_play_samplerate, 1, 1, 2)
+                                ret = self.play_pcm.stream_write_start()
+                                print("self.record_pcm.stream_write_start:ret:{}".format(ret))
+                                last_err = None
+                                break
+                            except Exception as e:
+                                last_err = e
+                                try:
+                                    if self.play_pcm != None:
+                                        self.play_pcm.close()
+                                except Exception:
+                                    pass
+                                self.play_pcm = None
+                        if last_err:
+                            raise last_err
+                elif self.VC_play_TYPE == VC_play_TYPE_MP3:
+                    pass
+                self.VC_Play_flag = 1
+                try:
+                    if not self._ptt_should_skip_remote_playback():
+                        self._vc_publish_led("playing", "VC_Play_Start/tts")
+                except Exception:
+                    pass
+            except Exception as e:
+                self.VC_Play_End()
+                usys.print_exception(e)
+    def VC_Play_Pause(self,Pause_flag):
+        try:
+            if self.VC_play_TYPE == VC_play_TYPE_PCM:
+                if self.play_pcm != None:    
+                    self.play_pcm.stream_pause(Pause_flag)
+        except Exception as e:
+            self.VC_Play_End()
+            usys.print_exception(e)
+    def VC_Play_End(self):
+        self.VC_Play_flag = 0
+        if self.VC_play_TYPE == VC_play_TYPE_PCM:
+            if self.VC_play_samplerate == 8000:
+                self.aud.stopPlayStream()
+                self.aud.stopAll()
+            elif self.VC_play_samplerate == 16000:
+                if self.play_pcm != None:
+                    print("Close:play_pcm.close()!!!!!!!!!!!!!!!!!!")
+                    try:
+                        self.play_pcm.stream_stop()
+                        self.play_pcm.close()
+                    except Exception as e:
+                        usys.print_exception(e)
+                    self.play_pcm=None
+                    
+                self.aud.stopPlayStream()
+                self.aud.stopAll()
+        elif self.VC_play_TYPE == VC_play_TYPE_MP3:
+            self.aud.stopPlayStream()
+            self.aud.stopAll()
+        if self.AI_audio_queue.empty()!=True:
+            self.AI_audio_queue.clear()
+
+        # TTS 播完:按真实 FSM/录音态恢复灯;本地短音不走此路径
+        try:
+            if getattr(self, "_vc_play_end_suppress_idle_led", 0):
+                if self._vc_led_state_debug_enabled():
+                    print("[LED] VC_Play_End skip led (suppress)")
+            elif getattr(self, "VC_record_state_flag", 0) != 0:
+                self._vc_publish_led("listening", "VC_Play_End/recording")
+            else:
+                self._vc_led_sync_from_fsm()
+        except Exception:
+            pass
+        # 关键日志:定位“tts 结束绿灯仍不灭”究竟是 base 态(如 charging/full)还是 playing 未退出
+        try:
+            if "vc_config" in globals() and vc_config and (
+                vc_config.get_bool("LED_DEBUG", False) or vc_config.get_bool("KEY_LED_DEBUG", False) or vc_config.get_bool("HW_STATUS_DEBUG", False)
+            ):
+                try:
+                    led = EventMesh.publish("get_led_debug_state")
+                except Exception:
+                    led = None
+                print("[VC] VC_Play_End led={}".format(led))
+        except Exception:
+            pass
+    def _vc_tts_bytes_per_sec(self):
+        try:
+            return int(self.VC_play_samplerate) * 2
+        except Exception:
+            return 32000
+
+    # audio_audio.c: ring_buf_len = 64*1024;stream_get_buf_len 返回「空闲可写」而非待播
+    _PCM_STREAM_RING_BYTES = 65536
+
+    def _vc_pcm_stream_pending_bytes(self, pcm_free):
+        if pcm_free < 0:
+            return -1
+        if pcm_free >= self._PCM_STREAM_RING_BYTES:
+            return 0
+        return self._PCM_STREAM_RING_BYTES - pcm_free
+
+    def _vc_try_play_end_tick(self):
+        """tts.completed 后判定本地 PCM 是否真正播完,避免过早 VC_Play_End 截断 TTS。"""
+        try:
+            if not getattr(self.fsm, "await_played", False):
+                return False
+            in_len = getattr(self, "tts_in_data_len", 0)
+            out_len = getattr(self, "tts_out_data_len", 0)
+            try:
+                q_empty = self.AI_audio_queue.empty()
+            except Exception:
+                q_empty = True
+            tts_done_ms = getattr(self, "_tts_completed_wall_ms", 0) or getattr(
+                self.fsm, "last_tts_completed_ms", 0)
+            if not (in_len > 0 and out_len >= in_len and q_empty and tts_done_ms):
+                return False
+
+            is_pcm_empty = (self.play_pcm is None) or (
+                self.play_pcm is not None and self.play_pcm.stream_is_empty() == 1
+            )
+            pcm_free = 0
+            if self.play_pcm is not None:
+                try:
+                    pcm_free = self.play_pcm.stream_get_buf_len()
+                except Exception:
+                    pcm_free = -1
+            pcm_pending = self._vc_pcm_stream_pending_bytes(pcm_free)
+            if pcm_pending < 0:
+                pcm_pending = max(0, in_len - out_len)
+
+            now = utime.ticks_ms()
+            bps = self._vc_tts_bytes_per_sec()
+            fw = getattr(self, "_tts_first_write_ms", 0)
+            # 丢弃明显陈旧的 completed 时间(上轮残留或 last_tts_completed_ms==1 等)
+            try:
+                if fw and tts_done_ms and utime.ticks_diff(tts_done_ms, fw) < 0:
+                    tts_done_ms = now
+                if tts_done_ms and utime.ticks_diff(now, tts_done_ms) > 120000:
+                    return False
+            except Exception:
+                pass
+
+            try:
+                since_done = utime.ticks_diff(now, tts_done_ms)
+            except Exception:
+                since_done = 0
+            debounce_ms = 150
+            if pcm_pending > 0 and bps > 0:
+                tail_ms = max(debounce_ms, int(pcm_pending * 1000 // bps) + 200)
+            else:
+                tail_ms = debounce_ms
+            tail_ok = since_done >= tail_ms
+            # 与旧 ai_VC LocalRespondingEnded 一致:stream 空且环形缓冲无待播数据
+            pcm_drained = is_pcm_empty and pcm_pending == 0
+            ready = pcm_drained and tail_ok
+
+            if not ready:
+                try:
+                    if not getattr(self, "_tts_play_check_log_ms", 0) or utime.ticks_diff(
+                        now, self._tts_play_check_log_ms
+                    ) >= 800:
+                        self._tts_play_check_log_ms = now
+                        print(
+                            "[TTS] play_end_wait since_done={}ms tail~{}ms "
+                            "in={} out={} q_empty={} pcm_free={} pcm_pending={} pcm_empty={}".format(
+                                since_done, tail_ms, in_len, out_len,
+                                1 if q_empty else 0, pcm_free, pcm_pending,
+                                1 if is_pcm_empty else 0,
+                            )
+                        )
+                except Exception:
+                    pass
+                return False
+
+            try:
+                print(
+                    "[TTS] play_end_fire since_done={}ms tail~{}ms "
+                    "in={} out={} pcm_free={} pcm_pending={} pcm_empty={}".format(
+                        since_done, tail_ms, in_len, out_len, pcm_free, pcm_pending,
+                        1 if is_pcm_empty else 0,
+                    )
+                )
+            except Exception:
+                pass
+            EventMesh.publish("publish_AI_btnInfo", {"func": self.VC_Play_End})
+            self._fsm_apply_actions(self.fsm.on_local_play_end())
+            try:
+                self.fsm.reset_rx_phases()
+            except Exception:
+                pass
+            self._tts_first_write_ms = 0
+            return True
+        except Exception as e:
+            usys.print_exception(e)
+            return False
+    def AI_client_VC_send_speech(self):
+        json_data = \
+        {
+            "header": {
+                "event": "audio.create",
+                
+            },
+        }
+        
+        if self.VC_mode == 1:#单对话模式
+            # 协议门控(可用 VC_PROTO_GUARDS 临时关闭)
+            if (not VC_PROTO_GUARDS) or (self._vc_session_started == 1):
+                self._ptt_audio_created = 1
+                try:
+                    print("[VC][proto] audio.create send session_started={} proto_guards={}".format(
+                        self._vc_session_started, VC_PROTO_GUARDS))
+                except Exception:
+                    pass
+                self.AI_client_VC_send(ujson.dumps(json_data))
+            else:
+                try:
+                    print("[VC][proto] audio.create SKIP session_started={} VC_PROTO_GUARDS={} "
+                          "(等对端 started 后再开麦)".format(self._vc_session_started, VC_PROTO_GUARDS))
+                except Exception:
+                    pass
+        
+
+            
+            
+        
+        
+        try:
+            self.fsm.on_server_asr_started()
+        except Exception:
+            pass
+    def AI_client_VC_end_speech(self):
+        json_data = \
+        {
+            "header": {
+                "event": "audio.completed",
+                
+            },
+        }
+        self.AI_client_VC_send(ujson.dumps(json_data))
+        self._ptt_audio_created = 0
+        EventMesh.publish("publish_AI_btnInfo",{"func":self._uplink_request_stop, "data":"audio.completed"})
+    def AI_client_VC_synthesis(self):
+        # 幂等保护:在上一条 synthesis 还未等到 tts.started 前,不重复发(防止多兜底/多入口重复触发)
+        try:
+            if self._tts_pending == 1:
+                return
+        except Exception:
+            pass
+        json_data = \
+        {
+            "event": "synthesis",
+            "payload": {
+                "text": self.AI_TTS_str
+            }
+        }
+        # 语义标记只用于“是否自动开麦”的策略。
+        # 当前策略:长对话不自动开麦(需按键开始),因此这里不对提示音做自动开麦标记。
+        self.AI_TTS_str = ""
+        # 标记为“等待 tts.started”,避免在 synthesis→tts.started 窗口误开下一轮录音上行
+        self._tts_pending = 1
+        try:
+            self._tts_pending_ms = utime.ticks_ms()
+        except Exception:
+            self._tts_pending_ms = 0
+        try:
+            self.fsm.on_local_synthesis_sent()
+        except Exception:
+            pass
+        print("AI_client_VC_synthesis!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+        # if self.VC_state == VC_State_RequestToRespond:
+        #     print("AI_client_VC_RequestToRespond 重复 tts_str:{}\nasr_str:{}".format(self.AI_TTS_str,self.AI_ASR_str))
+        self.AI_client_VC_send(ujson.dumps(json_data))
+    def AI_client_VC_audio_played(self):
+        # V2.5:播完上报;与 VC_PROTO_GUARDS 无关(见文件头说明)
+        try:
+            if self.AI_client_VC_isconnect != 1 or self.AI_client_VC == None:
+                return
+            json_data = {"event": "audio.played", "payload": {}}
+            print("AI_client_VC_audio_played")
+            self.AI_client_VC_send(ujson.dumps(json_data))
+        except Exception as e:
+            usys.print_exception(e)
+    def set_vad_silence(self,event = None,msg= None):
+        self.vad_silence = msg
+        EventMesh.publish("set_storage_config",["vad_silence",self.vad_silence])  
+    def set_VC_TTS_voice_type(self,event = None,msg= None):
+        self.VC_TTS_voice_type["APP_voice"] = msg
+        #self.VC_TTS_voice_type[self.VC_TTS_voice_key]
+        self.VC_TTS_voice_key = "APP_voice"
+        EventMesh.publish("set_storage_config",["VC_TTS_voice_type",self.VC_TTS_voice_type])
+        EventMesh.publish("set_storage_config",["VC_TTS_voice_key",self.VC_TTS_voice_key])
+        print("[VC] TTS_voice key={} map={}".format(self.VC_TTS_voice_key, self.VC_TTS_voice_type))
+    def change_volume(self,event =None, msg =None):
+        if msg == 0 :
+            self.VC_TTS_volume -= 10
+            if self.VC_TTS_volume < 0:
+                self.VC_TTS_volume = 0
+                return 0
+            else:
+                self.aud.setVolume(self.VC_TTS_volume //10)
+                EventMesh.publish("set_storage_config",["VC_TTS_volume",self.VC_TTS_volume])
+                EventMesh.publish("post_device_state")
+                EventMesh.publish("adjust_volume_toBle",self.VC_TTS_volume)
+                return 1
+        elif msg == 1:
+            self.VC_TTS_volume += 10
+            if self.VC_TTS_volume > 100:
+                self.VC_TTS_volume = 100
+                return 0
+            else:
+                self.aud.setVolume(self.VC_TTS_volume //10)
+                EventMesh.publish("set_storage_config",["VC_TTS_volume",self.VC_TTS_volume])
+                EventMesh.publish("post_device_state")
+                EventMesh.publish("adjust_volume_toBle",self.VC_TTS_volume)
+                return 1
+        elif msg >= 100:#修改为具体音量
+            if msg > 200:
+                self.VC_TTS_volume = 100
+                return 0
+            else:
+                self.VC_TTS_volume = msg - 100#还原数据
+                self.aud.setVolume(self.VC_TTS_volume //10)
+                EventMesh.publish("set_storage_config",["VC_TTS_volume",self.VC_TTS_volume])
+                EventMesh.publish("post_device_state")
+                EventMesh.publish("adjust_volume_toBle",self.VC_TTS_volume)
+                return 1
+    def NLP_set_vad_silence(self,event,msg):
+        if msg == 1:#聆听变快
+            self.vad_silence -= 1000
+            if self.vad_silence < 1000:
+                AI_TTS_str = "已经是最快了" 
+                self.vad_silence = 1000
+            else:
+                AI_TTS_str = "配置成功,当前语音断句速度是{}秒".format(self.vad_silence/1000)
+        elif msg == 0:#聆听变慢
+            self.vad_silence += 1000
+            if self.vad_silence > 4000:
+                self.vad_silence = 4000
+                #EventMesh.publish("audio_montage_play",["yinliangyizuixiao.mp3"])
+                #self.aud.play(1,0,'U:/static/yinliangyizuixiao.mp3')#音量已最小
+                AI_TTS_str = "已经是最慢了"
+            else:
+                AI_TTS_str = "配置成功,当前语音断句速度是{}秒".format(self.vad_silence/1000)        
+        EventMesh.publish("set_storage_config",["vad_silence",self.vad_silence])
+        return AI_TTS_str
+    def change_voice(self,event ,msg):
+        voice = msg
+        # if "男声" in msg or "普通话" in msg:
+        #     self.VC_TTS_voice_key = "男"
+        # else:
+        #     self.VC_TTS_voice_key = voice
+        self.outbound_attributess_provider = "xun-fei"
+        if "男声" in msg or "普通话" in msg:
+            self.VC_TTS_voice_key = "男"
+            self.outbound_attributess_provider = "volcengine"
+        elif "女声" in msg:
+            self.VC_TTS_voice_key = "女"
+            self.outbound_attributess_provider = "volcengine"
+        elif "童声" in msg:
+            self.VC_TTS_voice_key = "童"    
+            self.outbound_attributess_provider = "volcengine"
+        elif "香港粤语" in msg:
+            self.VC_TTS_voice_key = "香港粤语"
+        elif "四川话" in msg:
+            self.VC_TTS_voice_key = "四川"    
+        elif "上海话" in msg:
+            self.VC_TTS_voice_key = "上海" 
+        elif "安徽合肥话" in msg:
+            self.VC_TTS_voice_key = "安徽合肥"    
+        elif "山东话" in msg:
+            self.VC_TTS_voice_key = "山东话" 
+        elif "陕西话" in msg:
+            self.VC_TTS_voice_key = "陕西"    
+        elif "广东话" in msg:
+            self.VC_TTS_voice_key = "广东" 
+        elif "成都话" in msg:
+            self.VC_TTS_voice_key = "成都"    
+        elif "湖南话" in msg:
+            self.VC_TTS_voice_key = "湖南" 
+        elif "苏州话" in msg:
+            self.VC_TTS_voice_key = "苏州"    
+        elif "东北话" in msg:
+            self.VC_TTS_voice_key = "东北" 
+        elif "河南话" in msg:
+            self.VC_TTS_voice_key = "河南"    
+        elif "湖北话" in msg:
+            self.VC_TTS_voice_key = "湖北" 
+        elif "闽南话" in msg:
+            self.VC_TTS_voice_key = "闽南"    
+        elif "内蒙古话" in msg:
+            self.VC_TTS_voice_key = "内蒙古话" 
+        EventMesh.publish("set_storage_config",["VC_TTS_voice_key",self.VC_TTS_voice_key])
+        tts_play_str = "声音已改为{}".format(voice)
+        EventMesh.publish("play_tts",[tts_play_str,0])
+    def play_single_voice(self,event = None,msg= None):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_single_voice_cb,"data":msg}) 
+    def play_single_voice_cb(self,data):
+        try:
+            new_voice_url = data[0] #音频地址
+            is_close_VC = data[1]
+            if new_voice_url != "":
+                self.AI_new_voice_url = new_voice_url
+                self.alarm_play_flag = 1
+            if is_close_VC != None:
+                self.Close_flag = is_close_VC
+            if self.AI_VC_send_data_queue.empty()!=True:
+                self.AI_VC_send_data_queue.clear()
+            if self.AI_ASR_send_data_queue.empty()!=True:
+                self.AI_ASR_send_data_queue.clear()
+            EventMesh.publish("set_led_state", {"ai_state": "thinking"})
+            self.AI_ASR_send_buf_len = 0
+            self._uplink_request_stop("play_single_voice_cb")
+            self.websocket_deconnect_VC()
+            self.AI_VC_start()
+            self.play_new_voice_flag = 1
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) 
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) 
+        except Exception as e:
+                usys.print_exception(e)   
+        pass       
+    def play_new_voice(self,event = None,msg= None):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_new_voice_cb,"data":msg}) 
+    def play_new_voice_cb(self,data):
+        try:
+            new_voice_url = data[0] #音频地址
+            is_close_VC = data[1]
+            if new_voice_url != "":
+                self.recv_voice = 1#收到留言
+                print("已收到最新留言!!!!!")
+                # EventMesh.publish("set_motor_time",[1,1,5,8,4])#设置马达参数
+                self.AI_new_voice_url = new_voice_url
+                # if self.pwk_cnt_voice == 0 and self.pwk_notice_flag == 0:#并且不在多条留言的状态
+                #     EventMesh.publish("play_tts",["您有一条语音留言,10秒内按下中间键可以收听",1])
+                if self.pwk_notice_flag == 0 and EventMesh.publish("get_app_new_voice_flag"):#不处于播放状态并且是小程序下发的语音而不是请求的
+                    self.pwk_cnt_voice += 1#加上当前这条
+                    EventMesh.publish("set_storage_config",["is_noread_voice",1])
+                    EventMesh.publish("set_app_new_voice_flag",0)
+                    print("voice_add_to = {}".format(self.pwk_cnt_voice))
+                print("self.pwk_notice_flag = {}".format(self.pwk_notice_flag))
+                if self.pwk_cnt_voice >1:
+                    if self.pwk_notice_flag == 0:
+                        tts_str = "您有" + str(self.pwk_cnt_voice) + "条未读语音留言,10秒内按下中间键可以收听"
+                        EventMesh.publish("play_tts",[tts_str,1])
+                elif self.pwk_cnt_voice == 1:
+                    if self.pwk_notice_flag == 0:
+                        tts_str = "您有1条语音留言,10秒内按下中间键可以收听"
+                        EventMesh.publish("play_tts",[tts_str,1])
+            if is_close_VC != None:
+                self.Close_flag = is_close_VC
+            if self.AI_VC_send_data_queue.empty()!=True:
+                self.AI_VC_send_data_queue.clear()
+            if self.AI_ASR_send_data_queue.empty()!=True:
+                self.AI_ASR_send_data_queue.clear()
+            EventMesh.publish("set_led_state", {"ai_state": "thinking"})
+            self.AI_ASR_send_buf_len = 0
+            self._uplink_request_stop("play_new_voice_cb")
+            self.websocket_deconnect_VC()
+            self.AI_VC_start()
+            self.play_new_voice_flag = 1
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) 
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) 
+        except Exception as e:
+                usys.print_exception(e)   
+        pass
+    def play_his_voice(self,event = None,msg= None):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_his_voice_cb,"data":msg}) 
+    def play_his_voice_cb(self,data):
+        try:
+            new_voice_url = data[0] #音频地址
+            is_close_VC = data[1]
+            time = data[2]
+            tts_play_str = time + "收到" 
+            EventMesh.publish("play_tts",[tts_play_str,1])
+            if new_voice_url != "":
+                self.recv_voice = 1#收到留言
+                print("历史留言查看")
+                # tts_play_str = "收到" 
+                # EventMesh.publish("play_tts",[tts_play_str,1])
+                # EventMesh.publish("set_motor_time",[1,1,5,8,4])#设置马达参数
+                self.AI_new_voice_url = new_voice_url
+            if is_close_VC != None:
+                self.Close_flag = is_close_VC
+            if self.AI_VC_send_data_queue.empty()!=True:
+                self.AI_VC_send_data_queue.clear()
+            if self.AI_ASR_send_data_queue.empty()!=True:
+                self.AI_ASR_send_data_queue.clear()
+            EventMesh.publish("set_led_state", {"ai_state": "thinking"})
+            self.AI_ASR_send_buf_len = 0
+            self._uplink_request_stop("play_his_voice_cb")
+            self.websocket_deconnect_VC()
+            self.AI_VC_start()
+            self.play_new_voice_flag = 1
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) 
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) 
+        except Exception as e:
+                usys.print_exception(e)   
+        pass
+    def play_wait_voice(self,event = None,msg= None):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_wait_voice_cb,"data":msg}) 
+    def play_wait_voice_cb(self,data):
+        try:
+            new_voice_url = data[0] #音频地址
+            is_close_VC = data[1]
+            print("是否为第一次:{}".format(self.wait_first_flag))
+            if self.wait_first_flag == 0:
+                tts_play_str = "您有" + str(len(EventMesh.publish("get_play_new_voice_list"))) + "条未听留言,10秒内按下中间键可以收听"
+                EventMesh.publish("play_tts",[tts_play_str,1])  
+                # self.wait_first_timer.start(1000,1,self.noread_wait_vocie)
+                self.wait_first_flag = 1
+            elif self.wait_first_flag == 1:
+                self.wait_sort += 1
+                self.AI_TTS_str = "第"+str(self.wait_sort)+"条" 
+            if new_voice_url != "":
+                self.recv_voice = 1#收到留言
+                print("未读留言查看")
+                self.AI_new_voice_url = new_voice_url
+            if is_close_VC != None:
+                self.Close_flag = is_close_VC
+            if self.AI_VC_send_data_queue.empty()!=True:
+                self.AI_VC_send_data_queue.clear()
+            if self.AI_ASR_send_data_queue.empty()!=True:
+                self.AI_ASR_send_data_queue.clear()
+            EventMesh.publish("set_led_state", {"ai_state": "thinking"})
+            self.AI_ASR_send_buf_len = 0
+            self._uplink_request_stop("play_wait_voice_cb")
+            self.websocket_deconnect_VC()
+            self.AI_VC_start()
+            self.play_new_voice_flag = 1
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) 
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) 
+        except Exception as e:
+                usys.print_exception(e)   
+        pass
+    def noread_wait_vocie(self,*args):#如果一直没读
+        self.wait_first_cnt += 1
+        print("未读留言等待收听中:{}".format(self.wait_first_cnt))
+        if EventMesh.publish("get_recv_voice") == 1:#如果检测到留言播放
+            self.wait_first_cnt = 0
+            self.wait_first_timer.stop()
+        if self.wait_first_cnt == 15:
+            EventMesh.publish("set_wait_first_flag",0)
+            EventMesh.publish("set_history_voice_flag",0)
+            EventMesh.publish("set_wait_voice_flag",0)
+            self.wait_first_cnt = 0
+            self.wait_first_timer.stop()
+    def play_music(self,event = None,msg= None):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_music_cb,"data":msg}) 
+    def play_music_cb(self,data):
+        try:
+            music_name = data[0]
+            new_voice_url = data[1] #音频地址
+            is_close_VC = data[2]
+            if new_voice_url != "":
+                self.AI_new_voice_url = new_voice_url
+                self.music_play_flag = 1#表示音乐播放开启
+                EventMesh.publish("play_tts",["下面为您播放歌曲{}".format(music_name),1])
+            if is_close_VC != None:
+                self.Close_flag = is_close_VC
+            if self.AI_VC_send_data_queue.empty()!=True:
+                self.AI_VC_send_data_queue.clear()
+            if self.AI_ASR_send_data_queue.empty()!=True:
+                self.AI_ASR_send_data_queue.clear()
+            EventMesh.publish("set_led_state", {"ai_state": "thinking"})
+            self.AI_ASR_send_buf_len = 0
+            self._uplink_request_stop("play_music_cb")
+            self.websocket_deconnect_VC()
+            self.AI_VC_start()
+            self.play_new_voice_flag = 1
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) 
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) 
+        except Exception as e:
+                usys.print_exception(e)   
+    def play_trans_tts(self,event = None,msg= None):
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.play_trans_tts_cb,"data":msg}) 
+    def play_trans_tts_cb(self,data):
+        try:
+            new_voice_url = data #音频地址
+            if new_voice_url != "":
+                self.AI_new_voice_url = new_voice_url
+            if self.AI_VC_send_data_queue.empty()!=True:
+                self.AI_VC_send_data_queue.clear()
+            if self.AI_ASR_send_data_queue.empty()!=True:
+                self.AI_ASR_send_data_queue.clear()
+            EventMesh.publish("set_led_state", {"ai_state": "thinking"})
+            self.AI_ASR_send_buf_len = 0
+            self._uplink_request_stop("play_trans_tts_cb")
+            self.websocket_deconnect_VC()
+            self.AI_VC_start()
+            EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_client_new_voice})#播放留言
+            self.play_new_voice_flag = 1
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.VC_Speech_End})
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_deconnect_VC}) 
+            # EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_VC_start}) 
+        except Exception as e:
+                usys.print_exception(e)   
+    def AI_audio_queue_thread_close(self):
+        print("AI_audio_queue_thread_close1!!!!!!!!!!!!!!!!!!!!!!")
+        if self.AI_audio_queue_thread_id != 0:
+            count =500
+            while True:
+                count -= 1
+                if self.AI_audio_queue_thread_lock == 0 or count <=0:
+                    break
+                else:
+                    self.AI_audio_queue_thread_lock = 2
+                    print("AI_audio_queue_thread going:{}...".format(count))
+                    utime.sleep_ms(1)
+            self.AI_audio_queue_thread_lock = 0
+            print("AI_audio_queue_thread_close2!!!!!!!!!!!!!!!!!")
+            if self.AI_audio_queue_thread_id != 0:
+                try:
+                    _thread.stop_thread(self.AI_audio_queue_thread_id)
+                except Exception as e:
+                    usys.print_exception(e)
+        self.AI_audio_queue_thread_id = 0
+    def AI_audio_queue_thread(self):
+        print("AI_audio_queue_thread start")
+        count = 0
+        if self.AI_audio_queue.empty()!=True:
+            self.AI_audio_queue.clear()
+            print("音频数据还有剩余!!!!!!!!!!!!")
+        while True:
+            try:
+                if self.AI_audio_queue_thread_lock == 2 or self.VC_stop_play_audio_flag == 0:
+                    print("AI_audio_queue_thread 该退出了")
+                    break
+                #
+                count+=1
+                if count%500 == 1 and VC_HEARTBEAT_LOG:
+                    print("AI_audio_queue_thread on")
+                utime.sleep_ms(1)
+                #print("AI_audio_queue_thread")
+                if self.AI_audio_queue.empty() :
+                    self.AI_audio_queue_thread_lock = 0
+                    
+                    
+                else:
+                    self.AI_audio_queue_thread_lock = 1
+                    data = self.AI_audio_queue.get()
+                    self.close_timerout = 0
+                    if self.play_flag == 0:
+                        self.play_flag =1
+                    #self.AI_client_tts_data_len=len(data)
+                    #print("audio_queue:len:{}".format(len(data[0])))
+                    
+                    if len(data[0]) > 10:
+                        try:
+                            if self._ptt_should_drop_inbound_tts():
+                                continue
+                        except Exception:
+                            pass
+                        for i in range(5): 
+                            if self.VC_Play_flag == 0:
+                                self.VC_Play_Start()
+                                utime.sleep_ms(2)
+                            else:
+                                break
+                        # 打断后 PCM 偶发未打开:加重试,避免「队列有数据、play_pcm=None、无声」
+                        if self.VC_play_TYPE == VC_play_TYPE_PCM and self.play_pcm is None and self.VC_stop_play_audio_flag == 1:
+                            for j in range(4):
+                                if self.VC_Play_flag == 0:
+                                    self.VC_Play_Start()
+                                utime.sleep_ms(30 + j * 35)
+                                if self.play_pcm is not None:
+                                    break
+                        try:
+                            if self.VC_play_TYPE == VC_play_TYPE_PCM:
+                                # if self.VC_play_samplerate == 8000:
+                                #     tmp= self.aud.playStream(1,data[0] )
+                                # else:
+                                    write_buf = bytearray(data[0])
+                                    #print("play_pcm.stream_write1 len:{}".format(len(write_buf)))
+                                    if self.play_pcm != None:
+                                        #print("play_pcm.stream_write !!!")
+                                        ret = self.play_pcm.stream_write(write_buf,len(write_buf))
+                                        #ret= self.aud.playStream(1, data[0])#播放音频流
+                                        if self.tts_out_data_len == 0:
+                                            try:
+                                                self._tts_first_write_ms = utime.ticks_ms()
+                                            except Exception:
+                                                self._tts_first_write_ms = 0
+                                            try:
+                                                sid = getattr(self, "_cur_sid", "")
+                                                did = getattr(self, "_cur_dialog_id", "")
+                                            except Exception:
+                                                sid, did = "", ""
+                                            print("[TTS] first_write ret={} len={} sid={} did={}".format(
+                                                ret, len(write_buf), sid, did
+                                            ))
+                                        if ret <= 0:
+                                            print("[TTS] stream_write ret={} len={} (no audio?)".format(ret, len(write_buf)))
+                                        self.tts_out_data_len += ret
+                                        try:
+                                            if getattr(self.fsm, "await_played", False) and self.AI_audio_queue.empty():
+                                                self._vc_try_play_end_tick()
+                                        except Exception:
+                                            pass
+                                        #print("play_pcm.stream_write2:{}".format(self.tts_out_data_len))
+                            elif self.VC_play_TYPE == VC_play_TYPE_MP3:
+                                tmp= self.aud.playStream(3, data[0])#播放音频流
+                                
+                                if tmp != 0:
+                                    print("2playStream:error tmp:{} data len:{}!!!!!!!!!!!!".format(tmp,len(data[0])))
+                                    # # #播放失败的时候填充WAV头
+                                    # data_len = len(data[0])
+                                    # data_len_b = data_len.to_bytes(4,'little')#数据长度转16进制数组
+                                    # h_data = [ 0x52 ,0x49 ,0x46 ,0x46 #,0x24 ,0x00 ,0x00 ,0x00 #文件标识、文件大小
+                                    #         ,0x57 ,0x41 ,0x56 ,0x45 ,0x66 ,0x6D ,0x74 ,0x20  #文件格式、格式块标识符
+                                    #         ,0x10 ,0x00 ,0x00 ,0x00 ,0x01 ,0x00 ,0x01 ,0x00 ,0x80 ,0x3E ,0x00 ,0x00  #格式块大小4、音频格式2、声道数2、采样率4
+                                    #         ,0x80 ,0x3E ,0x00 ,0x00 ,0x02 ,0x00 ,0x10 ,0x00 ,0x64 ,0x61 ,0x74 ,0x61]#每秒字节数4、数据块对齐2、位深度2、data4
+                                    # h_data_b = bytes(h_data)
+                                    # file_len = data_len+len(h_data_b)+4+4
+                                    # file_len_b = file_len.to_bytes(4,'little')#文件长度转16进制数组
+                                    # h_data_b = h_data_b[:4]+file_len_b+h_data_b[4:]+data_len_b
+                                    # #self.aud.playStream(2, h_data_b)#播放音频流
+                                    # tmp= self.aud.playStream(2, h_data_b+data[0])#播放音频流
+                                    # print("3playStream:tmp:{} !!!!!!!!!!!!".format(tmp))
+                            
+                            
+                            mem = gc.mem_free()
+                            #print('1剩余内存RAM:{} KB'.format(mem / 1024))
+                        except Exception as e:
+                            print("AI_audio_queue_thread:ERROR:")
+                            usys.print_exception(e)
+                    
+                    # if self.VC_state == VC_State_TTSEnd and self.AI_audio_queue.empty() :#self.AI_audio_queue为空且tts下发结束
+                    #     #通知服务器播放结束
+                    #     self.AI_client_VC_LocalRespondingEnded()
+            except Exception as e:
+                print("AI_audio_queue_thread:ERROR:")
+                usys.print_exception(e)
+        self.AI_audio_queue_thread_lock = 0
+        self.AI_audio_queue_thread_id = 0
+        print("AI_audio_queue_thread:exit")