1、添加产品pid,key的配置在 default_config.py
authorsnickersun <snickersun@163.com>
Tue, 19 May 2026 10:15:30 +0000 (18:15 +0800)
committersnickersun <snickersun@163.com>
Tue, 19 May 2026 10:15:30 +0000 (18:15 +0800)
2、添加ai键的开关,可配置power+ai 键(k8),或者只有power键 (k8s)。
3、优化指令识别

17 files changed:
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/default_config.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/default_srv.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/configs/jswm/default_config.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/configs/k8/default_config.py [new file with mode: 0644]
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/configs/k8s/default_config.py [new file with mode: 0644]
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/product_k8.ini
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/product_k8_jswm.ini [new file with mode: 0644]
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/product_k8s.ini [new file with mode: 0644]
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/srv_configs/beike/default_srv.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/srv_configs/beike_dev/default_srv.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/srv_configs/kxc/default_srv.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/protocol.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_text_intent.py [new file with mode: 0644]
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/version_util.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/versions.py

index 1a864dc..fc5a2fa 100644 (file)
@@ -958,6 +958,7 @@ frozen_mpy_list =   $(BUILD)/_boot.mpy \
                                        $(BUILD)/constants.mpy \
                                        $(BUILD)/default_config.mpy \
                                        $(BUILD)/vc_config.mpy \
+                                       $(BUILD)/vc_text_intent.mpy \
                                        $(BUILD)/default_srv.mpy \
                                        $(BUILD)/srv_config.mpy \
                                        $(BUILD)/EventMesh.mpy \
index 6b0b06b..8329c4f 100644 (file)
-from common import Abstract, LogAdapter
-try:
-    from qp_log import qp_print as print
-except Exception:
-    pass
-import EventMesh
-import uwebsocket
-import usys
-import gc
-import urandom
-import uhashlib
-import ubinascii
-import ujson
-import osTimer
-import _thread
-from queue import Queue
-import utime
-import audio
-import uos
-from machine import Pin
-from machine import ExtInt
-from machine import WDT
-import modem
-import sim
-import net
-import base64
-import ucryptolib
-import urandom as random
-import request
-import ql_fs
-from misc import Power,USB,PowerKey
-import ntptime
-import machine
-import dataCall
-import constants
-import versions
-import version_util
-from machine import UART
-try:
-    import vc_config
-except Exception as e:
-    usys.print_exception(e)
-try:
-    import VAD
-except Exception as e:
-    usys.print_exception(e)
-try:   
-    import usr.ai_alarm as _ai_alarm
-except Exception as e:
-    usys.print_exception(e)
-    import ai_alarm as _ai_alarm
-PRESS_AUDIO_MODE = 0 #是按键音 0 是使用音频文件
-# dev_type = "G10"
-#dev_type = "7PIN_BK"#小瑞
-#dev_type = "7PIN_BK_coze"#k扣子小瑞
-#dev_type = "7PIN_BK_EU"#英文小瑞
-#dev_type = "7PIN_coze"#柒牌扣子
-dev_type = "7PIN"#柒牌(默认值,实际优先从 usr/default_config.py 覆盖)
-# dev_type = "7PIN_1_5"#柒牌1.5
-#dev_type = 'momo'#食梦貘
-#dev_type = 'sjgq'#四季甘泉
-#dev_type = 'szbst'#深圳百事通
-#dev_type = 'jswm'#金三完美
-#dev_type = 'jswm_BOX'#金三完美柒牌盒子
-#dev_type = 'csh'#慈善会
-
-# 统一从配置加载 DEV_TYPE(usr 覆盖优先)
-try:
-    if "vc_config" in globals() and vc_config:
-        dev_type = vc_config.get_dev_type(dev_type)
-except Exception:
-    pass
-
-# 1=打印 VC 周期性保活专用行「VC: ping ping」;默认 0(WS send 明细里已跳过 ping,无需再开此项)
-VC_HEARTBEAT_LOG = 0
-# 1=打印音频帧级日志:Record_len / send_data len / 下发音频长度;默认 0(只看 uplink START/END)
-# VC_AUDIO_FRAME_LOG = 0
-# 1=启用我们新增的协议层“严格门控/缓冲/兜底”行为;0=临时排除这些改动(用于定位协议影响)
-# 受控项(与对端 V2.5+ 能力对齐;关=回退旧端行为):
-# - 上行:收到 started 前缓冲/丢弃 PCM;PTT 模式下 audio.create 前禁发 PCM
-# - 兜底:llm.completed 后若久无 tts.started,端侧可重启会话(不做 force_tts)
-# 注意:audio.played 不受此开关控制——金三 V2.5 服务端依赖端侧播完后上报才能进入下一轮 ASR;
-#       若误关会导致问候结束后首轮用户说话无 vad/asr。
-VC_PROTO_GUARDS = 0
-# 1=打印 VC WebSocket 每条收发的完整文本(流量大、易刷屏);0 仅一行 event 摘要(llm.delta 省略)
-VC_WS_IO_LOG = 0
-# 长对话「无业务下行」idle:下列 JSON event/type 及裸文本 ping/pong 不重置 close_timerout。
-# 帧级 WebSocket PING/PONG 在 uwebsocket.recv() 内已处理,不会进入 AI_client_VC_recv。
-# 金三 V2.5 文档示例:WebSocket 文本帧 "ping" 保活;对端可能回 pong / heartbeat 类事件。
-_VC_KEEPALIVE_RX_EVENTS = frozenset(("pong", "ping", "heartbeat", "keepalive"))
-
-
-def _vc_out_json_event_meta(s):
-    """解析 WS 出站 JSON:event 可能在 header 或顶层(Start/audio.create vs synthesis)。"""
-    try:
-        jd = ujson.loads(s)
-        h = jd.get("header")
-        if isinstance(h, dict):
-            ev = h.get("event") or ""
-            if ev:
-                return ev, h.get("trace_id") or ""
-        ev = jd.get("event") or ""
-        return ev, ""
-    except Exception:
-        return "", ""
-
-
-class AI_led(Abstract):
-    def __init__(self):
-        try:
-            if "vc_config" in globals() and vc_config:
-                r_gpio, g_gpio, b_gpio = vc_config.led_gpio_tuple()
-            else:
-                r_gpio, g_gpio, b_gpio = Pin.GPIO36, Pin.GPIO21, Pin.GPIO19
-        except Exception:
-            r_gpio, g_gpio, b_gpio = Pin.GPIO36, Pin.GPIO21, Pin.GPIO19
-        self._led_gpio_r = r_gpio
-        self._led_gpio_g = g_gpio
-        self._led_gpio_b = b_gpio
-        self.led_red = Pin(r_gpio, Pin.OUT, Pin.PULL_PD, 0)
-        self.led_green = Pin(g_gpio, Pin.OUT, Pin.PULL_PD, 1)
-        self.led_blue = Pin(b_gpio, Pin.OUT, Pin.PULL_PD, 1)
-        print("AI_led GPIO dev_type={} red={} green={} blue={}".format(
-            dev_type, r_gpio, g_gpio, b_gpio))
-
-        # 统一输入状态(充电/电量/信号/AI对话),按优先级表输出
-        self._st_ai = "idle"         # idle/listening/thinking/playing
-        self._st_charging = "none"   # none/charging/full
-        self._st_battery = "normal"  # normal/low/empty
-        self._st_signal = "normal"   # normal/weak
-        self._st_alert = None        # None / rb_flash_2(REQ-ERR-01/03)
-
-        self._timer = osTimer()
-        self._mode = "off"  # off/red_on/green_on/green_blink/blue_blink/red_blink/bg_alt/alert_rb
-        self._alert_steps_left = 0
-        self._alert_rb_phase = 0
-        self._alert_period_ms = 250
-        self._blink_on = 0
-        self._bg_phase = 0
-        self._period_ms = 1000
-        self._gb_on_ms = 1000
-        self._gb_off_ms = 1000
-
-        EventMesh.subscribe("set_led_state", self.set_led_state)
-        EventMesh.subscribe("get_led_debug_state", self.get_led_debug_state)
-        # LED 电平极性:有些板子是低电平点亮(active-low),也可能仅某一路反相
-        self._inv_r = 0
-        self._inv_g = 0
-        self._inv_b = 0
-        try:
-            if "vc_config" in globals() and vc_config:
-                inv = vc_config.get_attr("LED_ACTIVE_LOW_RGB", None)
-                if isinstance(inv, (tuple, list)) and len(inv) == 3:
-                    self._inv_r = 1 if inv[0] else 0
-                    self._inv_g = 1 if inv[1] else 0
-                    self._inv_b = 1 if inv[2] else 0
-                else:
-                    all_inv = 1 if vc_config.get_bool("LED_ACTIVE_LOW", False) else 0
-                    self._inv_r = all_inv
-                    self._inv_g = all_inv
-                    self._inv_b = all_inv
-        except Exception:
-            pass
-        try:
-            self._apply_by_table()
-        except Exception:
-            try:
-                self._set_off()
-            except Exception:
-                pass
-
-    def _timer_stop(self):
-        try:
-            self._timer.stop()
-        except Exception:
-            pass
-
-    def _set_rgb(self, r, g, b):
-        try:
-            rr = 1 if r else 0
-            gg = 1 if g else 0
-            bb = 1 if b else 0
-            if self._inv_r:
-                rr = 0 if rr else 1
-            if self._inv_g:
-                gg = 0 if gg else 1
-            if self._inv_b:
-                bb = 0 if bb else 1
-            self.led_red.write(rr)
-            self.led_green.write(gg)
-            self.led_blue.write(bb)
-        except Exception:
-            pass
-
-    def _set_off(self):
-        self._mode = "off"
-        self._timer_stop()
-        self._set_rgb(0, 0, 0)
-
-    def _set_red_on(self):
-        self._mode = "red_on"
-        self._timer_stop()
-        self._set_rgb(1, 0, 0)
-
-    def _set_green_on(self):
-        self._mode = "green_on"
-        self._timer_stop()
-        self._set_rgb(0, 1, 0)
-
-    def _set_green_blink(self, on_ms=1000, off_ms=None):
-        """待机/空闲:绿灯慢闪(替代呼吸灯,数字 GPIO 整颗亮灭)。off_ms 缺省则与 on_ms 对称。"""
-        on_ms = int(on_ms or 1000)
-        if off_ms is None:
-            off_ms = on_ms
-        else:
-            off_ms = int(off_ms or on_ms)
-        try:
-            if (self._mode == "green_blink" and int(self._gb_on_ms) == int(on_ms)
-                    and int(self._gb_off_ms) == int(off_ms)):
-                return
-        except Exception:
-            pass
-        self._mode = "green_blink"
-        self._gb_on_ms = on_ms
-        self._gb_off_ms = off_ms
-        self._period_ms = on_ms
-        self._blink_on = 1
-        self._timer_stop()
-        self._set_rgb(0, 1, 0)
-        self._timer.start(self._gb_on_ms, 0, self._timer_cb)
-
-    def _set_blue_blink(self, period_ms=1000):
-        self._mode = "blue_blink"
-        self._period_ms = period_ms or 1000
-        self._blink_on = 1
-        self._timer_stop()
-        self._set_rgb(0, 0, 1)
-        self._timer.start(self._period_ms, 1, self._timer_cb)
-
-    def _set_red_blink(self, period_ms=1000):
-        self._mode = "red_blink"
-        self._period_ms = period_ms or 1000
-        self._blink_on = 1
-        self._timer_stop()
-        self._set_rgb(1, 0, 0)
-        self._timer.start(self._period_ms, 1, self._timer_cb)
-
-    def _set_bg_alt(self, period_ms=1000):
-        self._mode = "bg_alt"
-        self._period_ms = period_ms or 1000
-        self._bg_phase = 0
-        self._timer_stop()
-        self._set_rgb(0, 0, 1)
-        self._timer.start(self._period_ms, 1, self._timer_cb)
-
-    def _set_alert_rb_flash(self, cycles=2, period_ms=250):
-        """REQ-ERR-01/03:红蓝快闪(约 2 次/秒),结束后回到 _apply_by_table。"""
-        try:
-            cycles = int(cycles or 2)
-        except Exception:
-            cycles = 2
-        if cycles < 1:
-            cycles = 1
-        try:
-            period_ms = int(period_ms or 250)
-        except Exception:
-            period_ms = 250
-        if period_ms < 100:
-            period_ms = 100
-        self._mode = "alert_rb"
-        self._st_alert = "rb_flash_2"
-        self._alert_period_ms = period_ms
-        self._alert_steps_left = cycles * 2
-        self._alert_rb_phase = 0
-        self._timer_stop()
-        self._set_rgb(1, 0, 0)
-        self._timer.start(self._alert_period_ms, 0, self._timer_cb)
-
-    def _set_red_flash_limited(self, flashes=3, period_ms=500):
-        """REQ-BAT-03:红快闪若干次后熄灭(约 2 次/秒)。"""
-        try:
-            flashes = int(flashes or 3)
-        except Exception:
-            flashes = 3
-        if flashes < 1:
-            flashes = 1
-        try:
-            period_ms = int(period_ms or 500)
-        except Exception:
-            period_ms = 500
-        if period_ms < 100:
-            period_ms = 100
-        self._mode = "red_flash_n"
-        self._red_flash_steps_left = flashes * 2
-        self._alert_period_ms = period_ms
-        self._blink_on = 1
-        self._timer_stop()
-        self._set_rgb(1, 0, 0)
-        self._timer.start(self._alert_period_ms, 0, self._timer_cb)
-
-    def _timer_cb(self, *args, **kwargs):
-        EventMesh.publish("publish_AI_btnInfo", {"func": self._timer_cb_})
-
-    def _timer_cb_(self):
-        if self._mode == "green_blink":
-            self._blink_on = 0 if self._blink_on else 1
-            self._set_rgb(0, 1 if self._blink_on else 0, 0)
-            next_ms = self._gb_on_ms if self._blink_on else self._gb_off_ms
-            try:
-                self._timer.start(next_ms, 0, self._timer_cb)
-            except Exception:
-                pass
-            return
-        if self._mode == "blue_blink":
-            self._blink_on = 0 if self._blink_on else 1
-            self._set_rgb(0, 0, 1 if self._blink_on else 0)
-            return
-        if self._mode == "red_blink":
-            self._blink_on = 0 if self._blink_on else 1
-            self._set_rgb(1 if self._blink_on else 0, 0, 0)
-            return
-        if self._mode == "red_flash_n":
-            self._red_flash_steps_left -= 1
-            if self._red_flash_steps_left <= 0:
-                self._set_off()
-                return
-            self._blink_on = 0 if self._blink_on else 1
-            self._set_rgb(1 if self._blink_on else 0, 0, 0)
-            try:
-                self._timer.start(self._alert_period_ms, 0, self._timer_cb)
-            except Exception:
-                pass
-            return
-        if self._mode == "bg_alt":
-            if self._bg_phase == 0:
-                self._set_rgb(0, 1, 0)
-                self._bg_phase = 1
-            else:
-                self._set_rgb(0, 0, 1)
-                self._bg_phase = 0
-            return
-        if self._mode == "alert_rb":
-            self._alert_steps_left -= 1
-            if self._alert_steps_left <= 0:
-                self._st_alert = None
-                try:
-                    self._apply_by_table()
-                except Exception:
-                    self._set_off()
-                return
-            if self._alert_rb_phase == 0:
-                self._set_rgb(0, 0, 1)
-                self._alert_rb_phase = 1
-            else:
-                self._set_rgb(1, 0, 0)
-                self._alert_rb_phase = 0
-            try:
-                self._timer.start(self._alert_period_ms, 0, self._timer_cb)
-            except Exception:
-                pass
-            return
-
-    def set_led_state(self, event=None, msg=None):
-        # msg: dict {ai_state, charging, battery, signal, alert}
-        try:
-            if isinstance(msg, dict):
-                if "alert" in msg:
-                    aval = msg.get("alert")
-                    if aval in ("rb_flash_2", "rb_flash", "rb_flash2"):
-                        self._set_alert_rb_flash(2, 250)
-                        return
-                    if aval in ("red_flash_3", "red_flash3"):
-                        try:
-                            n = 3
-                            ms = 500
-                            if "vc_config" in globals() and vc_config:
-                                n = int(vc_config.get_int("BATTERY_EMPTY_FLASH_COUNT", 3))
-                                ms = int(vc_config.get_int("BATTERY_EMPTY_FLASH_PERIOD_MS", 500))
-                            self._set_red_flash_limited(n, ms)
-                        except Exception:
-                            self._set_red_flash_limited(3, 500)
-                        return
-                    if aval in (None, "", "none", "clear"):
-                        self._st_alert = None
-                if "ai_state" in msg:
-                    self._st_ai = msg.get("ai_state") or "idle"
-                if "charging" in msg:
-                    self._st_charging = msg.get("charging") or "none"
-                if "battery" in msg:
-                    self._st_battery = msg.get("battery") or "normal"
-                if "signal" in msg:
-                    self._st_signal = msg.get("signal") or "normal"
-        except Exception:
-            pass
-        if self._mode in ("alert_rb", "red_flash_n"):
-            return
-        if self._led_state_debug_enabled() and isinstance(msg, dict):
-            try:
-                print("[LED] set_led_state in={}".format(msg))
-            except Exception:
-                pass
-        self._apply_by_table(msg)
-
-    def _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 _led_log(self, tag, extra=""):
-        if not self._led_state_debug_enabled():
-            return
-        try:
-            print("[LED] {} ai={} chg={} bat={} sig={} -> mode={} period_ms={} {}".format(
-                tag, self._st_ai, self._st_charging, self._st_battery, self._st_signal,
-                self._mode, self._period_ms, extra))
-        except Exception:
-            pass
-
-    def _apply_by_table(self, _msg=None):
-        if self._mode in ("alert_rb", "red_flash_n"):
-            return
-        _prev_mode = self._mode
-        # P0:低电/空电(高于 AI 交互灯;充电中不显示,走充电灯态)
-        try:
-            if self._st_charging not in ("charging", "full"):
-                if self._st_battery == "empty":
-                    try:
-                        ms = 500
-                        if "vc_config" in globals() and vc_config:
-                            ms = int(vc_config.get_int("BATTERY_EMPTY_FLASH_PERIOD_MS", 500))
-                        self._set_red_blink(ms)
-                    except Exception:
-                        self._set_red_blink(500)
-                    return
-                if self._st_battery == "low":
-                    self._set_red_blink(1000)
-                    return
-        except Exception:
-            pass
-        # P1~P3:AI 阶段(播放 > 思考 > 聆听)
-        try:
-            if self._st_ai == "playing":
-                self._set_green_on()
-                return
-            if self._st_ai == "thinking":
-                # 蓝绿「交替」:约 0.5 s 换一色(`period_ms` 为换色间隔)
-                self._set_bg_alt(500)
-                return
-            if self._st_ai == "listening":
-                # 长对话蓝慢闪(≈1s);单对话 PTT 蓝快闪(≈2次/s,REQ-AI-07)
-                period_ms = 1000
-                try:
-                    if EventMesh.publish("get_storage_config", "VC_mode") == 1:
-                        period_ms = 250
-                except Exception:
-                    pass
-                self._set_blue_blink(period_ms)
-                return
-        except Exception:
-            pass
-        # AI 交互中(聆听/思考/播放/流播)不让充电灯抢占;交互结束 idle 后恢复充电红/充满绿(REQ 冲突规避)
-        _skip_charge_led = 0
-        try:
-            if self._st_ai in ("listening", "thinking", "playing"):
-                _skip_charge_led = 1
-        except Exception:
-            pass
-        if not _skip_charge_led:
-            try:
-                if EventMesh.publish("get_vc_stream_active") == 1:
-                    _skip_charge_led = 1
-            except Exception:
-                pass
-        # 基础态:充电/电量/信号
-        try:
-            if not _skip_charge_led:
-                if self._st_charging == "full":
-                    self._set_green_on()
-                    return
-                if self._st_charging == "charging":
-                    self._set_red_on()
-                    return
-            if self._st_signal == "weak":
-                # 弱网:蓝绿「慢交替」,与 thinking 同节拍定义(约 1 s 换一色)
-                self._set_bg_alt(1000)
-                return
-        except Exception:
-            pass
-        # 空闲待机:默认灭灯(REQ-BAT-01 / GAP-005 方案甲);LED_IDLE_MODE=green_blink 可恢复旧工程绿灯慢闪
-        try:
-            idle_mode = "off"
-            if "vc_config" in globals() and vc_config:
-                _im = vc_config.get_attr("LED_IDLE_MODE", None)
-                if _im is not None:
-                    idle_mode = str(_im).strip().lower()
-            if idle_mode in ("green_blink", "blink", "green"):
-                idle_on = 1000
-                if "vc_config" in globals() and vc_config:
-                    idle_on = int(vc_config.get_int("LED_IDLE_GREEN_BLINK_MS", 1000))
-                idle_off = idle_on
-                if "vc_config" in globals() and vc_config:
-                    _ioff = vc_config.get_attr("LED_IDLE_GREEN_BLINK_OFF_MS", None)
-                    if _ioff is not None:
-                        try:
-                            idle_off = int(_ioff)
-                        except Exception:
-                            idle_off = idle_on
-                if idle_on < 400:
-                    idle_on = 400
-                if idle_off < 400:
-                    idle_off = 400
-                self._set_green_blink(idle_on, idle_off)
-            else:
-                self._set_off()
-        except Exception:
-            self._set_off()
-        if self._mode != _prev_mode:
-            self._led_log("apply_table", "in={}".format(_msg))
-
-    def get_led_debug_state(self, event=None, msg=None):
-        # 供按键/状态机打印当前 LED 聚合状态与最终模式
-        try:
-            return {
-                "ai": self._st_ai,
-                "charging": self._st_charging,
-                "battery": self._st_battery,
-                "signal": self._st_signal,
-                "mode": self._mode,
-                "period_ms": self._period_ms,
-            }
-        except Exception:
-            return {"mode": getattr(self, "_mode", "?")}
-
-
-def _default_battery_ocv_map():
-    # 与 mgr.py BATTERY_OCV_TABLE nix_coy_mnzo2 默认一致;可被 default_config.BATTERY_OCV_MAP 覆盖
-    return {
-        4290: 100, 4237: 95, 4179: 90, 4123: 85, 4070: 80, 4020: 75, 3963: 70, 3919: 65, 3882: 60, 3847: 55,
-        3818: 50, 3794: 45, 3772: 40, 3752: 35, 3733: 30, 3716: 25, 3699: 20, 3677: 15, 3652: 10, 3629: 5, 3400: 0,
-    }
-
-
-def _parse_battery_ocv_map(raw):
-    """解析 default_config.BATTERY_OCV_MAP:dict 或 [(mv, soc), ...]。"""
-    if raw is None:
-        return None
-    out = {}
-    try:
-        if isinstance(raw, dict):
-            pairs = raw.items()
-        elif isinstance(raw, (tuple, list)):
-            pairs = ((p[0], p[1]) for p in raw if isinstance(p, (tuple, list)) and len(p) >= 2)
-        else:
-            return None
-        for k, v in pairs:
-            out[int(k)] = int(v)
-        return out if out else None
-    except Exception:
-        return None
-
-
-def _soc_from_vbatt_mv(volt_mv, ocv_map, ocv_volts, vbatt_simple=None):
-    """由电压(mV) 算 SoC(0~100);配置来自 default_config / _BatteryCfg。"""
-    try:
-        v = int(volt_mv)
-    except Exception:
-        return None
-    try:
-        if vbatt_simple:
-            _full, _v20, _v5 = vbatt_simple
-            if v >= int(_full):
-                return 100
-            if v <= int(_v5):
-                return 0
-            if v <= int(_v20):
-                return 10
-            return 60
-    except Exception:
-        pass
-    try:
-        volts = ocv_volts
-        if not volts:
-            return None
-        pre_volt = 0
-        volt_not_under = 0
-        soc1 = 0
-        soc2 = 0
-        for vv in volts:
-            if v > vv:
-                volt_not_under = 1
-                soc1 = ocv_map.get(vv, 0)
-                soc2 = ocv_map.get(pre_volt, 0)
-                break
-            else:
-                pre_volt = vv
-        if pre_volt == 0:
-            return soc1
-        if volt_not_under == 0:
-            return 0
-        return int(soc2 - (soc2 - soc1) * (pre_volt - v) // (pre_volt - vv))
-    except Exception:
-        return None
-
-
-class _BatteryCfg(object):
-    """从 vc_config(default_config) 加载电池参数,供 AI_hw_status / get_battery / AI_dev 共用。"""
-    _inst = None
-
-    def __init__(self):
-        self.ocv_map = _default_battery_ocv_map()
-        self.ocv_volts = sorted(self.ocv_map.keys(), reverse=True)
-        self.charge_full_mv = 4290
-        self.low_mv = 3699
-        self.empty_mv = 3629
-        self.low_soc = 20
-        self.empty_soc = 5
-        self.vbatt_simple = None
-        try:
-            if "vc_config" in globals() and vc_config:
-                self.charge_full_mv = int(vc_config.get_int("VBATT_CHARGE_FULL_MV", 4290))
-                self.low_mv = int(vc_config.get_int("VBATT_LOW_MV", 3699))
-                self.empty_mv = int(vc_config.get_int("VBATT_EMPTY_MV", 3629))
-                self.low_soc = int(vc_config.get_int("BATTERY_LOW_SOC_PERCENT", 30))
-                self.empty_soc = int(vc_config.get_int("BATTERY_EMPTY_SOC_PERCENT", 10))
-                _ocv_cfg = _parse_battery_ocv_map(vc_config.get_attr("BATTERY_OCV_MAP", None))
-                if _ocv_cfg:
-                    self.ocv_map = _ocv_cfg
-                    self.ocv_volts = sorted(self.ocv_map.keys(), reverse=True)
-                if vc_config.get_bool("USE_SIMPLE_VBATT_THRESH", False):
-                    self.vbatt_simple = (self.charge_full_mv, self.low_mv, self.empty_mv)
-        except Exception:
-            pass
-
-    @classmethod
-    def instance(cls):
-        if cls._inst is None:
-            cls._inst = cls()
-        return cls._inst
-
-    def soc_from_vbatt(self, volt_mv):
-        return _soc_from_vbatt_mv(volt_mv, self.ocv_map, self.ocv_volts, self.vbatt_simple)
-
-
-class AI_hw_status(Abstract):
-    # 精简硬件状态采集:仅维护 LED 状态机所需的 charging/battery/signal
-    def __init__(self):
-        # 无条件首行:确认是否进入初始化(避免被异常/注册顺序误判)
-        try:
-            print("[HW] __init__ enter")
-        except Exception:
-            pass
-        _bcfg = _BatteryCfg.instance()
-        self._bcfg = _bcfg
-        self._ocv_map = _bcfg.ocv_map
-        self._ocv_volts = _bcfg.ocv_volts
-        self._charge_full_mv = _bcfg.charge_full_mv
-        self._low_soc = _bcfg.low_soc
-        self._empty_soc = _bcfg.empty_soc
-        self._vbatt_simple = _bcfg.vbatt_simple
-        # cached state (avoid repeat publish)
-        self._chg = None
-        self._bat = None
-        self._sig = None
-        self._usb = None
-        self._charge_full_latched = 0
-        self._charge_full_streak = 0
-        self._charge_full_streak_need = 2
-        self._timer = osTimer()
-        self._period_ms = 10000
-        self._weak_csq = 10
-        self._dbg = 0
-        try:
-            if "vc_config" in globals() and vc_config:
-                self._period_ms = vc_config.get_int("HW_STATUS_PERIOD_MS", 10000) or 10000
-                self._weak_csq = vc_config.get_int("SIGNAL_WEAK_CSQ", 10) or 10
-                self._dbg = 1 if vc_config.get_bool("HW_STATUS_DEBUG", False) else 0
-        except Exception:
-            pass
-        # 若开启了 LED/按键调试,也一并打开硬件监控打印,便于关联分析
-        try:
-            if "vc_config" in globals() and vc_config:
-                if vc_config.get_bool("LED_DEBUG", False) or vc_config.get_bool("KEY_LED_DEBUG", False):
-                    self._dbg = 1
-        except Exception:
-            pass
-
-        # 注意:AI_dev 也会注册 USB callback;USB 模块通常只支持一个 callback,后注册会覆盖先注册。
-        # 为避免“刚开始有效,后面失效”,这里不直接 setCallback,改为订阅 AI_dev 转发的 USB 状态事件,
-        # 并在 10 秒轮询中再读一次 GET_USB_STATE 作兜底。
-        try:
-            EventMesh.subscribe("hw_usb_state", self._on_usb_state)
-        except Exception:
-            pass
-
-        # 初始化:先立即刷新一次,再开启周期轮询(避免 one-shot/覆盖差异导致只跑一次)
-        try:
-            self._update()
-        except Exception:
-            pass
-        try:
-            self._timer.start(self._period_ms, 1, self._tick_cb)
-        except Exception:
-            pass
-        # 初始化打印(一次)
-        try:
-            print("[HW] init ok period_ms={} weak_csq={} usb_init={} algo={}".format(
-                self._period_ms,
-                self._weak_csq,
-                self._usb,
-                "simple" if getattr(self, "_vbatt_simple", None) else "ocv"))
-            if self._dbg == 1:
-                print("[HW] init detail vbatt_simple={}".format(getattr(self, "_vbatt_simple", None)))
-        except Exception:
-            pass
-
-    def _on_usb_state(self, event=None, msg=None):
-        try:
-            state = msg
-            prev = self._usb
-            self._usb = 1 if state == 1 else 0
-            if self._usb == 1 and prev != 1:
-                self._charge_full_latched = 0
-                self._charge_full_streak = 0
-            if self._usb == 0:
-                self._charge_full_latched = 0
-                self._charge_full_streak = 0
-        except Exception:
-            pass
-        try:
-            if self._dbg == 1:
-                print("[HW] usb_evt state={} usb={}".format(state, self._usb))
-        except Exception:
-            pass
-        try:
-            self._update()
-        except Exception:
-            pass
-
-    def _tick_cb(self, *args, **kwargs):
-        # 定时器回调直接刷新(不依赖 AI_dev 的事件线程)
-        try:
-            self._tick()
-        except Exception:
-            pass
-
-    def _tick(self, *args, **kwargs):
-        self._update()
-
-    def _update(self):
-        # 每次刷新都“同步推送一次”当前硬件基础态到 LED(不依赖变化检测)
-        # 目的:保证 10 秒轮询和 USB callback 都能及时刷新充电/电量/信号的基础态显示
-        # charging/battery
-        try:
-            v = Power.getVbatt()
-        except Exception:
-            v = None
-        soc = None
-        csq = None
-        rssi = None
-        usb_status = None
-        chg = "none"
-        bat = "normal"
-        sig = "normal"
-        try:
-            usb_status = self._usb
-            if usb_status is None:
-                try:
-                    usb_status = EventMesh.publish("GET_USB_STATE")
-                    if usb_status is None:
-                        usb_status = 0
-                except Exception:
-                    usb_status = 0
-            # charging:插入后先红灯;勿用瞬时 vbatt≥满电阈值直接判满(充电器接入电压即偏高)
-            if usb_status == 1:
-                if getattr(self, "_charge_full_latched", 0) == 1:
-                    chg = "full"
-                else:
-                    if v is not None and v >= int(self._charge_full_mv):
-                        self._charge_full_streak = int(getattr(self, "_charge_full_streak", 0)) + 1
-                        need = int(getattr(self, "_charge_full_streak_need", 2) or 2)
-                        if self._charge_full_streak >= need:
-                            self._charge_full_latched = 1
-                            chg = "full"
-                        else:
-                            chg = "charging"
-                    else:
-                        self._charge_full_streak = 0
-                        chg = "charging"
-            # battery level for LED table
-            if usb_status != 1 and v is not None:
-                soc = self._bcfg.soc_from_vbatt(v)
-                if soc is not None:
-                    if soc < int(self._empty_soc):
-                        bat = "empty"
-                    elif soc < int(self._low_soc):
-                        bat = "low"
-            self._chg = chg
-            self._bat = bat
-        except Exception:
-            pass
-
-        # signal
-        try:
-            csq = net.csqQueryPoll()
-            # csqQueryPoll 返回 (rssi, ber) 或 int,按保守取 rssi
-            try:
-                if isinstance(csq, (tuple, list)) and len(csq) > 0:
-                    rssi = csq[0]
-                else:
-                    rssi = csq
-            except Exception:
-                rssi = csq
-            sig = "weak" if (rssi is not None and int(rssi) >= 0 and int(rssi) < int(self._weak_csq)) else "normal"
-            self._sig = sig
-        except Exception:
-            pass
-
-        # 统一推送一次当前基础态(一次调用带齐字段,减少来回切换)
-        try:
-            EventMesh.publish("set_led_state", {"charging": chg, "battery": bat, "signal": sig})
-        except Exception:
-            pass
-
-        # 轮询调试打印(每次 10s tick 一次):硬件采样一行 + LED 聚合状态一行内打完
-        try:
-            if self._dbg == 1:
-                led = None
-                try:
-                    led = EventMesh.publish("get_led_debug_state")
-                except Exception:
-                    pass
-                print("[HW] usb={} vbatt_mv={} soc={} chg={} bat={} csq={} rssi={} sig={} period_ms={} led={}".format(
-                    usb_status, v, soc, self._chg, self._bat, csq, rssi, self._sig, self._period_ms, led))
-        except Exception:
-            pass
-
-    def _soc_from_vbatt(self, volt_mv):
-        return self._bcfg.soc_from_vbatt(volt_mv)
-
-
-def set_ipv4_datacall():
-       # 获取第一路的APN,ipType 等信息
-       pdpCtx = dataCall.getPDPContext(1)
-       if pdpCtx != -1:
-               if pdpCtx[0] != 0:
-                       # 如果不是用户需要的ipv4,使用如下方式配置,使用如下方式配置
-                       ret = dataCall.setPDPContext(1, 0,pdpCtx[1],pdpCtx[2],pdpCtx[3],pdpCtx[4])
-                       if ret == 0:
-                               print('配置成功。')
-                               # cfun0/1后按照配置的信息进行拨号
-                               net.setModemFun(0)
-                               net.setModemFun(1)
-                       else:
-                               print('配置失败。')
-               else:
-                       print('已经配置为IPv4')
-       else:
-               print('获取PDP Context失败。')
-
-#HEARDER+TOTALLEN+DATELEN+DATA
-HEARDER = "FFFE"
-TOTALLEN = "00000000"
-DATELEN = "0000"
-DATA = {}
-class AI_UART():
-    #串口命令通道
-    def __init__(self):
-        self.send_count=0
-        self.AI_uart = UART(UART.UART4, 230400, 8, 0, 1, 0)
-        self.AI_uart.set_callback(self.uart_call)
-        
-        EventMesh.subscribe("bt_ota_run",self.bt_ota_run)#
-        EventMesh.subscribe("start_ota",self.start_ota)
-        EventMesh.subscribe("end_ota",self.end_ota)
-        EventMesh.subscribe("send_bt_switch_toble",self.send_bt_switch_toble)
-        EventMesh.subscribe("send_ble_switch_toble",self.send_ble_switch_toble)
-        EventMesh.subscribe("send_ble_scan",self.send_ble_scan)
-        EventMesh.subscribe("send_connect_bt_mac",self.send_connect_bt_mac)
-        EventMesh.subscribe("send_disconnect_bt_mac",self.send_disconnect_bt_mac)
-        EventMesh.subscribe("send_imei_to_ble",self.send_imei_to_ble)
-        EventMesh.subscribe("send_get_connect_bt_state",self.send_get_connect_bt_state)
-        EventMesh.subscribe("recv_bt_connect_rest",self.recv_bt_connect_rest)
-        EventMesh.subscribe("AI_send_bt_ble_switch_toble",self.AI_send_bt_ble_switch_toble)
-        EventMesh.subscribe("get_bt_versions",self.get_bt_versions)
-        EventMesh.subscribe("adjust_volume_toBle",self.adjust_volume_toBle)
-        EventMesh.subscribe("clear_earphone_record",self.clear_earphone_record)
-        self.bt_switch = 0
-        self.ble_switch = 0
-        self.uart_timer = osTimer()
-        self.get_bt_connect_timer = osTimer()
-        self.get_bt_connect_flag = 0
-        self.uart_timer_count = 0
-        self.bt_ota_flag = 0
-        self.read_ack_flag = 0
-        self.ping_json_str = {}
-        self.ping_json_str_tmp =""
-        self.read_uart_data = ""
-        self.bt_versions=""
-        try:
-            uart_ok = False
-            if "vc_config" in globals() and vc_config:
-                uart_ok = vc_config.uart_supported()
-        except Exception:
-            uart_ok = False
-        if uart_ok:
-            self.uart_send_queue = Queue(100)
-            self.uart_send_queue_thread_id = _thread.start_new_thread(self.uart_send_queue_thread, ())
-
-            self.uart_recv_queue = Queue(100)
-            self.uart_recv_queue_thread_id = _thread.start_new_thread(self.uart_recv_queue_thread, ())
-            self.send_get_versions()#获取蓝牙版本
-    
-    def uart_send_queue_thread(self):
-        while True:
-            data = self.uart_send_queue.get()
-            
-            #EventMesh.debug_log("Info_queue:data = {}".format(data))
-            if data != None:
-                try:
-                    if len(data) != 0:
-                        print("send_queu_uart_data2:{} data:{}".format(data,data["data"]))
-                        # json_str  =ujson.dumps(data)
-                        if data["event"] == "uart_send_data":
-                            print("send_queu_uart_data sucess data:{} {}".format(data["data"],type(data["data"])))
-                            self.uart_queue_send_data(data["data"])
-                        if "resend_flag" in data:
-                            print("send_reset_cb:{} {}".format(data["resend_flag"],data["ACK_time"]))
-                            if data["resend_flag"] == 1:
-                                self.send_count = 5#重试5次
-                                ack_timer = 0 #单次最大等待时间
-                                count = 0
-                                if "ACK_time" in data:
-                                    if data["ACK_time"] == None:
-                                        ack_timer = 100#最大重发5次,没ack或者返回error重发
-                                    else:
-                                        ack_timer = data["ACK_time"]
-                                if ack_timer > 0:
-                                    while self.send_count:
-                                        
-                                        if self.send_count <=0 or self.read_ack_flag == 1:
-                                            print("4G串口重发机制退出:{} self.read_ack_flag:{} json_str:{}".format(self.send_count,self.read_ack_flag,data["data"]))
-                                            break
-                                        if count >= ack_timer:
-                                            count = 0
-                                            print("串口异常,重发:")
-                                            self.uart_queue_send_data(data["data"])
-                                            self.send_count-=1
-                                        count += 1
-                                        utime.sleep_ms(1)
-
-                except Exception as e:
-                    usys.print_exception(e)
-                utime.sleep_ms(10)
-    
-    def uart_recv_queue_thread(self):
-        while True:
-            data = self.uart_recv_queue.get()
-            
-            #EventMesh.debug_log("Info_queue:data = {}".format(data))
-            if data != None:
-                try:
-                    if len(data) != 0:
-                        print("recv_queu_uart_data2:{}".format(data))
-                        # json_str  =ujson.dumps(data)
-                        # queue_data={"event":"uart_recv_data"}
-                        if data["event"] == "uart_recv_data":
-                            print("recv_queu_uart_data sucess data:{}".format(type(data["event"])))
-                            self.pares_uart_data()
-                except Exception as e:
-                    usys.print_exception(e)
-
-    def send_imei_to_ble(self,event,msg):
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.send_imei_to_ble_cb})
-
-    def send_imei_to_ble_cb(self):
-        self.read_ack_flag=0
-        imei  = modem.getDevImei()
-        json_data = \
-        {
-            
-            "event": "imei",
-            "data":str(imei)
-           
-        }
-        json_str  =ujson.dumps(json_data)
-        print("send imei:{}".format(json_str))
-        self.send_data(json_str)
-        pass
-    def send_connect_bt_mac(self,event,msg):
-        self.read_ack_flag=0
-        json_data = \
-        {
-            "event": "connect_bt",
-            "data":msg
-        }
-        json_str =ujson.dumps(json_data)
-        self.send_data(json_str,1,2000)
-        # self.send_reset_cb(json_str,0,2000) #每2s发送一次 10ms为单位
-        pass
-    def send_disconnect_bt_mac(self,event,msg):
-        self.read_ack_flag=0
-        json_data = \
-        {
-            "event": "disconnect_bt",
-            "data":msg
-        }
-        json_str =ujson.dumps(json_data)
-        self.send_data(json_str,1)
-        # self.send_reset_cb(json_str)
-
-        pass
-    def uart_timer_cb(self, *args, **kwargs):
-        self.uart_timer_count+=1
-        if self.uart_timer_count >40:
-            self.read_uart_data = ""
-        if self.uart_timer_count >200:
-            self.read_uart_data = ""
-            self.uart_timer.stop()
-    def split_combined_json(self,combined_str):
-        # 将连续的JSON字符串分割为多个独立的JSON字符串列表
-        #
-        # 参数:
-        #     combined_str: 多个JSON拼接的字符串
-        # 返回:
-        #     分割后的
-        json_list = []
-        start_index = 0
-        brace_count = 0  # 用于匹配{}括号
-        print("combined_str:{}".format(combined_str))
-        for i, char in enumerate(combined_str):
-            if char == '{':
-                brace_count += 1
-            elif char == '}':
-                brace_count -= 1
-                
-                # 当括号完全匹配(count=0)时,说明一个完整的JSON对象结束
-                if brace_count == 0:
-                    # 提取从start_index到当前位置的子串(包含当前})
-                    json_str = combined_str[start_index:i+1].strip()
-                    if json_str:  # 避免空字符串
-                        json_list.append(json_str)
-                    start_index = i + 1  # 下一个JSON的起始位置
-        print("json_list{}".format(json_list))
-        
-        return json_list
-    
-    def uart_call(self,para):
-        queue_data={"event":"uart_recv_data"}
-
-        self.uart_recv_queue.put(queue_data)
-        # print("uart_call start")
-        # EventMesh.publish("publish_AI_btnInfo",{"func":self.pares_uart_data})
-        # print("uart_call end")
-        #print("ai_uart:{}".format(para))  
-    #[{"event":""},{},{}]
-    def pares_uart_data(self):  
-        uart_data = self.AI_uart.read()
-        self.uart_timer.start(10, 1, self.uart_timer_cb)
-        self.uart_timer_count = 0
-        ack_flag = 0#是否要返回ack
-        print("uart_data:{}",uart_data)
-        uart_data_str = uart_data.decode('utf-8')
-    
-        self.read_uart_data = self.read_uart_data +uart_data_str
-        try:
-            ujson.loads(self.read_uart_data)
-        except Exception as e:
-            print("json数据不完整:self.read_uart_data{}".format(self.read_uart_data))
-            usys.print_exception(e)
-            return
-        self.uart_timer.stop()
-        self.uart_timer_count = 0
-        self.playload_data(self.read_uart_data)
-
-        # 分割JSON
-        # json_strings = self.split_combined_json(uart_data_str)
-        
-        # # 逐个解析并处理
-        # for i, json_str in enumerate(json_strings, 1):
-        #     print("第{}个JSON{}".format(i,json_str))
-        #     try:
-        #         data = ujson.loads(json_str)
-             
-        #         # 在这里添加你的业务逻辑,比如根据event处理不同事件
-        #         self.uart_timer.stop()
-        #         self.uart_timer_count = 0
-        #         self.playload_data(self.read_uart_data)
-        #         # if data.get("event") == "bt_connect_single":
-                    
-        #         # elif data.get("event") == "bt_scan_rest":
-                    
-        #     except Exception as e:
-        #         print("JSON解析错误:原始字符串:")
-        #     print("-" * 50)
-
-        
-        
-        # print("uart_data_str:{}",uart_data_str)
-        # if len(uart_data_str) < (HEARDER+1+1):
-        #     return
-        # head = uart_data_str[0:4]
-        # pack_flag = uart_data_str[4:5]
-        # recvflag = uart_data_str[5:6]
-        
-        
-        # if head == HEARDER:
-        #     if pack_flag == "1": #分包数据
-        #         if recvflag == "1": #未发送完成
-        #             self.ping_json_str_tmp+=uart_data_str[6:]
-        #             pass
-        #         else:
-        #             self.playload_data(self.ping_json_str_tmp)
-        #             pass
-        #     else:
-        #         self.playload_data(uart_data_str[6:])
-                    
-        # else:
-        #    print("uart is not FFFE:") 
-
-    def playload_data(self,msg):
-        ack_flag=0
-        try:
-            print("playload_data:{}".format(msg))
-            jsload_data =  ujson.loads(msg)
-        #print("AI_client_VC_recv jsload_data = {}".format(jsload_data))
-        
-            if jsload_data["event"] == "AI_KEY_PRESS" :#AI按键按下
-                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_key_press})  
-                ack_flag = 1
-            elif jsload_data["event"] == "BT_AI_KEY_PRESS" :#蓝牙耳机key ai对话模式不支持长按对话模式 只支持短按按键对话模式 重新定义按键
-                EventMesh.publish("publish_AI_btnInfo",{"func":self.BT_AI_key_press})  
-                ack_flag = 1
-            elif jsload_data["event"] == "AI_PWR_PRESS" :#电源按键按下
-                EventMesh.publish("publish_AI_btnInfo",{"func":self.pwk_key_release})  
-                ack_flag = 1
-            elif jsload_data["event"] == "bt_scan_rest":#扫描列表 #蓝牙停止扫描后主动向4g发送扫描列表需要发送ack
-                EventMesh.publish("send_bt_scan_list",jsload_data)
-                ack_flag=1
-            elif jsload_data["event"]=="bt_connect_single": #收到蓝牙主动发送过来的单个连接蓝牙耳机的连接状态 需要发送ack
-                EventMesh.publish("save_bt_connect_list",jsload_data)
-                ack_flag=1
-                
-            elif jsload_data["event"]=="need_send_bt_connect_list": #发送连接蓝牙连接记录列表 不需要发送ack
-                self.get_bt_connect_timer.stop()
-                self.get_bt_connect_flag = 0
-                EventMesh.publish("save_bt_connect_list",jsload_data) #先保存连接记录再发送连接耳机列表to IOT
-                EventMesh.publish("send_bt_connect_list")
-               
-            elif jsload_data["event"]=="get_ble_switch":
-                ble_switch_flag =  EventMesh.publish("get_storage_config","switch_ble_state")#ble开关 不需要发送ack
-                if ble_switch_flag == None:
-                    ble_switch_flag = "1"
-                EventMesh.publish("send_ble_switch_toble",str(ble_switch_flag))
-                
-                pass
-            elif jsload_data["event"]=="get_bt_switch":
-                bt_switch_flag =  EventMesh.publish("get_storage_config","switch_bt_state")#bt开关 不需要发送ack
-                if bt_switch_flag == None:
-                    bt_switch_flag = "1"
-                EventMesh.publish("send_bt_switch_toble",bt_switch_flag)
-                
-                pass
-            elif jsload_data["event"]=="get_cat1_imei": #蓝牙主动向4G获取imei  4g不需要发送ack 蓝牙收到imei需要回ack给4改
-                EventMesh.publish("send_imei_to_ble")
-                ack_flag=1
-                pass
-            elif jsload_data["event"] == "post_bt_connect_rest": #蓝牙主动蓝牙耳机连接状态 需要发送ack给蓝牙
-                EventMesh.publish("recv_bt_connect_rest",jsload_data)
-                ack_flag=1
-
-            elif jsload_data["event"] == "clear_earphone_record_ok":#清除蓝牙耳机连接记录
-                tts_play_str = "删除成功"
-                EventMesh.publish("play_tts",[tts_play_str,1])
-                self.read_ack_flag = 1
-            
-            elif jsload_data["event"] == "versions":
-                self.bt_versions = jsload_data["data"]
-                print("当前蓝牙版本是:{}".format(self.bt_versions))
-                self.read_ack_flag = 1
-
-            # elif jsload_data["event"] == "kws" :#
-            #     EventMesh.publish("kws_even")
-            elif jsload_data["event"] == "ack" :#
-                self.read_ack_flag = 1
-            if ack_flag == 1:
-                self.send_ack()
-            self.read_uart_data = ""
-        except Exception as e:
-            usys.print_exception(e)
-            self.read_uart_data = ""
-    def recv_bt_connect_rest(self,event,msg):
-        date_tmp = utime.localtime()
-        time_tmp = utime.mktime(date_tmp)
-        if "data" in msg:
-            EventMesh.publish("send_bt_connect_state_to_iot",msg)
-            pass
-    def get_bt_versions(self,event=None,msg=None):
-        return self.bt_versions
-    def clear_earphone_record(self,event,msg): #清楚所有耳机记录
-
-        json_data = \
-        {
-            "event": "clear_earphone_record",
-        }
-        json_str  =ujson.dumps(json_data)
-        print("clear_earphone_record{}".format(json_str))
-        self.send_data(json_str,1)
-    def adjust_volume_toBle(self,event,msg): #
-        self.read_ack_flag=0
-        volume = int(msg)
-        if volume <= 10:
-            volume = 10
-        json_data = \
-        {
-            "event": "adjust_volume",
-            "data":str(volume)
-        }
-        json_str  =ujson.dumps(json_data)
-        print("adjust_volume_toBle{}".format(json_str))
-        self.send_data(json_str,1)
-
-    def AI_send_bt_ble_switch_toble(self,event,msg):
-        # // {
-        #     //     "event":"switch_bt_state",
-        #     //     "data":{
-        #     //         "switch_bt_state":"0"
-        #     //     }
-        #     // } #格式
-       
-        self.read_ack_flag=0
-        if msg == 1: #AI对话开启蓝牙
-            json_data = \
-            {
-                
-                "event": "switch_ble_state",
-                "data":{
-                    "switch_ble_state":"1"
-                }   
-            
-            }
-            json_str  =ujson.dumps(json_data)
-            print("switch_ble_state{}".format(json_str))
-            self.send_data(json_str,1)
-
-        elif  msg == 2: #AI对话开启蓝牙
-            json_data = \
-            {
-                
-                "event": "switch_ble_state",
-                "data":{
-                    "switch_ble_state":"0"
-                }   
-            
-            }
-            json_str  =ujson.dumps(json_data)
-            print("switch_ble_state{}".format(json_str))
-            self.send_data(json_str,1)
-        elif  msg == 3: #AI对话开启蓝牙
-            json_data = \
-            {
-                
-                "event": "switch_bt_state",
-                "data":{
-                    "switch_bt_state":"1"
-                }   
-            }
-            json_str  =ujson.dumps(json_data)
-            print("switch_bt_state{}".format(json_str))
-            self.send_data(json_str,1)
-
-        elif  msg == 4: #AI对话开启蓝牙
-            json_data = \
-            {
-                
-                "event": "switch_bt_state",
-                "data":{
-                    "switch_bt_state":"0"
-                }   
-            
-            }
-            json_str  =ujson.dumps(json_data)
-            print("switch_bt_state{}".format(json_str))
-            self.send_data(json_str,1)
-
-    def send_bt_switch_toble(self,event,msg):
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.send_bt_switch_toble_cb,"data":msg})
-    def send_bt_switch_toble_cb(self,data):
-        # // {
-        #     //     "event":"switch_bt_state",
-        #     //     "data":{
-        #     //         "switch_bt_state":"0"
-        #     //     }
-        #     // } #格式
-        self.read_ack_flag=0
-        json_data = \
-        {
-            
-            "event": "switch_bt_state",
-            "data":{
-                "switch_bt_state":str(data)
-            }   
-           
-        }
-        json_str  =ujson.dumps(json_data)
-        print("send_bt_switch_toble{}".format(json_str))
-        self.send_data(json_str,1)
-
-    def send_ble_switch_toble(self,event,msg):
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.send_ble_switch_toble_cb,"data":msg})
-    def send_ble_switch_toble_cb(self,data):
-        # // {
-        #     //     "event":"switch_ble_state",
-        #     //     "data":{
-        #     //         "switch_ble_state":"0"
-        #     //     }
-        #     // } #格式
-    
-        self.read_ack_flag=0
-        json_data = \
-        {
-            "event": "switch_ble_state",
-            "data":{
-                "switch_ble_state":str(data)
-            }   
-        }
-        json_str  =ujson.dumps(json_data)
-        print("send_ble_switch_toble{}".format(json_str))
-        self.send_data(json_str,1)    
-
-    # 重发机制
-    def send_reset_cb(self,json_str,need_resend = None,timeOut=None):
-        print("send_reset_cb:{} {} {}".format(json_str,need_resend,timeOut))
-        if timeOut == None:
-            self.send_count = 100#最大重发5次,没ack或者返回error重发
-        else:
-            self.send_count = timeOut
-
-        while self.send_count:
-            self.send_count-=1
-            utime.sleep_ms(10)
-            if self.send_count <=0 or self.read_ack_flag == 1:
-                print("4G串口重发机制退出:{} self.read_ack_flag:{} json_str:{}".format(self.send_count,self.read_ack_flag,json_str))
-                break
-            if self.send_count%(self.send_count//10) == 0:
-                print("串口异常,重发:")
-                self.send_data(json_str)
-
-        pass
-    def send_get_connect_bt_state(self,event,msg):
-        json_data = \
-        {
-            
-            "event": "get_connect_bt_state"
-        }
-        json_str  =ujson.dumps(json_data)
-        print("send_get_connect_bt_state{}".format(json_str))
-        self.send_data(json_str)
-        self.get_bt_connect_flag = 1
-        self.get_bt_connect_timer.start(2000,0,self.get_bt_connect_timer_cb)
-    def get_bt_connect_timer_cb(self,*agrc):
-        if self.get_bt_connect_flag == 1:
-            EventMesh.publish("send_bt_connect_list")
-            pass
-        self.get_bt_connect_flag = 0
-        pass
-    def send_ble_scan(self,event,msg):
-        self.read_ack_flag=0
-        # // {
-        #     //     "event":"send_ble_scan",
-        #     //     "data":{
-        #     //         "send_ble_scan":"0"
-        #     //     }
-        #     // } #格式
-        json_data = \
-        {
-            
-            "event": "bt_scan",
-            "data":msg 
-           
-        }
-        json_str  =ujson.dumps(json_data)
-        print("send_ble_scan{}".format(json_str))
-        self.send_data(json_str,1)
-        # self.send_reset_cb(json_str)
-
-        print("bt_send_end")
-    def send_data(self,str_data,need_resend = None,ACK_time =None):
-        try:
-            if not ("vc_config" in globals() and vc_config and vc_config.uart_supported()):
-                return
-        except Exception:
-            return
-        if self.bt_ota_flag == 1:
-            print("ota 中不需要上传")
-            return
-        print("send_data{} {} {}".format(str_data,need_resend,ACK_time))
-        queue_data={"event":"uart_send_data","data":str_data}
-        if need_resend != None:
-            queue_data={"event":"uart_send_data","data":str_data,"resend_flag":1,"ACK_time":ACK_time}
-
-        self.uart_send_queue.put(queue_data)
-    def uart_queue_send_data(self,str_data):
-        if self.bt_ota_flag == 1:
-            return
-        #EventMesh.publish("wakeup_BT")
-        print("bt_seuart_queue_send_data start {}".format(str_data))
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
-        self.AI_uart.write(str_data)
-        print("bt_seuart_queue_send_data end {}".format(str_data))
-    def led_r_open(self,event=None,msg=None):
-        json_data = \
-        {
-            
-            "event": "led_r_open",
-                
-           
-        }
-        json_str  =ujson.dumps(json_data)
-        print("led_r_open{}".format(json_str))
-        self.send_data(json_str)
-    def led_g_open(self,event=None,msg=None):
-        json_data = \
-        {
-
-            "event": "led_g_open",
-
-        }
-        json_str  =ujson.dumps(json_data)
-        print("led_g_open{}".format(json_str))
-        self.send_data(json_str)
-    def led_b_open(self,event=None,msg=None):
-        json_data = \
-        {
-
-            "event": "led_b_open",
-   
-        }
-        json_str  =ujson.dumps(json_data)
-        print("led_b_open{}".format(json_str))
-        self.send_data(json_str)
-    def led_rgb_open(self,event=None,msg=None):
-        json_data = \
-        {
-
-            "event": "led_rgb_open",
-
-        }
-        json_str  =ujson.dumps(json_data)
-        print("led_rgb_open{}".format(json_str))
-        self.send_data(json_str)
-    def led_close(self,event=None,msg=None):
-        json_data = \
-        {
-
-            "event": "led_close",
-
-        }
-        json_str  =ujson.dumps(json_data)
-        print("led_close{}".format(json_str))
-        self.send_data(json_str)
-        
-    def send_ack(self):
-        json_data = \
-        {
-
-            "event": "ack",
-
-        }
-        json_str  =ujson.dumps(json_data)
-        print("send_ack{}".format(json_str))
-        self.send_data(json_str)
-    def AI_key_press(self):
-        self.check_rest_timer_count = 0
-        print("AI_key_press")
-        EventMesh.publish("AI_key_press")
-    def BT_AI_key_press(self):
-        self.check_rest_timer_count = 0
-        print("BT_AI_key_press")
-        EventMesh.publish("BT_AI_key_press")
-
-    def pwk_key_release(self):
-        self.check_rest_timer_count = 0
-        #print("pwk_key_release")
-        EventMesh.publish("pwk_key_release")
-    def send_get_versions(self,event=None,msg=None):
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
-        #self.read_ack_flag = 0
-        json_data = \
-        {
-            
-            "event": "get_versions",
-        }
-        json_str  =ujson.dumps(json_data)
-        print("send_get_versions {}".format(json_str))
-        self.send_data(json_str,1)
-        #self.AI_uart.write(json_str)
-        count =1000
-        while True:
-            count -=1
-            if count <=0 or self.read_ack_flag == 1:
-                break
-            utime.sleep_ms(1)
-
-        pass
-    def start_ota(self,event=None,msg=None):#通过串口给蓝牙升级
-        #EventMesh.publish("wakeup_BT") 
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
-        print("start_ota!!!")
-        print("start_ota!!!")
-        json_data = \
-        {
-            "event": "ota_start",
-        }
-        json_str  =ujson.dumps(json_data)
-        self.AI_uart.write(json_str)
-        print(json_str)
-        count =100
-        while True:
-            count -=1
-            if count <=0 or self.read_ack_flag == 1:
-                break
-            utime.sleep_ms(1)
-        print("start_ota count:{}!!!".format(count))
-
-    def end_ota(self,event=None,msg=None):#通过串口给蓝牙升级
-        #EventMesh.publish("wakeup_BT") 
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
-        print("end_ota!!!")
-        print("end_ota!!!")
-        json_data = \
-        {
-            "event": "ota_end",
-        }
-        json_str  =ujson.dumps(json_data)
-        self.AI_uart.write(json_str)
-        print(json_str)
-        count =100
-        while True:
-            count -=1
-            if count <=0 or self.read_ack_flag == 1:
-                break
-            utime.sleep_ms(1)
-        print("end_ota count:{}!!!".format(count))
-
-    def bt_ota_run(self,event=None,msg=None):#通过串口给蓝牙升级
-        # EventMesh.publish("wakeup_BT") 
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
-        print("bt_ota_run!!!:{}".format(msg))
-        print("bt_ota_run!!!")
-        print("bt_ota_run!!!")
-        self.bt_ota_flag = 1
-        self.read_ack_flag = 0
-        
-        json_data = \
-        {
-            "event": "ota_start",
-        }
-        json_str  =ujson.dumps(json_data)
-        self.AI_uart.write(json_str)
-        print(json_str)
-        count =2000#等20秒,擦除flash
-        while True:
-            count -=1
-            if count <=0 or self.read_ack_flag == 1:
-                break
-            utime.sleep_ms(10)
-            EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
-        print("ota_start:count:{}".format(count))
-        url = msg[0]
-        bt_size = msg[1]
-        headers_T = {"Content-Type":"application/octet-stream"}
-        response = request.get(url = url,headers=headers_T,timeout = 120,decode = False)
-        offset = 0
-        checksum = 0
-        for i in response.content:
-            EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
-            
-            
-            data = base64.b64encode(i)
-            send_count = 5#最大重发5次,没ack或者返回error重发
-            while 1:
-                send_count-=1
-                self.read_ack_flag = 0
-                checksum = 0
-                # 对每个字节进行异或运算
-                for byte in i:
-                    checksum ^= byte  # 等价于 checksum = checksum ^ byte
-                json_data = \
-                {
-                    "event": "ota_data",
-                    "update":data,
-                    "offset":offset,
-                    "checksum":checksum 
-                }
-                print("offset:{},checksum:{}".format(offset,checksum))
-                json_str  =ujson.dumps(json_data)
-                self.AI_uart.write(json_str)
-                #utime.sleep_ms(100)
-                count =300
-                while True:
-                    count -=1
-                    if count <=0 or self.read_ack_flag != 0 :
-                        break
-                    utime.sleep_ms(10)
-                print("ota_data:{} count:{}".format(len(json_str),count))
-                if  self.read_ack_flag == 1:
-                    break
-                if send_count <=0 :
-                    return -1
-            offset+=len(i)
-        if offset < bt_size:
-            return -1
-        self.read_ack_flag = 0
-        json_data = \
-        {
-            "event": "ota_end",
-        }
-        json_str  =ujson.dumps(json_data)
-        self.AI_uart.write(json_str)
-        
-        count =100
-        while True:
-            count -=1
-            if count <=0 or self.read_ack_flag == 1:
-                break
-            utime.sleep_ms(1)
-        self.bt_ota_flag = 0
-        return 0
-audio_buf = ['chaobingfatis', 'chenggong', 'didianguanji', 'IMEIhao', 'jianquancuowu', 'key_prompt', 'liuliangtishi', 'Low_power_warning', 'R_CHARGE_FULL', 'R_CHARGE_IN', 'R_CHARGE_OUT', 'R_READ_TIME_0', 'R_READ_TIME_1', 'R_READ_TIME_2', 'R_READ_TIME_3', 'R_READ_TIME_4', 'R_READ_TIME_5', 'R_READ_TIME_6', 'R_READ_TIME_7', 'R_READ_TIME_8', 'R_READ_TIME_9', 'R_READ_TIME_A', 'R_READ_TIME_B', 'R_READ_TIME_C', 'R_READ_TIME_D', 'R_READ_TIME_E', 'R_READ_TIME_F', 'R_READ_TIME_G', 'R_READ_TIME_H', 'R_READ_TIME_I', 'R_READ_TIME_J', 'R_READ_TIME_K', 'R_READ_TIME_L', 'R_READ_TIME_M', 'R_READ_TIME_N', 'R_READ_TIME_O', 'R_READ_TIME_P', 'R_READ_TIME_Q', 'R_READ_TIME_R', 'R_READ_TIME_S', 'R_READ_TIME_T', 'R_READ_TIME_U', 'R_READ_TIME_V', 'R_READ_TIME_W', 'R_READ_TIME_X', 'R_READ_TIME_Y', 'R_READ_TIME_Z', 'send_tone', 'shouhou', 'xinhaoruo1', 'zhengzaiguanji']
-class AI_dev(Abstract):
-    # AI
-    def __init__(self):
-        # if(dev_type != "7PIN"):
-        #     set_ipv4_datacall()
-        self.is_wdt = 0 #是否开启看门狗
-        #AT+QDAI=1,0,0,1,0,0,1,1PCM音频输出设置为主模式、短同步、16 kHz采样率和256 kHz时钟频率,采样位深为16bit,使用的时间间隙数量为1,并且使用第一个时间间隙。
-        try:
-            bt_pm_en = False
-            pin_bt_on = None
-            pin_mot = None
-            qdai_cfg = None
-            if "vc_config" in globals() and vc_config:
-                bt_pm_en = bool(vc_config.get_attr("BT_POWER_MOTOR_ENABLED", False))
-                pin_bt_on = vc_config.get_attr("PIN_BT_ON_GPIO", None)
-                pin_mot = vc_config.get_attr("PIN_MOT_GPIO", None)
-                qdai_cfg = vc_config.get_attr("AUDIO_QDAI_CFG", None)
-        except Exception:
-            bt_pm_en = False
-            pin_bt_on = None
-            pin_mot = None
-            qdai_cfg = None
-
-        if bt_pm_en:
-            try:
-                if pin_bt_on:
-                    self.PIN_BT_ON = Pin(getattr(Pin, pin_bt_on), Pin.OUT, Pin.PULL_DISABLE, 0)
-                if pin_mot:
-                    self.PIN_MOT = Pin(getattr(Pin, pin_mot), Pin.OUT, Pin.PULL_DISABLE, 1)
-            except Exception:
-                pass
-            try:
-                utime.sleep_ms(200)
-                if hasattr(self, "PIN_MOT"):
-                    self.PIN_MOT.write(0)
-                if hasattr(self, "PIN_BT_ON"):
-                    self.PIN_BT_ON.write(1)
-            except Exception:
-                pass
-            try:
-                if isinstance(qdai_cfg, (tuple, list)) and len(qdai_cfg) == 8:
-                    audio.set_qdai_cfg(*qdai_cfg)  # 用iis做音频输入输出
-            except Exception:
-                pass
-        self.KeyAI_status = 0
-        self.KeyAI_timer_count = 0
-        self.KeyAI_long_press = 0
-        self.KeySos_long_press = 0
-        self.KeyAI_press = 0
-        self.last_ai_key_time = 0#让ai按键不要频繁调用
-        if self.is_wdt == 1:
-            self.wdt = WDT(30) #开启看门狗
-        # self.wdt_timer = osTimer()
-        # self.wdt_timer.start(5000, 1, self.wdt_timer_cb)
-        
-        count = 0
-        while True:
-            count += 1
-            if count > 20 :
-                break
-            data = dataCall.getInfo(1, 0)
-            print("data call INFO {}".format(data))
-            if data[2][0]:
-                self.network_state = 1
-                break
-            print("等待网络 {}...".format(count))
-            utime.sleep_ms(500)
-        self.KeyAI_timer = osTimer()
-        self.KeySOS_timer = osTimer()
-        self.play_imei_timer = osTimer()
-        self.play_imei_timer_count=0
-        self.KeySOS_timer_count = 0
-        self.KeySOS_timer_flag = 0
-        try:
-            ai_gpio = "GPIO22"
-            pwk_gpio = "GPIO26"
-            bt_wakeup_en = False
-            wakeup_cta1_gpio = None
-            wakeup_bt_gpio = None
-            if "vc_config" in globals() and vc_config:
-                ai_gpio = vc_config.get_attr("EXTINT_AI_GPIO", "GPIO22") or "GPIO22"
-                pwk_gpio = vc_config.get_attr("PWK_KEY_GPIO", "GPIO26") or "GPIO26"
-                bt_wakeup_en = bool(vc_config.get_attr("BT_WAKEUP_4G_ENABLED", False))
-                wakeup_cta1_gpio = vc_config.get_attr("EXTINT_WAKEUP_CTA1_GPIO", None)
-                wakeup_bt_gpio = vc_config.get_attr("PIN_WAKEUP_BT_GPIO", None)
-        except Exception:
-            ai_gpio = "GPIO22"
-            pwk_gpio = "GPIO26"
-            bt_wakeup_en = False
-            wakeup_cta1_gpio = None
-            wakeup_bt_gpio = None
-
-        self.extint_AI = ExtInt(getattr(ExtInt, ai_gpio), ExtInt.IRQ_RISING_FALLING, ExtInt.PULL_PU, self.keyAI_extint_cb)
-        self.pwk_key = ExtInt(getattr(ExtInt, pwk_gpio), ExtInt.IRQ_RISING_FALLING, ExtInt.PULL_PU, self.pwk_extint_cb)
-
-        if bt_wakeup_en:
-            try:
-                if wakeup_cta1_gpio:
-                    self.extint_wakeup_cta1 = ExtInt(getattr(ExtInt, wakeup_cta1_gpio), ExtInt.IRQ_RISING, ExtInt.PULL_DISABLE, self.wakeup_extint_cb)
-                if wakeup_bt_gpio:
-                    self.PIN_wakeup_bt = Pin(getattr(Pin, wakeup_bt_gpio), Pin.OUT, Pin.PULL_DISABLE, 0)
-                self.extint_wakeup_cta1_timer = osTimer()
-                if hasattr(self, "extint_wakeup_cta1"):
-                    self.extint_wakeup_cta1.enable()
-            except Exception:
-                pass
-
-        self.extint_AI.enable()
-        self.pwk_key_status = 0
-        self.pwk_key_timer_count = 0
-        self.pwk_key_long_press = 0
-        self.pwk_key_press = 0
-        self.pwk_key_timer = osTimer()
-        self.pwk_is_double_timer = osTimer()
-        self.double_click_threshold = 1000#双击间隔时间
-        self.pwk_press_count = 0#按下次数
-        self.last_key_time = 0          # 上一次有效触发时间
-        self.key_debounce_interval = 20 # 防抖窗口20ms,标准值
-        #self.pwk_key = ExtInt(ExtInt.GPIO26, ExtInt.IRQ_RISING_FALLING, ExtInt.PULL_DISABLE, self.pwk_extint_cb)
-        
-        self.pwk_key.enable() 
-        print("AI_dev:1")
-        self.btnInfo_queue = Queue(100)
-        self.btnInfo_queue_count = 0
-        _thread.stack_size(16*1024)
-        self.btnInfo_queue_thread_id = _thread.start_new_thread(self.btnInfo_queue_thread, ())
-        _thread.stack_size(8*1024)
-        EventMesh.subscribe("publish_AI_btnInfo",self.publish_AI_btnInfo)
-        EventMesh.subscribe("get_battery",self.get_battery)
-        EventMesh.subscribe("audio_montage_play",self.audio_montage_play)
-        EventMesh.subscribe("paly_iccid_imei",self.paly_iccid_imei)
-        EventMesh.subscribe("paly_device_res_error",self.paly_device_res_error)
-        EventMesh.subscribe("Power_off",self.pwk_key_Long_Press)
-        EventMesh.subscribe("wakeup_BT",self.wakeup_BT)
-        EventMesh.subscribe("sleep_BT",self.sleep_BT)
-        EventMesh.subscribe("sys_sleep_cb",self.sys_sleep_cb)#系统休眠前回调
-        EventMesh.subscribe("sys_up_cb",self.sys_up_cb)#系统唤醒后回调
-        
-        EventMesh.subscribe("GET_USB_STATE",self.get_usb_state)
-        EventMesh.subscribe("SET_SPEECH_RATE",self.set_speech_rate)
-        EventMesh.subscribe("rest_TB",self.rest_TB)
-        
-        print("AI_dev:2")
-        self.pk = PowerKey()
-        self.pk.powerKeyEventRegister(self.powerKey_callback)
-        self.usb = USB()
-        self.usb.setCallback(self.usb_event_cb)
-        self.usb_status = self.usb.getStatus()
-        self.usb_charge_flag = 0
-        self._usb_evt_last_ms = 0
-        self._usb_evt_last_state = -1
-        self.Low_power_warning_timer = 0
-        self.AI_dev_timer = osTimer()
-        self.AI_dev_timer.start(10000, 1, self.AI_dev_timer_cb)
-        print("AI_dev:AI_dev_timer")
-        self.aud = audio.Audio(0)
-        self.Low_power_warning_flag = 0
-        self._low_warn_last_ms = 0
-        self._empty_shutdown_done = 0
-        self.VC_TTS_volume =  EventMesh.publish("get_storage_config","VC_TTS_volume")#音量配置
-        if self.VC_TTS_volume == None:
-            self.VC_TTS_volume = 100
-        self.aud.setVolume(self.VC_TTS_volume //10)
-        EventMesh.publish("set_storage_config",["VC_TTS_volume",self.VC_TTS_volume])
-        #self.aud.play(1,0,'U:/static/xianyikaiji.mp3')#开机音频
-        #EventMesh.publish("audio_montage_play",["xianyikaiji.mp3"])
-        #EventMesh.publish("publish_AI_btnInfo",{"func":self.ntptime_cb})
-        self.check_rest_timer_count = 0
-        try:
-            if "vc_config" in globals() and vc_config and vc_config.uart_supported():
-                self.ai_uart = AI_UART()
-        except Exception:
-            pass
-
-        self.Keycall1_press_count = 0
-        self.ble_switch_flag_time = osTimer()
-        self.ble_switch_flag_time.start(20000, 0, self.ble_switch_flag_time_init)
-        # AI_hw_status 先于本模块初始化,开机已插 USB 时需补同步一次
-        try:
-            if int(self.usb_status or 0) == 1:
-                EventMesh.publish("hw_usb_state", 1)
-                EventMesh.publish("publish_AI_btnInfo", {"func": self.usb_event, "data": 1})
-        except Exception:
-            pass
-    def ble_switch_flag_time_init(self,*args):
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.ble_switch_flag_time_cb})
-    def ble_switch_flag_time_cb(self,*args):
-        bt_switch_flag =  EventMesh.publish("get_storage_config","switch_bt_state")#bt开关
-        if bt_switch_flag == None:
-            bt_switch_flag = "1"
-        EventMesh.publish("send_bt_switch_toble",str(bt_switch_flag))
-        # EventMesh.publish("send_imei_to_ble")
-        ble_switch_flag =  EventMesh.publish("get_storage_config","switch_ble_state")#ble开关
-        if ble_switch_flag == None:
-            ble_switch_flag = "1"
-        EventMesh.publish("send_ble_switch_toble",str(ble_switch_flag))
-        vc_tts_volume = EventMesh.publish("get_storage_config","VC_TTS_volume")
-        if vc_tts_volume == None:
-            vc_tts_volume=100
-        # EventMesh.publish("adjust_volume_toBle",str(vc_tts_volume))
-
-        pass
-        print("ble_switch_flag_time_cb!!!!!!!")
-    def ntptime_cb(self):
-        #ntptime.settime(8)
-        print("timer:{}!!!!!!!!\r\n".format(utime.localtime()))
-    def usb_event_cb(self, state):
-        # USB 插拔抖动过滤(日志中 powerKey 与 usb_evt 0/1 连跳会导致误播 OUT/IN)
-        try:
-            now = utime.ticks_ms()
-            last_ms = getattr(self, "_usb_evt_last_ms", 0)
-            last_st = getattr(self, "_usb_evt_last_state", -1)
-            if last_ms and utime.ticks_diff(now, last_ms) < 800 and last_st != state:
-                return
-            self._usb_evt_last_ms = now
-            self._usb_evt_last_state = state
-        except Exception:
-            pass
-        # 转发给精简硬件监控(AI_hw_status),避免 USB callback 被覆盖导致“后面失效”
-        try:
-            EventMesh.publish("hw_usb_state", state)
-        except Exception:
-            pass
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.usb_event,"data":state})
-
-    def usb_event(self, state):    
-        status = state
-        self.usb_charge_flag = 0
-        if status == 1:#插入USB
-            print("charge R_CHARGE_in")
-            self.usb_status = 1
-            VC_satrt_flag = EventMesh.publish("get_VC_satrt_flag")#获取vc是否在运行
-            if VC_satrt_flag == None:
-                VC_satrt_flag = 0
-            
-            voice_record_satrt_flag = EventMesh.publish("get_voice_record_flag")#获取静默或者语音留言是否在运行
-            if voice_record_satrt_flag == None:
-                voice_record_satrt_flag = 0
-            if VC_satrt_flag == 0 and voice_record_satrt_flag == 0:
-                self.aud.stop()
-                #EventMesh.publish("flicker_led_red")#充电闪灯
-                #self.aud.play(1,1,'U:/static/R_CHARGE_IN.mp3')
-                try:
-                    if not ("vc_config" in globals() and vc_config and vc_config.get_bool("USE_MIN_HW_STATUS", True)):
-                        EventMesh.publish("set_led_state", {"charging": "charging"})
-                except Exception:
-                    pass
-                EventMesh.publish("audio_montage_play",["R_CHARGE_IN.mp3"])
-            self.Low_power_warning_flag  = 0
-            try:
-                self._empty_shutdown_done = 0
-            except Exception:
-                pass
-            pass
-        elif status == 0:#拨出usbs
-            self.usb_status = 0
-            VC_satrt_flag = EventMesh.publish("get_VC_satrt_flag")#获取vc是否在运行
-            if VC_satrt_flag == None:
-                VC_satrt_flag = 0
-            voice_record_satrt_flag = EventMesh.publish("get_voice_record_flag")#获取静默或者语音留言是否在运行
-            if voice_record_satrt_flag == None:
-                voice_record_satrt_flag = 0
-            if VC_satrt_flag == 0 and voice_record_satrt_flag == 0:
-                self.aud.stop()
-                try:
-                    if not ("vc_config" in globals() and vc_config and vc_config.get_bool("USE_MIN_HW_STATUS", True)):
-                        EventMesh.publish("set_led_state", {"charging": "none"})
-                except Exception:
-                    pass
-                EventMesh.publish("audio_montage_play",["R_CHARGE_OUT.mp3"])
-                #self.aud.play(1,1,'U:/static/R_CHARGE_OUT.mp3')
-    def get_usb_state(self,event,msg):
-        return self.usb_status
-    
-    def set_speech_rate(self,event,msg):
-        #msg的取值是0.5~2.0
-        #而语速初始值是0,范围是-50~50
-        rate = 0
-        if msg == 0.5:
-            rate = -50
-        elif msg < 1:
-            rate = (msg - 1)* 100
-        elif msg >= 1:
-            rate = (msg - 1)* 50
-        rate = round(rate)
-        print("速率已经设置为:{}".format(rate))
-        EventMesh.publish("rate_change",rate)
-        return rate
-    def btnInfo_queue_thread(self):
-        while True:
-            data = self.btnInfo_queue.get()
-            self.btnInfo_queue_count-=1
-            #EventMesh.debug_log("Info_queue:data = {}".format(data))
-            if data != None:
-                try:
-                    if "data" in data :
-                        data["func"](data["data"])
-                    else :
-                        data["func"]()
-                except Exception as e:
-                    #EventMesh.debug_log("btnInfo_queue:ERROR:{}".format(e))
-                    if "data" in data :
-                        print(data["data"])
-                    usys.print_exception(e)
-    def publish_AI_btnInfo(self,event,msg):
-        #print("publish_AI_btnInfo 1")
-        self.btnInfo_queue.put(msg)
-        self.btnInfo_queue_count+=1
-        if self.btnInfo_queue_count > 80:
-            self.btnInfo_queue_count =0
-            print("事件处理线程队列出问题,重启!!!!!!!!!!!!!!")
-            print("线程:{} 队列:{}".format(_thread.threadIsRunning(self.btnInfo_queue_thread_id),self.btnInfo_queue.size()))
-            # if _thread.threadIsRunning(self.btnInfo_queue_thread_id):
-            #     _thread.stop_thread(self.btnInfo_queue_thread_id)
-            # _thread.stack_size(10*1024)
-            # self.btnInfo_queue_thread_id = _thread.start_new_thread(self.btnInfo_queue_thread, ())
-            # _thread.stack_size(8*1024)
-            # self.btnInfo_queue.clear()
-            # self.btnInfo_queue = Queue(100)
-            EventMesh.publish("VC_error") #播放报错
-            utime.sleep_ms(1000)
-            self.aud.stopPlayStream()
-            self.aud.stopAll()
-            Power.powerRestart()
-            EventMesh.publish("set_storage_config",["powerRestart",1])
-        #print("publish_AI_btnInfo 2")
-    
-    def sys_sleep_cb(self,event=None,msg=None):#休眠前回调
-        try:
-            if not ("vc_config" in globals() and vc_config and vc_config.get_bool("BT_WAKEUP_4G_ENABLED", False)):
-                return
-        except Exception:
-            return
-        print("sys_sleep_cb!!!!!!!!!!!!!!!")
-        EventMesh.publish("sleep_BT")#休眠蓝牙
-        self.PIN_wakeup_bt.write(0)
-    def sys_up_cb(self,event=None,msg=None):#唤醒前回调
-        try:
-            if not ("vc_config" in globals() and vc_config and vc_config.get_bool("BT_WAKEUP_4G_ENABLED", False)):
-                return
-        except Exception:
-            return
-        print("sys_up_cb!!!!!!!!!!!!!!!")
-        self.wakeup_BT()
-        self.PIN_wakeup_bt.write(1)
-    def rest_TB(self,event=None,msg=None):
-        print("rest_TB!!!!!!!!!!!!!!!")
-        self.PIN_BT_ON.write(0)
-        utime.sleep_ms(100)
-        self.PIN_BT_ON.write(1)
-    def wakeup_BT(self,event=None,msg=None):#4G唤醒蓝牙
-        try:
-            if not ("vc_config" in globals() and vc_config and vc_config.get_bool("BT_WAKEUP_4G_ENABLED", False)):
-                return
-        except Exception:
-            return
-        print("wakeup_BT!!!!!!!!!!!!!!!")
-        wakeup_cta1_io = self.extint_wakeup_cta1.read_level()
-        if wakeup_cta1_io == 1:
-            return 1
-        self.PIN_wakeup_bt.write(0)
-        count = 5
-        while(True):
-            count-=1
-            if count<=0:
-                return -1
-            wakeup_cta1_io = self.extint_wakeup_cta1.read_level()
-            print("bt io状态:{}".format(wakeup_cta1_io))
-            if wakeup_cta1_io == 1:
-                self.PIN_wakeup_bt.write(1)
-                return 1
-            utime.sleep_ms(100)
-            if count%2 == 0:
-                self.PIN_wakeup_bt.write(1)
-            else:
-                self.PIN_wakeup_bt.write(0)
-
-        
-    def sleep_BT(self,event=None,msg=None):#使蓝牙休眠
-        try:
-            if not ("vc_config" in globals() and vc_config and vc_config.get_bool("BT_WAKEUP_4G_ENABLED", False)):
-                return
-        except Exception:
-            return
-        print("sleep_BT!!!!!")
-        print("sleep_BT!!!!!")
-        print("sleep_BT!!!!!")
-        print("sleep_BT!!!!!")
-        self.PIN_wakeup_bt.write(0)
-        count = 300
-        while(True):
-            count-=1
-            if count<=0:
-                return -1
-            if self.extint_wakeup_cta1.read_level() == 0:
-                return 1
-            utime.sleep_ms(1)
-   
-   
-    def wakeup_extint_cb(self,args):#蓝牙唤醒4g
-        try:
-            if not ("vc_config" in globals() and vc_config and vc_config.get_bool("BT_WAKEUP_4G_ENABLED", False)):
-                return
-        except Exception:
-            return
-        print("wakeup_extint_cb:!!!!!!!!!")
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
-        self.PIN_wakeup_bt.write(1)
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.extint_wakeup_cta1_cb})
-    def extint_wakeup_cta1_cb(self):
-        #self.extint_wakeup_cta1_timer.start(100, 1, self.extint_wakeup_cta1_timer_cb)
-        pass
-    def extint_wakeup_cta1_timer_cb(self, *args, **kwargs):
-        if self.extint_wakeup_cta1.read_level() == 0:
-            self.extint_wakeup_cta1_timer.stop()
-            #self.PIN_wakeup_bt.write(0)
-
-    def powerKey_callback(self, status):
-        print("powerKey_callback:{}".format(status))
-   
-
-    def pwk_extint_cb(self,args):#pwk
-        current_time = 0
-        interval = 0
-
-        try:
-            #按键电平判断
-            press_level = 0
-            current_level = self.pwk_key.read_level()
-            if current_level != press_level:
-                return  # 非按下状态,直接返回
-
-            #消抖处理
-            current_time = utime.ticks_ms()
-            if utime.ticks_diff(current_time, self.last_key_time) < self.key_debounce_interval:
-                return
-            self.last_key_time = current_time  # 更新最后按键时间
-            print("pwk 按键按下")
-
-            #仅处理"按下瞬间"事件(避免重复触发)
-            if len(args) < 2:
-                print("args参数不足,跳过")
-                return
-            self.pwk_key_status = int(args[1])
-            if self.pwk_key_status != 1:
-                return
-
-            #双击计数逻辑
-            self.pwk_press_count += 1
-            current_time = utime.ticks_ms()
-
-            #第一次按下(启动长按定时器+双击等待)
-            if self.pwk_press_count == 1:
-                self.pwk_first_press_time = current_time  # 记录首次按下时间
-                self.pwk_key_long_press = 0               # 重置长按标记
-                self.pwk_key_timer_count = 0              # 重置定时器计数
-                self.pwk_key_press = 1                    # 标记按键按下
-                
-                # 停止旧定时器,启动长按/单击判定定时器(100ms单次触发,循环靠回调自启)
-                self.pwk_key_timer.stop()
-                self.pwk_key_timer.start(100, 1, self.pwk_key_timer_cb)
-                print("第一次按下,等待第二次按下")
-
-            #第二次按下(双击判断)
-            elif self.pwk_press_count == 2:
-                # 计算两次按下间隔
-                interval = utime.ticks_diff(current_time, self.pwk_first_press_time)
-                
-                #符合双击阈值:执行双击逻辑
-                if interval <= self.double_click_threshold:
-                    self.pwk_key_timer.stop()  # 停止长按定时器(关键:避免触发长按)
-                    self.pwk_key_double_press() # 执行双击业务
-                    print("双击生效")
-                    # 重置所有状态
-                    self.pwk_press_count = 0
-                    self.pwk_key_press = 0
-                    self.pwk_key_timer_count = 0
-
-                #间隔过长:视为新的第一次按下
-                else:
-                    self.pwk_first_press_time = current_time  # 更新首次按下时间
-                    self.pwk_press_count = 1                  # 重置为第一次按下
-                    print("间隔过长,视为新的第一次按下")
-
-            #按下次数超过2次(防计数溢出)
-            else:
-                self.pwk_press_count = 0
-                self.pwk_key_timer.stop()
-                print("按键次数过多,重置计数")
-
-        except Exception as e:
-            # 异常时重置所有状态,避免卡死
-            self.pwk_press_count = 0
-            self.pwk_key_press = 0
-            self.pwk_key_timer_count = 0
-            self.pwk_key_timer.stop()
-            usys.print_exception(e)
-
-
-    def pwk_is_double_judge(self, *args):
-        # if self.press_count == 1:  # 仅一次按下,判定为单击
-            
-        # 重置计数(无论何种情况,超时后清零)
-        self.pwk_press_count = 0
-
-
-    def pwk_key_timer_cb(self, *args, **kwargs):
-        EventMesh.publish("set_wait_first_flag",0)
-        EventMesh.publish("set_history_board_flag",0)
-        EventMesh.publish("set_play_new_voice_state",0)
-        EventMesh.publish("set_history_voice_flag",0)
-        EventMesh.publish("set_wait_voice_flag",0)
-        EventMesh.publish("set_stop_play",0)
-    #     EventMesh.publish("publish_AI_btnInfo",{"func":self.pwk_key_timer_cb_})
-    # def pwk_key_timer_cb_(self) :
-        try:
-            self.pwk_key_status = self.pwk_key.read_level()
-            self.pwk_key_timer_count +=1
-            if self.pwk_key_timer_count >= 10*3: #长按3s 
-                #print("pwk_key_long_press {}".format(self.pwk_key_long_press))
-                if self.pwk_key_long_press == 0:
-                        self.pwk_key_long_press = 1
-                        #EventMesh.publish("AI_key_long_press")
-                        # EventMesh.publish("audio_montage_play",["zhengzaiguanji.mp3"])
-                        self.aud.play(1,0,'U:/static/zhengzaiguanji.mp3')#关机音频
-                        EventMesh.publish("set_led_state", {"battery": "empty"})
-                        utime.sleep_ms(2000)
-                        self.aud.stopPlayStream()
-                        self.aud.stopAll()
-                        Power.powerDown()
-                        #print("AI_key_release")
-                        #EventMesh.publish("pwk_key_long_press")
-                        #EventMesh.publish("publish_AI_btnInfo",{"func":self.pwk_key_Long_Press})  
-            if self.pwk_key_status == 1:#松开
-                #print("pwk_key_timer_cb:{}".format(self.pwk_key_timer_count))
-                try:
-                    key_count = vc_config.get_int("KEY_RELEASE_DEBOUNCE_COUNT", 5)
-                except Exception:
-                    key_count = 5
-                if self.pwk_key_timer_count >= key_count: #防抖200ms
-                    #EventMesh.publish("AI_key_release")
-                    EventMesh.publish("publish_AI_btnInfo",{"func":self.pwk_key_release})
-                    self.pwk_key.enable()
-                    self.pwk_key_timer_count = 0
-                    self.pwk_key_press = 0
-                    self.pwk_press_count = 0#按下次数
-                    self.pwk_key_timer.stop()
-            
-        except Exception as e: 
-            usys.print_exception(e) 
-            
-    def keyAI_extint_cb(self,args):#call 2
-        try:
-            #print("AI_btn:keyAI_extint_cb{}".format(args[1]))
-            #定义最短触发间隔
-            MIN_TRIGGER_INTERVAL = 1  # 可根据需求调整,单位ms
-            #记录上次按键触发时间(初始化放类的__init__里:self.last_ai_key_time = 0)
-            current_time = utime.ticks_ms()  # 获取当前毫秒时间
-            #重复按下,直接返回
-            if utime.ticks_diff(current_time, self.last_ai_key_time) < MIN_TRIGGER_INTERVAL:
-                print("AI按键触发过频,忽略本次操作")
-                return
-            #更新上次触发时间
-            self.last_ai_key_time = current_time
-
-            self.KeyAI_status = int(args[1])
-            if self.KeyAI_status == 0:
-                self.KeyAI_status = 1
-            else:
-                self.KeyAI_status = 0
-            
-            if self.KeyAI_status == 0:#按下
-                if EventMesh.publish("get_wait_voice_flag") == 1:
-                    print("播放未读")
-                    EventMesh.publish("set_wait_voicerupt_flag",1)
-                    return
-                if EventMesh.publish("get_recv_voice") == 1:#当处于收到留言的时候
-                    print("收到留言")
-                    EventMesh.publish("set_voicerupt_flag",1)
-                    return
-                if self.KeyAI_press == 0 :
-                    EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
-                    if EventMesh.publish("get_play_new_voice_state") == 1:
-                        EventMesh.publish("set_play_new_voice_state",0)
-                    
-                    EventMesh.publish("set_VC_stop_play_audio_flag",1)#按键按下不允许播放之前下发的音频数据
-                    self.extint_AI.disable()
-                    self.KeyAI_press = 1
-                    self.KeyAI_long_press = 0
-                    self.KeyAI_timer.start(100, 1, self.KeyAI_timer_cb)
-                    self.KeyAI_timer_count = 0
-                    #EventMesh.publish("AI_key_press")
-                    # if dev_type == "7PIN":
-                        # self.aud.aud_tone_play(10,100)#播放按键音
-                        
-                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_key_press})  
-        except Exception as e: 
-            usys.print_exception(e)
-    def KeyAI_timer_cb(self, *args, **kwargs):
-    #     EventMesh.publish("publish_AI_btnInfo",{"func":self.KeyAI_timer_cb_})
-    # def KeyAI_timer_cb_(self):
-        try:
-            self.KeyAI_status = self.extint_AI.read_level()
-            self.KeyAI_timer_count +=1
-            if self.KeyAI_timer_count >= 10*1 : #长按1s 
-               
-                if self.KeyAI_long_press == 0:
-                    self.KeyAI_long_press = 1
-                    #EventMesh.publish("AI_key_long_press")
-                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_key_long_press})  
-            try:
-                _sos_on = vc_config.get_bool("SOS_ENABLED", True)
-            except Exception:
-                _sos_on = True
-            if _sos_on and self.KeyAI_timer_count >= 10*3 : #长按3s 上一次是短按则拨打sos紧急联系人
-                print("long sos phone:{}".format(self.KeySOS_timer_flag))
-                
-                if self.KeySOS_timer_flag == 1:
-                    self.KeySOS_timer_flag=0
-                    self.KeySos_long_press = 1
-                    self.KeySOS_timer.stop()
-                    get_sos_flag = EventMesh.publish("get_sos_flag")
-                    if get_sos_flag == 0:
-                        EventMesh.publish("set_sos_info_call")
-                    pass
-
-            elif self.KeyAI_timer_count >= 10*8:#关机
-                try:
-                    allow_power_off = vc_config.get_bool("AI_LONG_PRESS_POWER_OFF_ENABLED", False)
-                except Exception:
-                    allow_power_off = False
-                if allow_power_off:
-                    EventMesh.publish("audio_montage_play",["zhengzaiguanji.mp3"])
-                    #self.aud.play(1,0,'U:/static/zhengzaiguanji.mp3')#关机音频
-                    EventMesh.publish("set_led_state", {"battery": "empty"})
-                    utime.sleep_ms(2000)
-                    self.aud.stopPlayStream()
-                    print("stopAll:8")
-                    self.aud.stopAll()
-                    Power.powerDown()
-            if self.KeyAI_status == 1:#松开
-                #print("KeyAI_timer_cb:{}".format(self.KeyAI_timer_count))
-                try:
-                    key_count = vc_config.get_int("KEY_RELEASE_DEBOUNCE_COUNT", 5)
-                except Exception:
-                    key_count = 5
-                if self.KeyAI_timer_count >= key_count: #防抖200ms
-                    if self.KeySOS_timer_flag == 1:
-                        self.KeySOS_timer_flag = 0
-                        self.KeySos_long_press = 0
-                        self.KeySOS_timer.stop()
-                        print("KeySOS_timer_flag!!!!!!!!!!!!!!!")  #短按两次需要清除标识
-
-                    if self.KeyAI_long_press == 0:
-                        try:
-                            _sos_on = vc_config.get_bool("SOS_ENABLED", True)
-                        except Exception:
-                            _sos_on = True
-                        if _sos_on and self.KeySOS_timer_flag == 0:
-                            self.KeySOS_timer_flag = 1
-                            self.KeySos_long_press = 0
-                            self.KeySOS_timer.start(5000, 0, self.KeySos_timer_cb) #短按开启5s定时器 5s之内没有长按3s则清除标识 长按3s拨打sos紧急联系人
-                    else:
-                        self.KeySOS_timer_flag = 0
-                        self.KeySos_long_press = 0
-                        self.KeySOS_timer.stop()
-                        
-                    #EventMesh.publish("AI_key_release")
-                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_key_release})  
-                    self.extint_AI.enable()
-                    self.KeyAI_timer.stop()
-                    self.KeyAI_timer_count = 0
-                    self.KeyAI_press = 0
-                    self.KeyAI_long_press=0
-            
-        except Exception as e: 
-            usys.print_exception(e) 
-       
-    def AI_key_press(self):
-        # EventMesh.publish("wakeup_BT")
-        
-        # if dev_type == "7PIN" or dev_type == "7PIN_coze" :
-        #     # self.aud.aud_tone_play(10,60)#播放按键音
-        #     EventMesh.publish("audio_montage_play",["chenggong.mp3"])  
-        self.check_rest_timer_count = 0
-        print("AI_key_press")
-        EventMesh.publish("AI_key_press")
-    def KeySos_timer_cb(self,*argc):
-        print("KeySos_timer_cb!!!!!!!!!!{} {}".format(self.KeySos_long_press,self.KeySOS_timer_flag))
-        
-        if self.KeySos_long_press == 1 and self.KeySOS_timer_flag == 1:  #清空sos标识
-            self.KeySos_long_press=0
-            self.KeySOS_timer_flag=0
-           
-        self.KeySos_long_press=0
-        self.KeySOS_timer_flag = 0
-        return
-        self.KeySOS_timer_flag = 0
-        if self.KeySOS_timer_count >= 3:
-            self.KeySOS_timer_count = 0
-            get_sos_flag = EventMesh.publish("get_sos_flag")
-            if get_sos_flag == 0:
-                EventMesh.publish("set_sos_info_call")
-        self.KeySOS_timer_count = 0
-        pass
-
-    def AI_key_long_press(self):
-        self.check_rest_timer_count = 0
-        print("AI_key_long_press")
-
-        EventMesh.publish("AI_key_long_press")
-        
-    def AI_key_release(self):
-        print("AI_key_release")
-        # self.KeySOS_timer_count +=1
-        # print("self.KeySOS_timer_count:{} {}".format(self.KeySOS_timer_count,self.KeySOS_timer_flag))
-        # if self.KeySOS_timer_flag == 0:
-        #     self.KeySOS_timer_flag = 1
-        #     self.KeySOS_timer.start(5000, 0, self.KeySos_timer_cb)
-        EventMesh.publish("AI_key_release")
-        
-    def pwk_key_double_press(self):
-        self.check_rest_timer_count = 0
-        print("pwk_key_double_press")
-        EventMesh.publish("pwk_key_double_press")
-
-    def pwk_key_Long_Press(self,event=None,msg=None):
-        self.check_rest_timer_count = 0
-        print("pwk_key_long_press!!!!!!!")
-        print("pwk_key_long_press!!!!!!!")
-        
-        # EventMesh.publish("paly_iccid_imei")
-        # return
-        EventMesh.publish("audio_montage_play",["zhengzaiguanji.mp3"])
-        #self.aud.play(1,0,'U:/static/zhengzaiguanji.mp3')#关机音频
-        EventMesh.publish("set_led_state", {"battery": "empty"})
-        utime.sleep_ms(3000)
-        self.aud.stopPlayStream()
-        self.aud.stopAll()
-        Power.powerDown()
-        #print("AI_key_release")
-        #EventMesh.publish("pwk_key_long_press")
-        
-    def pwk_key_release(self):
-        self.check_rest_timer_count = 0
-        #print("pwk_key_release")
-        EventMesh.publish("pwk_key_release")
-
-    
-    def wdt_timer_cb(self, *args, **kwargs):
-        if self.is_wdt == 1:
-            self.wdt.feed()
-
-    def AI_dev_timer_cb(self, *args, **kwargs):
-        #print("AI_dev_timer_cb")
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_dev_timer_event})  
-    def _battery_empty_shutdown_seq(self):
-        """REQ-BAT-03:红快闪 3 次 → 灭灯 → 关机提示音 → 关机(btn 线程执行)。"""
-        try:
-            EventMesh.publish("battery_priority_mute_vc")
-        except Exception:
-            pass
-        try:
-            n, ms = 3, 500
-            if "vc_config" in globals() and vc_config:
-                n = int(vc_config.get_int("BATTERY_EMPTY_FLASH_COUNT", 3))
-                ms = int(vc_config.get_int("BATTERY_EMPTY_FLASH_PERIOD_MS", 500))
-            EventMesh.publish("set_led_state", {"alert": "red_flash_3"})
-            utime.sleep_ms(n * ms * 2 + 200)
-            EventMesh.publish("set_led_state", {"ai_state": "idle", "battery": "normal", "charging": "none"})
-        except Exception:
-            pass
-        try:
-            EventMesh.publish("audio_montage_play", ["didianguanji.mp3"])
-            utime.sleep_ms(4500)
-            self.aud.stopPlayStream()
-            self.aud.stopAll()
-        except Exception:
-            pass
-        try:
-            Power.powerDown()
-        except Exception:
-            pass
-
-    def AI_dev_timer_event(self):
-        #EventMesh.publish("audio_montage_play",["liuliangtishi.mp3"])
-        EventMesh.publish("send_power_init_data")
-        if self.is_wdt == 1:
-            self.wdt.feed()
-        VC_satrt_flag = EventMesh.publish("get_VC_satrt_flag")#获取vc是否在运行
-        if VC_satrt_flag == None:
-            VC_satrt_flag = 0
-        voice_record_satrt_flag = EventMesh.publish("get_voice_record_flag")#获取静默或者语音留言是否在运行
-        if voice_record_satrt_flag == None:
-            voice_record_satrt_flag = 0
-        Vbatt = Power.getVbatt()
-        if self.usb_status == 1 and VC_satrt_flag == 0 and voice_record_satrt_flag == 0:
-            try:
-                if not ("vc_config" in globals() and vc_config and vc_config.get_bool("USE_MIN_HW_STATUS", True)):
-                    EventMesh.publish("set_led_state", {"charging": "charging"})#充电红灯常亮
-            except Exception:
-                pass
-        _bcfg = _BatteryCfg.instance()
-        if self.usb_status == 1 and Vbatt >= _bcfg.charge_full_mv and VC_satrt_flag == 0 and voice_record_satrt_flag == 0:
-            try:
-                if not ("vc_config" in globals() and vc_config and vc_config.get_bool("USE_MIN_HW_STATUS", True)):
-                    EventMesh.publish("set_led_state", {"charging": "full"})
-            except Exception:
-                pass
-            if self.usb_charge_flag == 0 :
-                EventMesh.publish("audio_montage_play",["R_CHARGE_FULL.mp3"])
-                #self.aud.play(1,0,'U:/static/R_CHARGE_FULL.mp3')#充电已满
-                self.usb_charge_flag = 1
-        try:
-            low_soc = int(_bcfg.low_soc)
-            empty_soc = int(_bcfg.empty_soc)
-        except Exception:
-            low_soc, empty_soc = 30, 10
-        warn_interval_ms = 300000
-        try:
-            if "vc_config" in globals() and vc_config:
-                warn_interval_ms = int(vc_config.get_int("BATTERY_LOW_WARN_INTERVAL_MS", 300000))
-        except Exception:
-            pass
-        soc = None
-        try:
-            soc = self.get_battery()
-        except Exception:
-            soc = None
-        if self.usb_status == 0 and soc is not None:
-            empty_by_soc = soc < empty_soc
-            empty_by_mv = Vbatt is not None and Vbatt < int(_bcfg.empty_mv)
-            if empty_by_soc or empty_by_mv:
-                if getattr(self, "_empty_shutdown_done", 0) == 0:
-                    self._empty_shutdown_done = 1
-                    try:
-                        print("[BAT] empty shutdown soc={} vbatt={}".format(soc, Vbatt))
-                    except Exception:
-                        pass
-                    EventMesh.publish("publish_AI_btnInfo", {"func": self._battery_empty_shutdown_seq})
-            elif soc < low_soc:
-                if voice_record_satrt_flag == 0:
-                    try:
-                        now = utime.ticks_ms()
-                        last = getattr(self, "_low_warn_last_ms", 0)
-                        if (not last) or utime.ticks_diff(now, last) >= warn_interval_ms:
-                            self._low_warn_last_ms = now
-                            try:
-                                print("[BAT] low warn soc={} interval_ms={}".format(soc, warn_interval_ms))
-                            except Exception:
-                                pass
-                            EventMesh.publish("audio_montage_play", ["Low_power_warning.mp3"])
-                    except Exception:
-                        pass
-            else:
-                try:
-                    self._low_warn_last_ms = 0
-                    self._empty_shutdown_done = 0
-                except Exception:
-                    pass
-                if VC_satrt_flag == 0 and Vbatt is not None and Vbatt >= int(_bcfg.low_mv):
-                    try:
-                        if not ("vc_config" in globals() and vc_config and vc_config.get_bool("USE_MIN_HW_STATUS", True)):
-                            EventMesh.publish("set_led_state", {"charging": "none", "battery": "normal"})
-                    except Exception:
-                        pass
-
-        self.check_rest_timer_count += 1
-        if self.check_rest_timer_count >= 8640:#24小时没按键,则关机
-            self.pwk_key_Long_Press()
-        
-    def get_battery(self, event=None, msg=None):
-        # 返回 int 型 SoC(0~100):便于 TTS/上报/比较,且 OCV 插值本身比裸电压稳;不做小数避免频繁跳变
-        try:
-            Vbatt = Power.getVbatt()
-        except Exception:
-            return 0
-        soc = _BatteryCfg.instance().soc_from_vbatt(Vbatt)
-        if soc is None:
-            return 0
-        if soc < 0:
-            return 0
-        if soc > 100:
-            return 100
-        return int(soc)
-    def _audio_is_battery_priority(self, msg):
-        try:
-            for i in (msg or []):
-                n = str(i)
-                if ("Low_power" in n) or ("didianguanji" in n):
-                    return True
-        except Exception:
-            pass
-        return False
-
-    def audio_montage_play(self,event,msg):
-        self.wakeup_BT()
-        bat_pri = self._audio_is_battery_priority(msg)
-        if bat_pri:
-            try:
-                EventMesh.publish("battery_priority_mute_vc")
-            except Exception:
-                pass
-        else:
-            # 仅服务端 TTS PCM 在播时跳过;低电/关机提示优先级更高(见上)
-            try:
-                if EventMesh.publish("get_vc_tts_playback_active") == 1:
-                    print("[audio-arb] skip local montage_play during tts_pcm")
-                    return
-            except Exception:
-                pass
-        self.aud.stopPlayStream()
-        self.aud.stopAll()
-        str_data='mp3files='
-        for i in msg:
-            try:
-                str_tmp = i.replace(".mp3","")
-                str_data+="{}.mp3+".format(audio_buf.index(str_tmp))
-            except Exception as e:
-                    usys.print_exception(e)    
-        str_data = str_data[:-1]
-        print("audio_montage_play:{}".format(str_data))
-        self.aud.montage_play('U:/static/audio.bin',1,1,str_data)
-        self.play_imei_timer_count=1
-        self.play_imei_timer.stop()
-        self.play_imei_timer.start(1000,1,self.play_imei_timer_cb)
-        
-    def paly_iccid_imei(self,event,msg):
-        
-        self.wakeup_BT()
-        self.aud.stopPlayStream()
-        self.aud.stopAll()
-        str_data = 'mp3files={}.mp3+'.format(audio_buf.index("liuliangtishi"))#流量提示
-        imei  = modem.getDevImei()
-        iccid = sim.getIccid()
-        print("paly_iccid_imei:ICCID:{}".format(iccid))
-        if iccid != -1 and len(iccid) > 5:
-            iccid = iccid.upper()
-            if len(iccid) <20:
-                for i in range(20-len(iccid)):
-                    iccid = "0"+iccid
-            str_data += '{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+'.format(audio_buf.index("R_READ_TIME_{}".format(iccid[0]))\
-                ,audio_buf.index("R_READ_TIME_{}".format(iccid[1])),audio_buf.index("R_READ_TIME_{}".format(iccid[2])),audio_buf.index("R_READ_TIME_{}".format(iccid[3])),\
-                audio_buf.index("R_READ_TIME_{}".format(iccid[4])),audio_buf.index("R_READ_TIME_{}".format(iccid[5])),audio_buf.index("R_READ_TIME_{}".format(iccid[6])),audio_buf.index("R_READ_TIME_{}".format(iccid[7])),\
-                audio_buf.index("R_READ_TIME_{}".format(iccid[8])),audio_buf.index("R_READ_TIME_{}".format(iccid[9])),audio_buf.index("R_READ_TIME_{}".format(iccid[10])),audio_buf.index("R_READ_TIME_{}".format(iccid[11])),\
-                audio_buf.index("R_READ_TIME_{}".format(iccid[12])),audio_buf.index("R_READ_TIME_{}".format(iccid[13])),audio_buf.index("R_READ_TIME_{}".format(iccid[14])),audio_buf.index("R_READ_TIME_{}".format(iccid[15])),\
-                audio_buf.index("R_READ_TIME_{}".format(iccid[16])),audio_buf.index("R_READ_TIME_{}".format(iccid[17])),audio_buf.index("R_READ_TIME_{}".format(iccid[18])),audio_buf.index("R_READ_TIME_{}".format(iccid[19])))
-        str_data += '{}.mp3+'.format(audio_buf.index("shouhou"))#可联系售后解决,联系方式见售后客服电话是400-886-8777或到微信公众号柒牌SEVENBRAND自行充值:
-        self.aud.montage_play('U:/static/audio.bin',1,1,str_data)
-        #开定时器不让系统休眠
-        self.play_imei_timer_count=1
-        self.play_imei_timer.stop()
-        self.play_imei_timer.start(1000,1,self.play_imei_timer_cb)
-
-    def play_imei_timer_cb(self,event=None,msg=None):
-        if self.play_imei_timer_count == 1:
-            if EventMesh.publish("get_audio_play_state") == 0:
-                print("音频文件播放完成")
-                self.play_imei_timer_count=0
-                self.play_imei_timer.stop()
-                return
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
-
-    #鉴权出错,播放imei号
-    def paly_device_res_error(self,event,msg):
-        self.wakeup_BT()
-        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
-        str_data = 'mp3files={}.mp3+'.format(audio_buf.index("jianquancuowu"))#AI功能异常,请联系客户查询,设备号为:
-        imei  = modem.getDevImei()
-        imei = imei.upper()
-        str_data += '{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3'.format(audio_buf.index("R_READ_TIME_{}".format(imei[0]))\
-                ,audio_buf.index("R_READ_TIME_{}".format(imei[1])),audio_buf.index("R_READ_TIME_{}".format(imei[2])),audio_buf.index("R_READ_TIME_{}".format(imei[3])),\
-                audio_buf.index("R_READ_TIME_{}".format(imei[4])),audio_buf.index("R_READ_TIME_{}".format(imei[5])),audio_buf.index("R_READ_TIME_{}".format(imei[6])),audio_buf.index("R_READ_TIME_{}".format(imei[7])),\
-                audio_buf.index("R_READ_TIME_{}".format(imei[8])),audio_buf.index("R_READ_TIME_{}".format(imei[9])),audio_buf.index("R_READ_TIME_{}".format(imei[10])),audio_buf.index("R_READ_TIME_{}".format(imei[11])),\
-                audio_buf.index("R_READ_TIME_{}".format(imei[12])),audio_buf.index("R_READ_TIME_{}".format(imei[13])),audio_buf.index("R_READ_TIME_{}".format(imei[14])))
-        #print("str_data:{}".format(str_data))
-        self.aud.montage_play('U:/static/audio.bin',1,1,str_data)
-        self.play_imei_timer_count=1
-        self.play_imei_timer.stop()
-        self.play_imei_timer.start(1000,1,self.play_imei_timer_cb)
-
-#存储信息
-class getStorageDataManager(Abstract):#获取内存数据
-        def __init__(self):
-            self.config_data = {}
-            check = ql_fs.path_exists("/usr/static/storage_file/config.json")
-            if check == True:
-                with open("/usr/static/storage_file/config.json","r") as fd:
-                    self.config_data = ujson.load(fd)
-            else :
-                self.config_data = {'old_time': 0, 'flip_sw': 0}
-                pass
-
-        def post_processor_after_instantiation(self):
-            EventMesh.subscribe("set_storage_data", self.set_storage_data)
-            EventMesh.subscribe("set_storage_config", self.set_storage_config)
-            EventMesh.subscribe("get_storage_data", self.get_storage_data)
-            EventMesh.subscribe("get_storage_config", self.get_storage_config)
-
-        def set_storage_data(self, event, msg): 
-            # EventMesh.debug_log("set_storage_data:{}".format(msg))
-            if msg != None:
-                self.config_data = msg
-                with open("/usr/static/storage_file/config.json",'w+',encoding="utf-8") as fd:
-                    ujson.dump(self.config_data,fd)
-        
-
-        def set_storage_config(self,event,msg): 
-            # EventMesh.debug_log("set_storage_key_data1:{}".format(msg))
-            if len(msg) >= 2:
-                self.config_data[msg[0]]=msg[1]
-                # EventMesh.debug_log("set_storage_key_data2:{}".format(self.config_data[msg[0]]))
-                EventMesh.publish("publish_message_event_ui",{"func":self.set_storage_save,"data":0})
-        def set_storage_save(self, data): 
-            with open("/usr/static/storage_file/config.json",'w+',encoding="utf-8") as fd:
-                ujson.dump(self.config_data,fd)
-        def get_storage_data(self, event, msg): 
-            # EventMesh.debug_log("get_storage_data:{}".format(self.config_data))
-            return self.config_data 
-        def get_storage_config(self,event,msg):
-            # EventMesh.debug_log("get_storage_key_data1:{}".format(msg))
-            b = msg in self.config_data.keys()
-            if b == True:
-                # EventMesh.debug_log("get_storage_key_data2:{}".format(self.config_data[msg]))
-                return self.config_data[msg]
-            else :
-                return None
-            # try:
-            #     return self.config_data[msg]
-            # except:
-            #     EventMesh.debug_log("get_storage_key_data:none key:{}",msg)
-            #     return None
-#设备资源授权平台
-class device_res_author(Abstract):
-   
-    def __init__(self) :
-        print("device_res_author __init__ start !!!!!!!!!!!!!!!!!")
-        self.deviceId = modem.getDevImei()#"876226002022574"
-        self.SHA1_BLOCK_SIZE = 64
-        self.PAD_BLOCK_SIZE = 16
-        self.SHA256 = uhashlib.sha256
-        self.pad = lambda s: s + chr(self.PAD_BLOCK_SIZE - len(s) % self.PAD_BLOCK_SIZE) * (self.PAD_BLOCK_SIZE - len(s) % self.PAD_BLOCK_SIZE)
-        # 除去补16字节的多余字符
-        self.unpad = lambda s: s[:-s[-1]]
-        #K01
-        # self.key = b"MWrMZjdtcPhj41jWuoqFAhTmqyFluqj1" 
-        # self.pid = "qAtdDOGXgXYjT2HGFr3a"
-        #G15
-        # self.key = b"3LWfBicxfRZ3ZAqmZCFuvMdGUXdAFWkb" 
-        # self.pid = "qLx4Pu4mSUQxyfVKGLXG"
-        #7pin
-        # pid:YiMEGsAbHqYwhO3IRWVE
-        # 产品密钥:yFXqKI3EvznGbKLb4mxuW2SErG5XyJwZ
-        self.key = b"uKLrChwrxA1LklgBc3VwawYjUkbT3BgX" 
-        self.pid = "cqxJZi5CNgzjS3mX21Zw"
-        # if dev_type == 'jswm' or dev_type == 'jswm_BOX':#金三完美
-        #     self.key = b"FuXG4ptpGbzvKbdB4vpUypisbENgy2nB" 
-        #     self.pid = "DieBeXahZHLrjSnORbgN"
-        self.dev_res_timer = osTimer()
-        self.dev_res_timer.start(20000, 1, self.dev_timer_cb)
-        self.dev_res_timerCount = 0
-        self.ud_token_timer = osTimer()
-        self.ud_token_timer.start(10000, 1, self.ud_token_timer_cb)
-        self.ud_token_timer_Count = 0
-        self.dev_token = None
-        self.res_error_count = 0
-        self.ntptime_flag = 0
-        EventMesh.subscribe("get_dev_token", self.get_dev_token)
-        EventMesh.subscribe("update_dev_token", self.update_dev_token)
-        EventMesh.subscribe("dev_active", self.dev_active)
-        EventMesh.subscribe("dev_sign_in", self.dev_sign_in)
-        EventMesh.subscribe("dev_NTP", self.dev_NTP)
-        EventMesh.subscribe("post_iccid_user", self.post_iccid_user)
-        # self.post_iccidd_token_timer = osTimer()
-        # self.post_iccidd_token/_timer.start(10000, 0, self.post_iccidd_token_timer_cb)
-        self.connect_web_flag = 0
-        #EventMesh.publish_async("dev_NTP",thread_size = 8192)
-
-        # count = 0
-        # while True:
-        #     if self.ntptime_flag == 1:
-        #         break
-        #     utime.sleep_ms(1)
-        #     count+=1
-        #     if count>600:
-        #         break
-        # EventMesh.publish_async("dev_active",thread_size = 8192)
-        # EventMesh.publish_async("dev_sign_in",thread_size = 8192)
-
-        self.dev_NTP()#获取时间
-        self.dev_active() #设备激活
-        self.dev_sign_in() #设备登入
-
-        
-        print("device_res_author __init__ end !!!!!!!!!!!!!!!!!")
-    def post_iccidd_token_timer_cb(self,*argc):
-        EventMesh.publish("post_iccid_user")
-
-    def get_dev_token(self, event, msg): 
-        return self.dev_token
-    #网络校时
-    def dev_NTP(self, event=None, msg=None):
-        response = None
-        try:
-            print("dev_NTP1")
-            url = constants.DMP_BASE + "/dev/time"
-            print("dev_NTP:{}".format(url))
-            response = request.get(url,timeout=5)
-            data = response.json()
-            print("dev_NTP:{}".format(data))
-            if data:
-                try:
-                    timeSecond = data["data"]["timestamp"]//1000
-                    
-                    next_alarm_time = utime.localtime(timeSecond)
-                    print("timeSecond {} next_alarm_time {}".format(timeSecond,next_alarm_time))
-                    machine.RTC().datetime((next_alarm_time[0],next_alarm_time[1],next_alarm_time[2],next_alarm_time[6],next_alarm_time[3],next_alarm_time[4],next_alarm_time[5],0))
-                    self.ntptime_flag  = 1
-                except Exception as e:
-                        usys.print_exception(e)
-        except Exception as e:
-            usys.print_exception(e)
-        if response != None:
-            response.close() 
-        print("dev_NTP end")
-    def dev_timer_cb(self, *args): #设备资源管理鉴权平台定时器
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.dev_timer_cb_}) 
-
-    def dev_timer_cb_(self):    
-        #if EventMesh.DEV_RES_MANAGE_AUTHEN_PLATFORM == 1:
-        try:
-            if self.dev_token == None:
-                if self.ntptime_flag == 0:
-                    self.ntptime_flag  = 1
-                    EventMesh.publish_async("dev_NTP",thread_size = 8192)
-                    #EventMesh.publish("publish_AI_btnInfo",{"func":self.dev_NTP})
-                print("timer:{}!!!!!!!!\r\n".format(utime.localtime()))
-                EventMesh.publish_async("dev_active",thread_size = 8192)
-                EventMesh.publish_async("dev_sign_in",thread_size = 8192)
-                # self.dev_active() #设备激活
-                # self.dev_sign_in() #设备登入
-                self.dev_res_timerCount+=1
-                if self.dev_res_timerCount > 3: #开机一分钟内没有获取到密码和token 等下一次更新令牌在走登入 激活流程
-                    self.dev_res_timerCount=0
-                    self.dev_res_timer.stop()
-        except Exception as e:
-            usys.print_exception(e)
-    def ud_token_timer_cb(self, *args): 
-        EventMesh.publish("publish_AI_btnInfo",{"func":self.ud_token_timer_cb_}) 
-
-    def ud_token_timer_cb_(self):   
-        try:
-            #print("ud_token_timer_!!!!")
-            self.ud_token_timer_Count+=1
-            if self.ud_token_timer_Count >= 720: #定时10*3000s时更新token
-                self.ud_token_timer_Count = 0
-                EventMesh.publish_async("dev_sign_in",thread_size = 8192)
-        except Exception as e:
-            usys.print_exception(e)    
-    def get_sim_Status(self):
-        return sim.getStatus() 
-    def HMAC(self,k, m):
-    
-        KEY_BLOCK = k + (b'\0' * (self.SHA1_BLOCK_SIZE - len(k)))
-        KEY_INNER = bytes((x ^ 0x36) for x in KEY_BLOCK)
-        KEY_OUTER = bytes((x ^ 0x5C) for x in KEY_BLOCK)
-        inner_message = KEY_INNER + m
-        outer_message = KEY_OUTER + self.SHA256(inner_message).digest()
-        return self.SHA256(outer_message)
-    def get_hmac_sha256(self,k, m):
-        data = self.HMAC(k, m).digest()
-        # print("HMAC(k, m).digest():", data)
-        # print("HMAC(k, m).digest():", ubinascii.hexlify(data))
-        hex_data = ubinascii.hexlify(data)
-        hex_data = hex_data.decode('ascii')
-        return hex_data
-    # 加密函数
-    def aes_CBC_Encrypt(self,data, key, iv):  # CBC模式的加密函数,data为明文,key为16字节密钥,iv为16字节的偏移量
-        key = key.encode('utf-8')
-        iv = iv.encode('utf-8')  # CBC 模式下的偏移量
-        data = self.pad(data)  # 补位
-        data = data.encode('utf-8')
-        aes = ucryptolib.aes(key,ucryptolib.MODE_CBC,iv)
-        result = base64.b64encode(aes.encrypt(data))
-        return str(result, 'utf-8')  # 以字符串的形式返回
-
-    # 解密函数
-    def aes_CBC_Decrypt(self,data, key, iv):  # CBC模式的解密函数,data为密文,key为16字节密钥
-        key = key.encode('utf-8')
-        iv = iv.encode('utf-8')
-        
-        aes = ucryptolib.aes(key,ucryptolib.MODE_CBC,iv) #CBC模式
-
-        # decrypt AES解密  B64decode为base64 转码
-        result = aes.decrypt(base64.b64decode(data))
-        result = self.unpad(result)  # 除去补16字节的多余字符
-        return str(result, 'utf-8')  # 以字符串的形式返回
-    #激活接口 没有密码走激活
-    def dev_active(self, event=None, msg=None):
-        signal = net.csqQueryPoll()
-        if signal == -1 or self.get_sim_Status() != 1:#网络不正常
-            print("dev_active 无 SIM卡!!!!!!!!\r\n")
-            return
-        print("dev_active 1\r\n")
-        # 签名内容: = deviceId+pid+timeStamp
-        dev_pass = ""
-        dev_pass = EventMesh.publish("get_storage_config","dev_pass")
-        print("dev_pass:{}\r\n".format(dev_pass))
-        if dev_pass == None:
-            dev_pass = ""
-        if dev_pass == "" or dev_pass == None:
-            try:
-                date_tmp = utime.localtime() #获取当前时间
-                print("date_tmp:{}\r\n".format(date_tmp)) #转换时间戳s
-                timeStamp = (utime.mktime(date_tmp))*1000#获取时间戳ms
-                deviceId = self.deviceId #"876226002022574" #以imei号
-                pid = self.pid
-                key = self.key
-                sign_data = "{}{}{}".format(deviceId,pid,timeStamp)  #签名内容
-                print("sign_data:{}\r\n".format(sign_data))
-                sha256_hex = self.get_hmac_sha256(key,sign_data)
-                print("sha256_hex:{}\r\n".format(sha256_hex))
-
-                data = {
-                    "deviceId":"{}".format(deviceId), #设备名
-                    "pid":pid, #产品PID
-                    "sign":"{}".format(sha256_hex), #HMACSHA256签名后的数据
-                    "timeStamp":timeStamp, #要以ms的形式
-                    "signMethod":"HMACSHA256" #签名方式 HMACSHA256
-                }
-                print(ujson.dumps(data))
-                headers_T ={"Content-Type":"application/json"}
-                url = constants.DMP_BASE + "/dev/auth/active"
-                print("dev_active:{}".format(url))
-                data =ujson.dumps(data)
-                response = None
-                response_result = 0
-                try:
-                    response = request.put(url,data=data,headers = headers_T,timeout=5)
-                    response_result = 1
-                except Exception as e:
-                    usys.print_exception(e)   
-                    print("put error!!!!!\r\n")
-                    response_result = 0
-                
-                IV = "" #偏移量
-                PASSWORD= "" #服务器返回后的加密内容 密码
-                if response_result == 1: #访问成功ip
-                    if response.status_code == 200:
-                        print("headers:{}".format(response.headers))
-                        content = response.content
-                        c = next(content)
-                        # "api.xcloudtech.com"
-                        # d_server_port = 9000
-                        data =  ujson.loads(c)
-                        # data = response.json()
-                        print("data-{}".format(data))
-                        if data != None:
-                            if "success" in data:
-                                if data['success'] == True:
-                                    if "data" in data:
-                                        data_tmp = data["data"]
-                                        if "nonce" in data_tmp: #偏移量
-                                            IV = data_tmp["nonce"]
-                                        if "password" in data_tmp:
-                                            PASSWORD = data_tmp["password"]
-                                            # EventMesh.publish("set_storage_config",["dev_pass",PASS])
-                                if data["code"] == 20001:#鉴权成功
-                                    pass
-                                elif data["code"] > 50000:#
-                                    self.res_error_count+=1
-                                    if self.res_error_count>=3:
-                                        self.res_error_count = 0
-                                        
-                                        EventMesh.publish("paly_device_res_error")
-                                    if data["code"] == 50003:#时间错误
-                                        self.ntptime_flag  = 0
-                        if PASSWORD != None and IV != None:
-                            if len(PASSWORD) != 0 and len(IV) != 0:
-                                password = PASSWORD
-                                iv = IV
-                                #解密 之后保存本地下一次登入做使用
-                                decrypt_data = self.aes_CBC_Decrypt(password, self.key, iv) #对服务器下发的密码进行解密
-                                print("decrypt_data {}".format(decrypt_data))
-                                EventMesh.publish("set_storage_config",["dev_pass",decrypt_data])    
-                    print("IV:{}\r\n PASS:{}".format(IV,PASSWORD))
-                if response != None:
-                    response.close()  
-            except Exception as e:
-                usys.print_exception(e)   
-            print("dev_active end\r\n")
-            return [response_result,PASSWORD,IV]
-    #随机生成16个字节的字符串作为加密的IV 偏移量
-    def generate_random_string(self,length=16): 
-        random_str = ""
-        chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
-        for i in range(length):
-            random_str+=chars[random.randint(0,length)]
-        return random_str
-
-    #设备登入 没有Token 走登入获取token
-    def dev_sign_in(self, event=None, msg=None):
-    
-
-        signal = net.csqQueryPoll()
-        if signal == -1 or self.get_sim_Status() != 1:#网络不正常
-            print("dev_sign_in 无 SIM卡!!!!!!!!\r\n")
-            return
-        print("dev_sign_in 1")
-        token = ""
-        try:
-            token = self.dev_token
-            dev_pass = EventMesh.publish("get_storage_config","dev_pass") 
-            #print("token:{}\r\n".format(token))
-            if token == None:
-                token = ""
-            if dev_pass == None:
-                dev_pass = ""
-            if len(dev_pass) == 0:#没有密码
-                return
-            #if token == None or token == "":
-            if 1: #返回结果成功
-                #获取本地保存的密码
-                decrypt_data=EventMesh.publish("get_storage_config","dev_pass")
-                # decrypt_data = self.aes_CBC_Decrypt(password, self.key, iv) #对服务器下发的密码进行解密
-                print("decrypt_data {}".format(decrypt_data))
-                #随机生成16个字符串 设备生成做加密使用
-                random_string = self.generate_random_string(16) #随机的16个字符串
-                enc_iv = random_string #加密的IV偏移量
-                #设备端进行密码加密
-                encrypt_data = self.aes_CBC_Encrypt(decrypt_data, self.key, enc_iv)
-                print("encrypt_data {}".format(encrypt_data))
-
-                #登入数据
-                deviceId = self.deviceId
-                sign_in_data = {
-                    "deviceId":"{}".format(deviceId), #设备ID
-                    "password":"{}".format(encrypt_data), #加密的数据
-                    "nonce":"{}".format(random_string) #随机的16个字符串
-                }
-                print(ujson.dumps(sign_in_data))
-                sign_url =None
-                sign_url = constants.DMP_BASE + "/dev/auth/sign_in"
-                #获取验证码
-                #url = "http://dmp.qipai.com.cn:30461/dev/device/bind-code/860132066060377"
-                # token = EventMesh.publish("get_dev_token")
-                # headers_T = {"Content-Type":"application/x-www-form-urlencoded","Accept":"*/*","bz-dt":"{}".format(token)}
-                # response = request.get(url,headers = headers_T,timeout=5)
-                # data = response.json()
-                print("dev_sign_in:{}".format(sign_url))
-                headers_T ={"Content-Type":"application/json"}
-                response_result = 0
-                response = 0
-                try:
-                    response = request.post(sign_url,data=ujson.dumps(sign_in_data),headers = headers_T,timeout=5)
-                    response_result = 1
-                except Exception as e:
-                    usys.print_exception(e)   
-                    print("post error!!!!!\r\n")
-                    response_result = 0
-                # {'msg': '', 'code': 20001, 'success': True, 
-                # 'data': {'deviceId': '876226002022574', 
-                # 'token': '95436a9e09fd0ff89feb8825257dca982f'}
-                # }
-                
-                #self.dev_token = ""
-                if response_result == 1:
-                    if response.status_code == 200:
-                        content = response.content
-                        c = next(content)
-                        data =  ujson.loads(c)
-                        print("data-{}".format(data))
-                        if data != None:
-                            if "success" in data:
-                                if data['success'] == True: #登入成功
-                                    if "data" in data:
-                                        data_tmp = data["data"]
-                                        if "token" in data_tmp: #token
-                                            self.dev_token = data_tmp["token"]
-                                else:#报错清空密钥
-                                    print("登录失败,清空密钥")
-                                    EventMesh.publish("set_storage_config",["dev_pass",""])
-                                if data["code"] == 50003:#时间错误
-                                    self.ntptime_flag  = 0
-                              
-                print("token:{}\r\n".format(self.dev_token))
-                if response != None:
-                    response.close()
-                pass
-        except Exception as e:
-                    print("post error!!!!!\r\n")
-                    response_result = 0
-        if EventMesh.publish("get_dev_token") != None:
-            if len(EventMesh.publish("get_dev_token")) != 0:
-                if self.connect_web_flag == 0:
-                    EventMesh.publish("post_iccid_user")
-                self.connect_web_flag = 1
-        print("dev_sign_in end")
-            #ICCID上传
-    def post_iccid_user(self,event=None,msg=None):
-        response = None
-        try:
-            iccid = sim.getIccid()
-            if iccid != -1 and len(iccid) > 5:
-                print("pots_ICCID:{}".format(iccid))
-                #Headers = {'Transfer-Encodong':'chunked'}
-                token = EventMesh.publish("get_dev_token")
-                headers_T = None
-                if token:
-                    headers_T = {"qp-dt":"{}".format(token)}
-                #http://dmp.raisound.com/bz-api/dev/iccid?deviceId=866610001099679&iccid=89860002192307889057
-                url =None
-                url = (constants.DMP_BASE + "/dev/iccid?deviceId={}&iccid={}").format(modem.getDevImei(),iccid)
-                print("{} H:{}".format(url,headers_T))
-                response = request.post(url,headers = headers_T)
-                if response.status_code == 200:
-                    data = response.json()
-                    print("pots_ICCID:{}".format(data))
-                else:
-                    print("pots_ICCID  error!!!!")
-                    for i in response.text:
-                        print(i)
-                response.close()
-        except Exception as e:
-            usys.print_exception(e)
-            if response != None:
-                response.close()
-    #获取新的令牌 更新token
-    def update_dev_token(self, event=None, msg=None):
-        print("update_dev_token")
-        try:
-            dev_pass = ""
-            dev_pass = EventMesh.publish("get_storage_config","dev_pass")
-            
-            if dev_pass == None:
-                dev_pass = ""
-            if len(dev_pass) == 0: #没有密码走激活
-                print("no dev_pass:{}\r\n".format(dev_pass))
-                EventMesh.publish("dev_active")
-            token = self.dev_token
-            if token == None:
-                token = ""
-            if len(token) == 0: #没有token走登入
-                print("no dev_token:{}\r\n".format(token))
-                #self.dev_sign_in()
-                EventMesh.publish("dev_sign_in")
-            dev_tokenip =None
-            dev_tokenip = constants.DMP_BASE + "/dev/auth/token"
-                     
-            token = self.dev_token
-            if token == None:
-                token = ""
-            if len(token) != 0:
-                response_result=0
-                response = None
-                headers_T = {"bz-dt":"{}".format(token)}
-                try:
-                    response = request.get(dev_tokenip,headers = headers_T,timeout=5)
-                    response_result = 1
-                except Exception as e:
-                    print("get error!!!!!\r\n")
-                    response_result = 0
-                if response_result == 1:
-                    if response.status_code == 200:
-                        try:
-                            headers_data = response.headers
-                            # print("headers_data!!!!! headers_data:{}\r\n".format(headers_data))
-                            if "bz-dt" in headers_data: #bz-dt字段是否在头部数据里面 如果有就需要更新
-                                bz_dt = headers_data["bz-dt"] 
-                                self.dev_token = bz_dt.replace(" ", "") #去掉所有的空格符
-                                print("update_dev_token:{}".format(self.dev_token))
-                        except Exception as e:
-                            print("headers_data!!!!! error\r\n")
-                if response != None:
-                    response.close()
-        except Exception as e:
-            usys.print_exception(e)   
-        print("update_dev_token end")
-
-
-
-        
-#OTA升级
-class OTA():
-    def __init__(self):
-        #EventMesh.subscribe("min_sys_ota",self.min_sys_ota)
-        self.downloadFileVoURL1= ""
-        self.downloadFileVoURL2= ""
-        self.downloadFileVoURLBT=""
-        self.BT_ota_size =0
-        self.ota_task_id  = None
-    #蓝牙升级升级,返回0,没有升级;返回,1有升级,正在执行升级;返回2,电压不够
-    def bt_ota(self,event=None,msg=None):
-        print("bt_ota_ota")
-        signal = net.csqQueryPoll()
-        if signal == -1 or sim.getStatus()  != 1:#网络不正常
-            print("bt_ota 无 SIM卡!!!!!!!!\r\n")
-            return 0
-        # return 0
-        response = None
-        ret_str =0
-
-        self.downloadFileVoURLBT=""
-        try:
-            #sourceVersion = versions.Build_Time
-            # sourceVersion = "7PIN-"+sourceVersion[:10]+"-"+sourceVersion[11:-3]
-            # print("sourceVersion:{}".format(sourceVersion))
-            # sourceVersion = sourceVersion.replace(" ", "0")
-            # print("sourceVersion2:{}".format(sourceVersion))
-            sourceVersion = version_util.get_unified_version()
-            print("sourceVersion:{}".format(sourceVersion))
-            bt_versions = EventMesh.publish("get_bt_versions")
-            if bt_versions == None:
-                bt_versions=""
-            print("bt_versions:{}".format(bt_versions))
-            #url = "http://iot.api.thisiot.com/ota/task?deviceId=860132060348836&sourceVersion=2024-12-20-15:35-1"
-            url =None
-            url = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}".format(modem.getDevImei(),sourceVersion)
-            print(url)
-            response = request.get(url,timeout=5)
-            if response.status_code == 200:
-                data = response.json()
-                print("OTA升级请求:{}".format(data))
-                
-                if data:
-                    try:
-                        if data["code"] == 20007:#有新版本
-                            if data["data"]["targetVersion"]  != bt_versions and "fileList"in data["data"]:
-                                #if len(data["data"]["fileList"]) == 2:
-                                    for fileList in data["data"]["fileList"]:
-                                        
-                                        if fileList["fileName"] == "update.ufw":
-                                            self.BT_ota_size = fileList["size"]
-                                            self.downloadFileVoURLBT = fileList["url"]
-                        if data["code"] == 20006:#已是最新版本
-                            ret_str = 0
-                                    
-                    except Exception as e:
-                            usys.print_exception(e)
-            else:
-                print("bt_ota get error")
-            response.close()
-        except Exception as e:
-            usys.print_exception(e)
-            if response != None:
-                response.close()
-        
-        print("downloadFileVoURLBT{}".format(self.downloadFileVoURLBT))
-        print("bt_ota end") 
-        if self.downloadFileVoURLBT !="":
-            battery = EventMesh.publish("get_battery")
-            if battery>=40:
-                self.ota_timer = osTimer()
-                #self.ota_timer.start(15000, 0, self.ota_timer_cb)
-                if self.ota_task_id ==None:
-                    self.ota_task_id = _thread.start_new_thread(self.ota_task,())
-                ret_str =  1  
-            else:
-                ret_str =  2
-        return ret_str
-    #小系统升级,返回0,没有升级;返回,1有升级,正在执行升级;返回2,电压不够
-    def min_sys_ota(self,event=None,msg=None):
-        print("min_sys_ota")
-        signal = net.csqQueryPoll()
-        if signal == -1 or sim.getStatus()  != 1:#网络不正常
-            print("min_sys_ota 无 SIM卡!!!!!!!!\r\n")
-            return 0
-        # return 0
-        response = None
-        ret_str =0
-        self.downloadFileVoURL1= ""
-        self.downloadFileVoURL2= ""
-        #self.downloadFileVoURLBT=""
-        try:
-            sourceVersion = version_util.get_unified_version()
-            print("sourceVersion:{}".format(sourceVersion))
-            url =None
-            url = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}".format(modem.getDevImei(),sourceVersion)
-            print(url)
-            response = request.get(url,timeout=5)
-            if response.status_code == 200:
-                data = response.json()
-                print("OTA升级请求:{}".format(data))
-                
-                if data:
-                    try:
-                        if data["code"] == 20007:#有新版本
-                            if "fileList"in data["data"]:
-                                #if len(data["data"]["fileList"]) == 2:
-                                    for fileList in data["data"]["fileList"]:
-                                        if fileList["fileName"] == "dfota_2.bin":
-                                            self.downloadFileVoURL2 = fileList["url"]
-                                        elif fileList["fileName"] == "dfota_1.bin":
-                                            self.downloadFileVoURL1 = fileList["url"]
-                                        elif fileList["fileName"] == "update.ufw":
-                                            self.BT_ota_size = fileList["size"]
-                                            self.downloadFileVoURLBT = fileList["url"]
-                        if data["code"] == 20006:#已是最新版本
-                            ret_str = 0
-                                    
-                    except Exception as e:
-                            usys.print_exception(e)
-            else:
-                print("min_sys_ota get error")
-            response.close()
-        except Exception as e:
-            usys.print_exception(e)
-            if response != None:
-                response.close()
-        
-        print("min_sys_ota:downloadFileVoURL1:{},\ndownloadFileVoURL2:{},\ndownloadFileVoURLBT{}".format(self.downloadFileVoURL1,self.downloadFileVoURL2,self.downloadFileVoURLBT))
-        print("min_sys_ota end") 
-        if self.downloadFileVoURL2!="" and self.downloadFileVoURL1!="" :
-            battery = EventMesh.publish("get_battery")
-            if battery>=40:
-                self.ota_timer = osTimer()
-                #self.ota_timer.start(15000, 0, self.ota_timer_cb)
-                if self.ota_task_id ==None:
-                    self.ota_task_id = _thread.start_new_thread(self.ota_task,())
-                ret_str =  1  
-            else:
-                ret_str =  2
-        return ret_str
-    
-    # def ota_timer_cb(self, *args, **kwargs):
-    #     EventMesh.publish("publish_AI_btnInfo",{"func":self.usb_event})
-
-    def ota_task(self):
-        import fota
-        utime.sleep_ms(15000)
-        count = 30#最多等待30秒
-        while 1:
-            count-=1
-            VC_satrt_flag = EventMesh.publish("get_VC_satrt_flag")#获取vc是否在运行
-            if VC_satrt_flag == None:
-                VC_satrt_flag = 0
-            voice_record_satrt_flag = EventMesh.publish("get_voice_record_flag")#获取静默或者语音留言是否在运行
-            if voice_record_satrt_flag == None:
-                voice_record_satrt_flag = 0
-            if VC_satrt_flag == 0 or count<=0 or voice_record_satrt_flag == 0:
-                break
-            utime.sleep_ms(100)
-        if self.downloadFileVoURLBT != "" and self.BT_ota_size != 0:
-            count = 5 #重试5次
-            while 1:
-                count -=1
-                ret = EventMesh.publish("bt_ota_run",[self.downloadFileVoURLBT,self.BT_ota_size ]) #升级蓝牙
-
-                if ret == 0 or count <=0:
-                    break
-                utime.sleep_ms(1000)
-            utime.sleep_ms(1000)
-            #EventMesh.publish("rest_TB") #复位蓝牙
-            utime.sleep_ms(50000)
-            if self.downloadFileVoURL1 == "" and self.downloadFileVoURL2 == "": #cat1没有升级的时候,要重启设备
-                Power.powerRestart()
-        if self.downloadFileVoURL1 != "" and self.downloadFileVoURL2 != "":
-            fota_obj = fota() 
-            fota_obj.httpDownload(url1=self.downloadFileVoURL1,url2=self.downloadFileVoURL2)
-    #ota升级结果返回,开机时调用一次即可
-    def ota_result(self,event=None,msg=None):
-        response = None
-        try:
-            currentVersion = version_util.get_unified_version()
-            print("currentVersion:{}".format(currentVersion))
-            url = (constants.DMP_BASE + "/ota/result?deviceId={}&currentVersion={}").format(modem.getDevImei(),currentVersion)
-            print(url)
-            response = request.post(url,timeout=5)
-            if response.status_code == 200:
-                data = response.json()
-                print("OTA升级结果上报:{}".format(data))
-            else:
-                print("ota_result  error!!!!")
-                for i in response.text:
-                    print(i)
-            response.close()
-        except Exception as e:
-            usys.print_exception(e)
-            if response != None:
-                response.close()
-    #ICCID上传
-    def pots_ICCID(self,event=None,msg=None):
-        response = None
-        try:
-            iccid = sim.getIccid()
-            if iccid != -1 and len(iccid) > 5:
-                print("pots_ICCID:{}".format(iccid))
-                #Headers = {'Transfer-Encodong':'chunked'}
-                token = EventMesh.publish("get_dev_token")
-                headers_T = None
-                if token:
-                    headers_T = {"bz-dt":"{}".format(token)}
-                #http://dmp.raisound.com/bz-api/dev/iccid?deviceId=866610001099679&iccid=89860002192307889057
-                url =None
-                url = (constants.DMP_BASE + "/dev/iccid?deviceId={}&iccid={}").format(modem.getDevImei(),iccid)
-                print("{} H:{}".format(url,headers_T))
-                response = request.post(url,headers = headers_T,timeout=5)
-                if response.status_code == 200:
-                    data = response.json()
-                    print("pots_ICCID:{}".format(data))
-                else:
-                    print("pots_ICCID  error!!!!")
-                    for i in response.text:
-                        print(i)
-                response.close()
-        except Exception as e:
-            usys.print_exception(e)
-            if response != None:
-                response.close()
-# 原 VC_State_* 整型已移除;会话/轮次见 VCTurnState / VCStateMachine(含 tts_rx_phase、speech_uplink_open)。
-
-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
-        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:
-                self.last_tts_completed_ms = 1
-        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)不应自动开麦
-                # 实际开麦是否允许由 _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 == "dialog":
-                            # 用户短按打断(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
-
-
-class AI_VC(Abstract):
-    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_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_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_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_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_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
-
-    # -------------------------------------------------------------------------
-    # 电源键 / AI 键:按 VC_mode 拆函数,便于阅读与修改(长对话 vs 单对话 PTT)
-    # -------------------------------------------------------------------------
-    def _handle_pwk_short_release(self, event=None, msg=None):
-        """电源键短按:读留言标志、退会议/翻译;否则 Close 结束对话(清 sid,下次再 Start)。"""
-        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
-        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 = ""
-        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 _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 _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})
-
-    # ===== Push-To-Talk(单对话模式)=====
-    # 语义:
-    # - 长按:无论当前处于 TTS/Thinking/任何中间态,立即打断并进入新一轮录音上行
-    # - 松开:停止录音并等待服务端返回(由 AI_client_VC_end_speech 触发)
-    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 _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 _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_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():
-                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 _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 _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 = ""
-
-    # ===== 状态机动作接口(骨架)=====
-    # 说明:统一把“副作用动作”收敛到这些方法,便于后续把 recv/timer 的 if/flag 逻辑迁移到 VCStateMachine。
-    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 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 _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
-
-    # AI
-
-    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
-        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._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_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_pwk_notice_flag(self,event,msg):#设置未读状态
-        self.pwk_notice_flag =  msg
-    #获取60S等待留言播放定时器状态
-    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 set_record_data_save_flag(self,event = None, msg = None):
-        self.record_data_save_flag = msg
-    def post_processor_after_instantiation(self):
-        pass
-    def get_AI_audio_queue(self, event, msg):
-        return self.AI_audio_queue
-    def get_VC_satrt_flag(self, event, msg):
-        return self.VC_satrt_flag
-    #检查有没有意外打开ai部分功能
-    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秒检查一下有没有关闭
-    #提交错误id           
-    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 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_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 get_audio_play_state(self, event=None, msg = None):
-        return self.audio_play_state
-    
-    
-    
-    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"])  
-        # self.chenggong_tone() #播放按键音
-
-    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:
-            pass
-        if self._ai_key_press_long_dialogue_interrupt():
-            return
-        try:
-            if self._ai_key_press_long_dialogue_when_session_ready():
-                return
-        except Exception:
-            pass
-        try:
-            if self._ai_key_press_long_dialogue_wake():
-                EventMesh.publish(constants.EVENT.SYSTEM_UP)
-                return
-        except Exception:
-            pass
-
-        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") #关闭语音录音
-        print("AI_key_press: unhandled mode branch")
-        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):#电源键短按,关闭
-        self._handle_pwk_short_release(event, msg)
-
-
-    def key_prompt(self):
-        EventMesh.publish("audio_montage_play",["key_prompt.mp3"])
-        #self.aud.play(1,1,'U:/static/key_prompt.mp3')#提示请您按住中间的按钮开始讲话,讲完松开即可
-    def VC_error(self, event, msg):#错误播报
-        self.websocket_error()
-        #EventMesh.publish("publish_AI_btnInfo",{"func":self.websocket_error}) 
-    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))
-    #开始录音,先缓存到buf,等服务器响应再发
-    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
-###################################################### VC ###################################################################################
-    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)
-        #self.AI_VC_send_data_queue_thread_close()#关闭发送线程
-        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)
-        self.AI_client_VC_isconnect = 1
-        print("websocket_connect_VC end")
-
-    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")    
-            # 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))
-    #websocket发送ping
-    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")
-
-    #websocket发送数据    
-    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              
-    #VC关闭读线程
-    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
-    #Ai VC websocket返回数据       
-    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
-                            # 播放灯由阶段事件控制
-                        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
-                            # 播完上报完全由 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 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 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 get_recv_voice(self,event,msg):
-        return self.recv_voice
-    def set_recv_voice(self,event,msg):
-        return self.recv_voice
-    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])
-        #print("下发音频数据2:{},{}".format(len(data[0]),self.tts_in_data_len))
-    #msg[0]是tts播放字符串,msg[1]是是否关闭当前tts
-    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 check_text_server(self,cmd):
-        return -1
-        
-    def check_text_llm(self,cmd):
-        try:
-                text = cmd["command"]["chinese_description"]
-                if text.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
-                if text.find("切换对话模式") != -1:
-                    if self.VC_mode == 0:
-                       self.VC_mode = 1
-                       self.AI_TTS_str = "了解,已切换到单对话模式" 
-                    else:
-                        self.VC_mode = 0 
-                        self.AI_TTS_str = "了解,已切换到长对话模式" 
-                    EventMesh.publish("set_storage_config",["VC_mode",self.VC_mode])
-
-
-                    return 0
-                
-                if text.find("音量调整") != -1:
-                        text = cmd["command"]["params_json"]
-                        text = ujson.loads(text)
-                        volume = text["n"]
-                        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 text.find("音量增大") != -1:
-                        print("self.VC_TTS_volume add:{}".format(self.VC_TTS_volume))
-                        self.VC_TTS_volume += 10
-                        if self.VC_TTS_volume > 100:
-
-                            self.AI_TTS_str = "音量已经是最大啦" 
-                            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)
-                            
-                        return 0
-                if text.find("音量减小") != -1:
-                    print("self.VC_TTS_volume end:{}".format(self.VC_TTS_volume))
-                    self.VC_TTS_volume -= 10
-                    if self.VC_TTS_volume < 0:
-                        self.VC_TTS_volume = 0
-                        #EventMesh.publish("audio_montage_play",["yinliangyizuixiao.mp3"])
-                        #self.aud.play(1,0,'U:/static/yinliangyizuixiao.mp3')#音量已最小
-                        self.AI_TTS_str = "音量已经是最小啦"
-                    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)
-                    return 0
-                
-                if text.find("语速增大") != -1:
-                        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 text.find("语速减小") != -1:
-                    self.speech_rate -= 10
-                    if self.speech_rate < -50:
-                        self.speech_rate = -50
-                        #EventMesh.publish("audio_montage_play",["yinliangyizuixiao.mp3"])
-                        #self.aud.play(1,0,'U:/static/yinliangyizuixiao.mp3')#音量已最小
-                        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 text.find("进入工厂模式") != -1 :
-                    self.factory_mode()
-                    return 1
-                if text.find("查询") != -1:
-                    if text.find("信号") != -1 :
-                        signal = net.csqQueryPoll()
-                        
-                        self.AI_TTS_str = "当前信号{}".format(signal)
-                        return 0
-                    if text.find("电量") != -1 :
-                        battery = EventMesh.publish("get_battery")
-                        print("电量查询:{}".format(battery))
-                        #EventMesh.publish("audio_montage_play",["dianliangshengyu{}.mp3".format(battery)])
-                        #self.aud.play(1,0,'U:/static/dianliangshengyu{}.mp3'.format(battery))#电量剩余
-                        self.AI_TTS_str = "电量剩余百分之{}".format(battery)
-                        return 0
-                    if text.find("电压") != -1 :
-                        Vbatt = Power.getVbatt()
-                        #EventMesh.publish("audio_montage_play",["dianliangshengyu{}.mp3".format(battery)])
-                        #self.aud.play(1,0,'U:/static/dianliangshengyu{}.mp3'.format(battery))#电量剩余
-                        self.AI_TTS_str = "当前电池电压{}".format(Vbatt)
-                        return 0
-
-                    if text.find("卡号") != -1 :
-                        iccid = sim.getIccid()
-                        if iccid != -1 and len(iccid) > 5:
-                            print("iccid{}".format(iccid))
-                            self.AI_TTS_str = "当前卡号为:{}".format(iccid)
-                        else:
-                            self.AI_TTS_str = "卡号查询失败"
-                        return 0
-                    if text.find("设备号") != -1 :
-                        imei = modem.getDevImei()
-                        if imei != -1 and len(imei) > 5:
-                            print("IMEI:{}".format(imei))
-                            self.AI_TTS_str = "当前设备号为:{}".format(imei)
-                        else:
-                            self.AI_TTS_str = "设备号查询失败"
-                        return 0
-                    if text.find("版本") != -1 :
-                        bt_versions = EventMesh.publish("get_bt_versions")
-                        if bt_versions =="" :
-                            bt_versions = "BT_NONE"
-                        self.AI_TTS_str ="当前版本号:"+version_util.get_tts_version(bt_versions)
-                        return 0
-                    if text.find("服务器地址") != -1 :
-                        self.AI_TTS_str ="服务器地址"+self.server_ip
-                        return 0
-                if text.find("升级") != -1 and (text.find("OTA") != -1 or text.find("ota") != -1):
-                    if self.ai_OTA.min_sys_ota()==1:
-                        self.AI_TTS_str = "有新版本,准备开始升级,升级过程会多次重启设备,请保证流量充足及电量充足"
-                    else:
-                        self.AI_TTS_str = "当前已是最新版本"
-                    return 0
-                
-                if len(text)<20 and (text.find("再见") != -1 or text.find("拜拜") != -1 or text.find("关闭对话") != -1):
-                    #EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) 
-                    self.AI_TTS_str = "好嘞,再见,我们下次再聊"
-                    self.Close_flag = 1
-                    return 0
-                if len(text)<12 and (text.find("关机") != -1 ):
-                    EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) 
-                    EventMesh.publish("Power_off")
-                    return 1
-                if text.find("闹钟设定") != -1:
-                    
-                    # {
-                    #     "event":"command",
-                    #     "payload":{
-                    #         "dialog_id":"d5ef6fc424834bc4915107c76b2c6d7e_1750671489126798340",
-                    #         "functions":[
-                    #         {
-                    #             "id":"56867702528670853",
-                    #             "type":"function",
-                    #             "function":{
-                    #                 "name":"device_action",
-                    #                 "arguments":"{\"command\":{\"chinese_description\":\"闹钟设定\",\"english_description\":\"alarm\",\"fail_prompt\":\"设定闹钟失败\",\"params_json\":\"{\\\"alarm\\\": [{\\\"alarm_content\\\": \\\"无\\\", \\\"alarm_timer\\\": \\\"7:00\\\", \\\"alarm_type\\\": \\\"单次\\\", \\\"repeat_day\\\": [\\\"2025-06-24\\\"], \\\"repeat_min\\\": 0, \\\"valid_time\\\": \\\"\\\"}]}\",\"succeed_prompt\":\"设定闹钟成功\"}}"
-                    #             }
-                    #         }
-                    #     ]
-                    #     },
-                    #     "sid":"d5ef6fc424834bc4915107c76b2c6d7e"
-                    # }
-
-                    text = cmd["command"]["params_json"]
-                    print("params_json:{}".format(text))
-
-                    try:
-                        alarm =  ujson.loads(text)
-                        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)
-                if text.find("取消闹钟") != -1: #清空所有闹钟
-                    try:
-                        self.AI_TTS_str = EventMesh.publish("AI_delete_alarm")
-                    except Exception as e:
-                        usys.print_exception(e)
-                    return 0
-                if text.find("闹钟列表") != -1 :
-                    EventMesh.publish("read_alarm_list")
-                    return 0
-
-        except Exception as e:
-                usys.print_exception(e)
-        self.AI_TTS_str  = cmd["command"]["fail_prompt"]
-        return -1
-    def check_text(self,text):
-        print("check_text:len:{}".format(len(text)))
-        if len(text)>60:
-            return -1
-        try:
-            if isinstance(text, str): 
-                # if dev_type != 'jswm' and dev_type != 'jswm_BOX':#金三完美,不使用设备切换角色
-                    # if len(text)<25 and (text.find("你好") != -1 or text.find("您好") != -1) :
-                    #     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 = "嗨,{}来了,有什么吩咐".format(self.VC_TTS_voice_key)
-                    #                 return 0
-                    #     except Exception as e:
-                    #         usys.print_exception(e) 
-                    #     return -1
-                    # if text.find("声音") != -1 and text.find("改") != -1:
-                    #     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])
-                    #             # EventMesh.publish("audio_montage_play",["peizhichenggong.mp3"])
-                    #             #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#切换成功
-                    #             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])
-                    #             # EventMesh.publish("audio_montage_play",["peizhichenggong.mp3"])
-                    #             #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#切换成功
-                    #             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])
-                    #             # EventMesh.publish("audio_montage_play",["peizhichenggong.mp3"])
-                    #             #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#切换成功
-                    #             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])
-                    #             # EventMesh.publish("audio_montage_play",["peizhichenggong.mp3"])
-                    #             #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#切换成功
-                    #             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])
-                    #             # EventMesh.publish("audio_montage_play",["peizhichenggong.mp3"])
-                    #             #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#切换成功
-                    #             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])
-                    #             # EventMesh.publish("audio_montage_play",["peizhichenggong.mp3"])
-                    #             #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#切换成功
-                    #             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])
-                    #             # EventMesh.publish("audio_montage_play",["peizhichenggong.mp3"])
-                    #             #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#切换成功
-                    #             self.AI_TTS_str = "更改成功" 
-                    #             return 0
-                    #     self.AI_TTS_str = "更改失败,没找到对应声音" 
-                    #     return 0
-                if text.find("切换对话模式") != -1:
-                    if self.VC_mode == 0:
-                        self.VC_mode = 1
-                        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
-                        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])
-                    # EventMesh.publish("audio_montage_play",["peizhichenggong.mp3"])
-                    #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#切换成功
-
-                    return 0
-                if text.find("音量") != -1:
-                    if  text.find("加") != -1 :
-                        self.VC_TTS_volume += 10
-                        if self.VC_TTS_volume > 100:
-                            #EventMesh.publish("audio_montage_play",["yinliangyizuida.mp3"])
-                            #self.aud.play(1,0,'U:/static/yinliangyizuida.mp3')#音量已最大
-                            self.AI_TTS_str = "音量已最大" 
-                            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)
-                            #EventMesh.publish("audio_montage_play",["peizhichenggong.mp3"])
-                            #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#配置成功
-                            self.AI_TTS_str = "配置成功,当前音量 {}".format(self.VC_TTS_volume)
-                          
-                        return 0
-                    if text.find("减") != -1:
-                        self.VC_TTS_volume -= 10
-                        if self.VC_TTS_volume < 0:
-                            self.VC_TTS_volume = 0
-                            #EventMesh.publish("audio_montage_play",["yinliangyizuixiao.mp3"])
-                            #self.aud.play(1,0,'U:/static/yinliangyizuixiao.mp3')#音量已最小
-                            self.AI_TTS_str = "音量已最小"
-                        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)
-                            #EventMesh.publish("audio_montage_play",["peizhichenggong.mp3"])
-                            #self.aud.play(1,0,'U:/static/peizhichenggong.mp3')#配置成功
-                            self.AI_TTS_str = "配置成功,当前音量 {}".format(self.VC_TTS_volume)
-                        return 0
-                
-                if text.find("进入工厂模式") != -1 :
-                    self.factory_mode()
-                    return 1
-                if text.find("查询") != -1:
-
-                    if text.find("信号") != -1 :
-                        signal = net.csqQueryPoll()
-                        
-                        self.AI_TTS_str = "当前信号{}".format(signal)
-                        return 0
-                    if text.find("电量") != -1 :
-                        battery = EventMesh.publish("get_battery")
-                        print("电量查询:{}".format(battery))
-                        #EventMesh.publish("audio_montage_play",["dianliangshengyu{}.mp3".format(battery)])
-                        #self.aud.play(1,0,'U:/static/dianliangshengyu{}.mp3'.format(battery))#电量剩余
-                        self.AI_TTS_str = "电量剩余百分之{}".format(battery)
-                        return 0
-                    if text.find("电压") != -1 :
-                        Vbatt = Power.getVbatt()
-                        #EventMesh.publish("audio_montage_play",["dianliangshengyu{}.mp3".format(battery)])
-                        #self.aud.play(1,0,'U:/static/dianliangshengyu{}.mp3'.format(battery))#电量剩余
-                        self.AI_TTS_str = "当前电池电压{}".format(Vbatt)
-                        return 0
-
-                    # if text.find("卡号") != -1 :
-                    #     iccid = sim.getIccid()
-                    #     if iccid != -1 and len(iccid) > 5:
-                    #         print("iccid{}".format(iccid))
-                    #         self.AI_TTS_str = "当前卡号为:{}".format(iccid)
-                    #     else:
-                    #         self.AI_TTS_str = "卡号查询失败"
-                    #     return 0
-                    # if text.find("设备号") != -1 :
-                    #     imei = modem.getDevImei()
-                    #     imei = num_str_to_chinese(imei)#转为中文
-                    #     if imei != -1 and len(imei) > 5:
-                    #         print("IMEI:{}".format(imei))
-                    #         self.AI_TTS_str = "当前设备号为:{}".format(imei)
-                    #     else:
-                    #         self.AI_TTS_str = "设备号查询失败"
-                    #     return 0
-                    if text.find("版本") != -1 :
-                        bt_versions = EventMesh.publish("get_bt_versions")
-                        if bt_versions ==None:
-                            bt_versions = "BT_NONE"
-                        self.AI_TTS_str ="当前版本号:"+version_util.get_tts_version(bt_versions)
-
-                        return 0
-                    if text.find("服务器地址") != -1 :
-                        self.AI_TTS_str ="服务器地址"+self.server_ip
-                        return 0
-                
-                if text.find("讲话") != -1 or text.find("说话") != -1:
-                    if text.find("快") != -1 and text.find("太") == -1 :
-                        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 text.find("慢") != -1 and text.find("太") == -1 :
-                        self.speech_rate -= 10
-                        if self.speech_rate < -50:
-                            self.speech_rate = -50
-                            #EventMesh.publish("audio_montage_play",["yinliangyizuixiao.mp3"])
-                            #self.aud.play(1,0,'U:/static/yinliangyizuixiao.mp3')#音量已最小
-                            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 len(text)<22 and(text.find("聆听") != -1 or text.find("明天")!= -1) :
-                    #print("text len :{}".format(len(text)))
-                    if text.find("快") != -1 and text.find("太") == -1 :
-                        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 text.find("慢") != -1 and text.find("太") == -1 :
-                        self.vad_silence += 200
-                        if self.vad_silence > 2000:
-                            self.vad_silence = 2000
-                            #EventMesh.publish("audio_montage_play",["yinliangyizuixiao.mp3"])
-                            #self.aud.play(1,0,'U:/static/yinliangyizuixiao.mp3')#音量已最小
-                            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 text.find("升级") != -1 and (text.find("OTA") != -1 or text.find("ota") != -1):
-                    if self.ai_OTA.min_sys_ota()==1:
-                        self.AI_TTS_str = "有新版本,准备开始升级,升级过程会多次重启设备,请保证流量充足及电量充足"
-                    else:
-                        self.AI_TTS_str = "当前已是最新版本"
-                    return 0
-                try:
-                    bt_voice_en = ("vc_config" in globals() and vc_config and vc_config.get_bool("BT_VOICE_FEATURES_ENABLED", False))
-                except Exception:
-                    bt_voice_en = False
-                if bt_voice_en:
-                    if text.find("语音留言") != -1:
-                        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)
-                        print("语音留言!!!!!!!!!!")
-                        return 0
-                    if text.find("开广播") != -1 or text.find("开蓝牙广播") != -1:
-                        self.AI_TTS_str = "正在开蓝牙广播"
-                        EventMesh.publish("AI_send_bt_ble_switch_toble",1)
-                        print("正在开蓝牙广播")
-                        return 0
-                    if text.find("关广播") != -1 or text.find("关蓝牙广播") != -1:
-                        self.AI_TTS_str = "正在关蓝牙广播"
-                        EventMesh.publish("AI_send_bt_ble_switch_toble",2)
-                        print("正在关蓝牙广播")
-                        return 0
-                    if text.find("开经典蓝牙") != -1:
-                        self.AI_TTS_str = "正在开经典蓝牙"
-                        EventMesh.publish("AI_send_bt_ble_switch_toble",3)
-                        print("正在开经典蓝牙")
-                        return 0
-                    if text.find("关闭经典蓝牙") != -1 or text.find("关经典蓝牙") != -1:
-                        self.AI_TTS_str = "正在关经典蓝牙"
-                        EventMesh.publish("AI_send_bt_ble_switch_toble",4)
-                        print("正在关经典蓝牙")
-                        return 0
-                    if text.find("确认解绑设备") != -1 or text.find("确认设备解绑") != -1:
-                        self.AI_TTS_str = "设备解绑中...."
-                        self.Close_flag = 1
-                        EventMesh.publish("send_unbind_dev_to_iot")
-                        print("解绑设备!!!!!!!!!!!!!!")
-                        return 0
-                    if text.find("删除耳机记录") != -1 or text.find("清楚耳机记录") != -1:
-                        self.AI_TTS_str = "正在删除"
-                        EventMesh.publish("clear_earphone_record")
-                        print("正在删除")
-                        return 0
-                # if len(text)<16 and (text.find("再见") != -1 or text.find("拜拜") != -1 or text.find("关闭对话") != -1):
-                #     #EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) 
-                #     if dev_type != "7PIN" and dev_type != "7PIN_coze" and dev_type != "7PIN_1_5":#柒牌
-                #         self.AI_TTS_str = "好嘞,再见,我们下次再聊"
-                #         self.Close_flag = 1
-                #         return 0
-                #     else:
-                #         EventMesh.publish("publish_AI_btnInfo",{"func":self.Close}) 
-                #         #EventMesh.publish("sleep_BT")#休眠蓝牙
-                #         return 1
-                if len(text)<12 and (text.find("关机") != -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
-        except Exception as e:
-                usys.print_exception(e)
-        return -1
-    #获取trace_id
-    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 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
-        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)
-    #暂停/继续声音播放   Pause_flag = 1暂停
-    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 _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:
-            if getattr(self.fsm, "await_played", False):
-                # PTT(VC_mode==1) 也必须走本段:旧逻辑整段包在 VC_mode!=1 内会导致 tts.completed 后仍 await_played,
-                # 无人触发 VC_Play_End,绿灯/playing 卡死至按键。
-                # 不能只用 stream_is_empty:队列仍可能有待播数据时提前关播会“没声”;in/out 字节在分帧下可能未完全对齐,
-                # 在已 tts.completed 且队列已空时,以服务端结束下发 + PCM 空为准。
-                is_pcm_empty = (self.play_pcm == None) or (self.play_pcm != None and self.play_pcm.stream_is_empty() == 1)
-                try:
-                    q_empty = self.AI_audio_queue.empty()
-                except Exception:
-                    q_empty = True
-                try:
-                    in_len = getattr(self, "tts_in_data_len", 0)
-                    out_len = getattr(self, "tts_out_data_len", 0)
-                except Exception:
-                    in_len, out_len = 0, 0
-                try:
-                    tts_done_ms = getattr(self.fsm, "last_tts_completed_ms", 0)
-                except Exception:
-                    tts_done_ms = 0
-                drained_strict = (in_len > 0 and out_len >= in_len and q_empty)
-                drained_after_server = (bool(tts_done_ms) and q_empty)
-                drained = drained_strict or drained_after_server
-                # 8s 超时:兜底认为播放结束(避免播放状态卡死)
-                too_long = False
-                try:
-                    t0 = getattr(self.fsm, "last_tts_completed_ms", 0)
-                    if t0:
-                        too_long = utime.ticks_diff(utime.ticks_ms(), t0) > 8000
-                except Exception:
-                    pass
-                if (is_pcm_empty and drained) or too_long:
-                    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
-        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_kws_start(self):    
-    #     print("AI_kws_start")
-    #     EventMesh.publish("wakeup_BT")
-    #     EventMesh.publish("system_sleep_rest")#防止黑屏
-    #     url="ws://118.145.158.207:22080/wakeup/ws"
-    #     self.AI_client_kws = uwebsocket.Client.connect(url, debug=True)
-    #     self.AI_client_VC_recv_id = _thread.start_new_thread(self.AI_client_VC_recv, (self.AI_client_VC,))
-    #     self.VC_Speech_Start()#开启线程获取录音数据
-    #VC开始
-    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 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_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_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
-        # #self.Close()
-        # self.AI_VC_start() 
-    #TTS播放
-    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))
-    # 金三 V2.5:AudioPlayed — 端侧 TTS 播报结束后上报,服务端进入下一轮对话(轻度重同步,不断开 WebSocket)
-    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 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_vad_silence(self,event = None,msg= None):
-        self.vad_silence = msg
-        EventMesh.publish("set_storage_config",["vad_silence",self.vad_silence])  
-        # EventMesh.publish("publish_AI_btnInfo",{"func":self.play_new_voice_cb,"data":msg}) 
-        #设置tts参数
-    def set_wait_first_flag(self,event = None,msg= None):#设置未读语音播报初次记录flag
-        self.wait_first_flag = msg
-    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))
-    #修改音量大小 msg为1则为加大,msg为0则为减小,返回1成功,返回0失败
-    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 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 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])
-
-    #闹钟的url播放接口
-    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)   
-
-    #AI翻译接收TTS
-    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_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 chenggong_tone(self):
-        EventMesh.publish("audio_montage_play",["chenggong.mp3"])    
-        # self.aud.aud_tone_play(10,400)
-    def send_tone(self):
-        EventMesh.publish("audio_montage_play",["send_tone.mp3"])
-    def concurrency_limit_exceeded_tone(self):
-        self.Close()
-        EventMesh.publish("audio_montage_play",["chaobingfatis.mp3"])    #播放超并发提醒
-    #给服务器回应ack
-
-      
-    ############################################
-    #关闭音频播放线程
-    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:
-                                                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
-                                        #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")
-    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
-        # try:
-        #     while True:
-        #         if self.AI_WS_connect_Queue.empty()!=True:
-        #             AI_WS_connect = self.AI_WS_connect_Queue.get()
-        #             if AI_WS_connect :
-        #                 try:
-        #                     AI_WS_connect._close()
-        #                     AI_WS_connect = None
-        #                 except Exception as e:
-        #                     usys.print_exception(e)
-        #             else:
-        #                 print("websocket链接已为空")
-        #         else:
-        #             break
-        # except Exception as e:
-        #     usys.print_exception(e)
-
-    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()))
-        # num = EventMesh.publish("get_volum_size")
-        # if num == None:
-        #     num = 8
-        # self.aud.setVolume(num)#恢复原始音量
-        
-def num_str_to_chinese(num_str):
-    # 数字与中文映射(含零)
-    num_map = {
-        '0': '零', '1': '一', '2': '二', '3': '三', '4': '四',
-        '5': '五', '6': '六', '7': '七', '8': '八', '9': '九'
-    }
-    chinese = ''
-    # 逐位转换,拼接单位
-    for char in num_str:
-        if char in num_map:
-            digit = num_map[char]
-            chinese += digit
-        else:
-            chinese += char
-    
-    return chinese
-
+from common import Abstract, LogAdapter\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
+from machine import ExtInt\r
+from machine import WDT\r
+import modem\r
+import sim\r
+import net\r
+import base64\r
+import ucryptolib\r
+import urandom as random\r
+import request\r
+import ql_fs\r
+from misc import Power,USB,PowerKey\r
+import ntptime\r
+import machine\r
+import dataCall\r
+import constants\r
+import versions\r
+import version_util\r
+from machine import UART\r
+try:\r
+    import vc_config\r
+except Exception as e:\r
+    usys.print_exception(e)\r
+try:\r
+    import vc_text_intent\r
+except Exception:\r
+    vc_text_intent = None\r
+try:\r
+    import VAD\r
+except Exception as e:\r
+    usys.print_exception(e)\r
+try:   \r
+    import usr.ai_alarm as _ai_alarm\r
+except Exception as e:\r
+    usys.print_exception(e)\r
+    import ai_alarm as _ai_alarm\r
+PRESS_AUDIO_MODE = 0 #是按键音 0 是使用音频文件\r
+# dev_type = "G10"\r
+#dev_type = "7PIN_BK"#小瑞\r
+#dev_type = "7PIN_BK_coze"#k扣子小瑞\r
+#dev_type = "7PIN_BK_EU"#英文小瑞\r
+#dev_type = "7PIN_coze"#柒牌扣子\r
+dev_type = "7PIN"#柒牌(默认值,实际优先从 usr/default_config.py 覆盖)\r
+# dev_type = "7PIN_1_5"#柒牌1.5\r
+#dev_type = 'momo'#食梦貘\r
+#dev_type = 'sjgq'#四季甘泉\r
+#dev_type = 'szbst'#深圳百事通\r
+#dev_type = 'jswm'#金三完美\r
+#dev_type = 'jswm_BOX'#金三完美柒牌盒子\r
+#dev_type = 'csh'#慈善会\r
+\r
+# 统一从配置加载 DEV_TYPE(usr 覆盖优先)\r
+try:\r
+    if "vc_config" in globals() and vc_config:\r
+        dev_type = vc_config.get_dev_type(dev_type)\r
+except Exception:\r
+    pass\r
+\r
+# 1=打印 VC 周期性保活专用行「VC: ping ping」;默认 0(WS send 明细里已跳过 ping,无需再开此项)\r
+VC_HEARTBEAT_LOG = 0\r
+# 1=打印音频帧级日志:Record_len / send_data len / 下发音频长度;默认 0(只看 uplink START/END)\r
+# VC_AUDIO_FRAME_LOG = 0\r
+# 1=启用我们新增的协议层“严格门控/缓冲/兜底”行为;0=临时排除这些改动(用于定位协议影响)\r
+# 受控项(与对端 V2.5+ 能力对齐;关=回退旧端行为):\r
+# - 上行:收到 started 前缓冲/丢弃 PCM;PTT 模式下 audio.create 前禁发 PCM\r
+# - 兜底:llm.completed 后若久无 tts.started,端侧可重启会话(不做 force_tts)\r
+# 注意:audio.played 不受此开关控制——金三 V2.5 服务端依赖端侧播完后上报才能进入下一轮 ASR;\r
+#       若误关会导致问候结束后首轮用户说话无 vad/asr。\r
+VC_PROTO_GUARDS = 0\r
+# 1=打印 VC WebSocket 每条收发的完整文本(流量大、易刷屏);0 仅一行 event 摘要(llm.delta 省略)\r
+VC_WS_IO_LOG = 0\r
+# 长对话「无业务下行」idle:下列 JSON event/type 及裸文本 ping/pong 不重置 close_timerout。\r
+# 帧级 WebSocket PING/PONG 在 uwebsocket.recv() 内已处理,不会进入 AI_client_VC_recv。\r
+# 金三 V2.5 文档示例:WebSocket 文本帧 "ping" 保活;对端可能回 pong / heartbeat 类事件。\r
+_VC_KEEPALIVE_RX_EVENTS = frozenset(("pong", "ping", "heartbeat", "keepalive"))\r
+\r
+\r
+def _vc_out_json_event_meta(s):\r
+    """解析 WS 出站 JSON:event 可能在 header 或顶层(Start/audio.create vs synthesis)。"""\r
+    try:\r
+        jd = ujson.loads(s)\r
+        h = jd.get("header")\r
+        if isinstance(h, dict):\r
+            ev = h.get("event") or ""\r
+            if ev:\r
+                return ev, h.get("trace_id") or ""\r
+        ev = jd.get("event") or ""\r
+        return ev, ""\r
+    except Exception:\r
+        return "", ""\r
+\r
+\r
+class AI_led(Abstract):\r
+    def __init__(self):\r
+        try:\r
+            if "vc_config" in globals() and vc_config:\r
+                r_gpio, g_gpio, b_gpio = vc_config.led_gpio_tuple()\r
+            else:\r
+                r_gpio, g_gpio, b_gpio = Pin.GPIO36, Pin.GPIO21, Pin.GPIO19\r
+        except Exception:\r
+            r_gpio, g_gpio, b_gpio = Pin.GPIO36, Pin.GPIO21, Pin.GPIO19\r
+        self._led_gpio_r = r_gpio\r
+        self._led_gpio_g = g_gpio\r
+        self._led_gpio_b = b_gpio\r
+        self.led_red = Pin(r_gpio, Pin.OUT, Pin.PULL_PD, 0)\r
+        self.led_green = Pin(g_gpio, Pin.OUT, Pin.PULL_PD, 1)\r
+        self.led_blue = Pin(b_gpio, Pin.OUT, Pin.PULL_PD, 1)\r
+        print("AI_led GPIO dev_type={} red={} green={} blue={}".format(\r
+            dev_type, r_gpio, g_gpio, b_gpio))\r
+\r
+        # 统一输入状态(充电/电量/信号/AI对话),按优先级表输出\r
+        self._st_ai = "idle"         # idle/listening/thinking/playing\r
+        self._st_charging = "none"   # none/charging/full\r
+        self._st_battery = "normal"  # normal/low/empty\r
+        self._st_signal = "normal"   # normal/weak\r
+        self._st_alert = None        # None / rb_flash_2(REQ-ERR-01/03)\r
+\r
+        self._timer = osTimer()\r
+        self._mode = "off"  # off/red_on/green_on/green_blink/blue_blink/red_blink/bg_alt/alert_rb\r
+        self._alert_steps_left = 0\r
+        self._alert_rb_phase = 0\r
+        self._alert_period_ms = 250\r
+        self._blink_on = 0\r
+        self._bg_phase = 0\r
+        self._period_ms = 1000\r
+        self._gb_on_ms = 1000\r
+        self._gb_off_ms = 1000\r
+\r
+        EventMesh.subscribe("set_led_state", self.set_led_state)\r
+        EventMesh.subscribe("get_led_debug_state", self.get_led_debug_state)\r
+        # LED 电平极性:有些板子是低电平点亮(active-low),也可能仅某一路反相\r
+        self._inv_r = 0\r
+        self._inv_g = 0\r
+        self._inv_b = 0\r
+        try:\r
+            if "vc_config" in globals() and vc_config:\r
+                inv = vc_config.get_attr("LED_ACTIVE_LOW_RGB", None)\r
+                if isinstance(inv, (tuple, list)) and len(inv) == 3:\r
+                    self._inv_r = 1 if inv[0] else 0\r
+                    self._inv_g = 1 if inv[1] else 0\r
+                    self._inv_b = 1 if inv[2] else 0\r
+                else:\r
+                    all_inv = 1 if vc_config.get_bool("LED_ACTIVE_LOW", False) else 0\r
+                    self._inv_r = all_inv\r
+                    self._inv_g = all_inv\r
+                    self._inv_b = all_inv\r
+        except Exception:\r
+            pass\r
+        try:\r
+            self._apply_by_table()\r
+        except Exception:\r
+            try:\r
+                self._set_off()\r
+            except Exception:\r
+                pass\r
+\r
+    def _timer_stop(self):\r
+        try:\r
+            self._timer.stop()\r
+        except Exception:\r
+            pass\r
+\r
+    def _set_rgb(self, r, g, b):\r
+        try:\r
+            rr = 1 if r else 0\r
+            gg = 1 if g else 0\r
+            bb = 1 if b else 0\r
+            if self._inv_r:\r
+                rr = 0 if rr else 1\r
+            if self._inv_g:\r
+                gg = 0 if gg else 1\r
+            if self._inv_b:\r
+                bb = 0 if bb else 1\r
+            self.led_red.write(rr)\r
+            self.led_green.write(gg)\r
+            self.led_blue.write(bb)\r
+        except Exception:\r
+            pass\r
+\r
+    def _set_off(self):\r
+        self._mode = "off"\r
+        self._timer_stop()\r
+        self._set_rgb(0, 0, 0)\r
+\r
+    def _set_red_on(self):\r
+        self._mode = "red_on"\r
+        self._timer_stop()\r
+        self._set_rgb(1, 0, 0)\r
+\r
+    def _set_green_on(self):\r
+        self._mode = "green_on"\r
+        self._timer_stop()\r
+        self._set_rgb(0, 1, 0)\r
+\r
+    def _set_green_blink(self, on_ms=1000, off_ms=None):\r
+        """待机/空闲:绿灯慢闪(替代呼吸灯,数字 GPIO 整颗亮灭)。off_ms 缺省则与 on_ms 对称。"""\r
+        on_ms = int(on_ms or 1000)\r
+        if off_ms is None:\r
+            off_ms = on_ms\r
+        else:\r
+            off_ms = int(off_ms or on_ms)\r
+        try:\r
+            if (self._mode == "green_blink" and int(self._gb_on_ms) == int(on_ms)\r
+                    and int(self._gb_off_ms) == int(off_ms)):\r
+                return\r
+        except Exception:\r
+            pass\r
+        self._mode = "green_blink"\r
+        self._gb_on_ms = on_ms\r
+        self._gb_off_ms = off_ms\r
+        self._period_ms = on_ms\r
+        self._blink_on = 1\r
+        self._timer_stop()\r
+        self._set_rgb(0, 1, 0)\r
+        self._timer.start(self._gb_on_ms, 0, self._timer_cb)\r
+\r
+    def _set_blue_blink(self, period_ms=1000):\r
+        self._mode = "blue_blink"\r
+        self._period_ms = period_ms or 1000\r
+        self._blink_on = 1\r
+        self._timer_stop()\r
+        self._set_rgb(0, 0, 1)\r
+        self._timer.start(self._period_ms, 1, self._timer_cb)\r
+\r
+    def _set_red_blink(self, period_ms=1000):\r
+        self._mode = "red_blink"\r
+        self._period_ms = period_ms or 1000\r
+        self._blink_on = 1\r
+        self._timer_stop()\r
+        self._set_rgb(1, 0, 0)\r
+        self._timer.start(self._period_ms, 1, self._timer_cb)\r
+\r
+    def _set_bg_alt(self, period_ms=1000):\r
+        self._mode = "bg_alt"\r
+        self._period_ms = period_ms or 1000\r
+        self._bg_phase = 0\r
+        self._timer_stop()\r
+        self._set_rgb(0, 0, 1)\r
+        self._timer.start(self._period_ms, 1, self._timer_cb)\r
+\r
+    def _set_alert_rb_flash(self, cycles=2, period_ms=250):\r
+        """REQ-ERR-01/03:红蓝快闪(约 2 次/秒),结束后回到 _apply_by_table。"""\r
+        try:\r
+            cycles = int(cycles or 2)\r
+        except Exception:\r
+            cycles = 2\r
+        if cycles < 1:\r
+            cycles = 1\r
+        try:\r
+            period_ms = int(period_ms or 250)\r
+        except Exception:\r
+            period_ms = 250\r
+        if period_ms < 100:\r
+            period_ms = 100\r
+        self._mode = "alert_rb"\r
+        self._st_alert = "rb_flash_2"\r
+        self._alert_period_ms = period_ms\r
+        self._alert_steps_left = cycles * 2\r
+        self._alert_rb_phase = 0\r
+        self._timer_stop()\r
+        self._set_rgb(1, 0, 0)\r
+        self._timer.start(self._alert_period_ms, 0, self._timer_cb)\r
+\r
+    def _set_red_flash_limited(self, flashes=3, period_ms=500):\r
+        """REQ-BAT-03:红快闪若干次后熄灭(约 2 次/秒)。"""\r
+        try:\r
+            flashes = int(flashes or 3)\r
+        except Exception:\r
+            flashes = 3\r
+        if flashes < 1:\r
+            flashes = 1\r
+        try:\r
+            period_ms = int(period_ms or 500)\r
+        except Exception:\r
+            period_ms = 500\r
+        if period_ms < 100:\r
+            period_ms = 100\r
+        self._mode = "red_flash_n"\r
+        self._red_flash_steps_left = flashes * 2\r
+        self._alert_period_ms = period_ms\r
+        self._blink_on = 1\r
+        self._timer_stop()\r
+        self._set_rgb(1, 0, 0)\r
+        self._timer.start(self._alert_period_ms, 0, self._timer_cb)\r
+\r
+    def _timer_cb(self, *args, **kwargs):\r
+        EventMesh.publish("publish_AI_btnInfo", {"func": self._timer_cb_})\r
+\r
+    def _timer_cb_(self):\r
+        if self._mode == "green_blink":\r
+            self._blink_on = 0 if self._blink_on else 1\r
+            self._set_rgb(0, 1 if self._blink_on else 0, 0)\r
+            next_ms = self._gb_on_ms if self._blink_on else self._gb_off_ms\r
+            try:\r
+                self._timer.start(next_ms, 0, self._timer_cb)\r
+            except Exception:\r
+                pass\r
+            return\r
+        if self._mode == "blue_blink":\r
+            self._blink_on = 0 if self._blink_on else 1\r
+            self._set_rgb(0, 0, 1 if self._blink_on else 0)\r
+            return\r
+        if self._mode == "red_blink":\r
+            self._blink_on = 0 if self._blink_on else 1\r
+            self._set_rgb(1 if self._blink_on else 0, 0, 0)\r
+            return\r
+        if self._mode == "red_flash_n":\r
+            self._red_flash_steps_left -= 1\r
+            if self._red_flash_steps_left <= 0:\r
+                self._set_off()\r
+                return\r
+            self._blink_on = 0 if self._blink_on else 1\r
+            self._set_rgb(1 if self._blink_on else 0, 0, 0)\r
+            try:\r
+                self._timer.start(self._alert_period_ms, 0, self._timer_cb)\r
+            except Exception:\r
+                pass\r
+            return\r
+        if self._mode == "bg_alt":\r
+            if self._bg_phase == 0:\r
+                self._set_rgb(0, 1, 0)\r
+                self._bg_phase = 1\r
+            else:\r
+                self._set_rgb(0, 0, 1)\r
+                self._bg_phase = 0\r
+            return\r
+        if self._mode == "alert_rb":\r
+            self._alert_steps_left -= 1\r
+            if self._alert_steps_left <= 0:\r
+                self._st_alert = None\r
+                try:\r
+                    self._apply_by_table()\r
+                except Exception:\r
+                    self._set_off()\r
+                return\r
+            if self._alert_rb_phase == 0:\r
+                self._set_rgb(0, 0, 1)\r
+                self._alert_rb_phase = 1\r
+            else:\r
+                self._set_rgb(1, 0, 0)\r
+                self._alert_rb_phase = 0\r
+            try:\r
+                self._timer.start(self._alert_period_ms, 0, self._timer_cb)\r
+            except Exception:\r
+                pass\r
+            return\r
+\r
+    def set_led_state(self, event=None, msg=None):\r
+        # msg: dict {ai_state, charging, battery, signal, alert}\r
+        try:\r
+            if isinstance(msg, dict):\r
+                if "alert" in msg:\r
+                    aval = msg.get("alert")\r
+                    if aval in ("rb_flash_2", "rb_flash", "rb_flash2"):\r
+                        self._set_alert_rb_flash(2, 250)\r
+                        return\r
+                    if aval in ("red_flash_3", "red_flash3"):\r
+                        try:\r
+                            n = 3\r
+                            ms = 500\r
+                            if "vc_config" in globals() and vc_config:\r
+                                n = int(vc_config.get_int("BATTERY_EMPTY_FLASH_COUNT", 3))\r
+                                ms = int(vc_config.get_int("BATTERY_EMPTY_FLASH_PERIOD_MS", 500))\r
+                            self._set_red_flash_limited(n, ms)\r
+                        except Exception:\r
+                            self._set_red_flash_limited(3, 500)\r
+                        return\r
+                    if aval in (None, "", "none", "clear"):\r
+                        self._st_alert = None\r
+                if "ai_state" in msg:\r
+                    self._st_ai = msg.get("ai_state") or "idle"\r
+                if "charging" in msg:\r
+                    self._st_charging = msg.get("charging") or "none"\r
+                if "battery" in msg:\r
+                    self._st_battery = msg.get("battery") or "normal"\r
+                if "signal" in msg:\r
+                    self._st_signal = msg.get("signal") or "normal"\r
+        except Exception:\r
+            pass\r
+        if self._mode in ("alert_rb", "red_flash_n"):\r
+            return\r
+        if self._led_state_debug_enabled() and isinstance(msg, dict):\r
+            try:\r
+                print("[LED] set_led_state in={}".format(msg))\r
+            except Exception:\r
+                pass\r
+        self._apply_by_table(msg)\r
+\r
+    def _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 _led_log(self, tag, extra=""):\r
+        if not self._led_state_debug_enabled():\r
+            return\r
+        try:\r
+            print("[LED] {} ai={} chg={} bat={} sig={} -> mode={} period_ms={} {}".format(\r
+                tag, self._st_ai, self._st_charging, self._st_battery, self._st_signal,\r
+                self._mode, self._period_ms, extra))\r
+        except Exception:\r
+            pass\r
+\r
+    def _apply_by_table(self, _msg=None):\r
+        if self._mode in ("alert_rb", "red_flash_n"):\r
+            return\r
+        _prev_mode = self._mode\r
+        # P0:低电/空电(高于 AI 交互灯;充电中不显示,走充电灯态)\r
+        try:\r
+            if self._st_charging not in ("charging", "full"):\r
+                if self._st_battery == "empty":\r
+                    try:\r
+                        ms = 500\r
+                        if "vc_config" in globals() and vc_config:\r
+                            ms = int(vc_config.get_int("BATTERY_EMPTY_FLASH_PERIOD_MS", 500))\r
+                        self._set_red_blink(ms)\r
+                    except Exception:\r
+                        self._set_red_blink(500)\r
+                    return\r
+                if self._st_battery == "low":\r
+                    self._set_red_blink(1000)\r
+                    return\r
+        except Exception:\r
+            pass\r
+        # P1~P3:AI 阶段(播放 > 思考 > 聆听)\r
+        try:\r
+            if self._st_ai == "playing":\r
+                self._set_green_on()\r
+                return\r
+            if self._st_ai == "thinking":\r
+                # 蓝绿「交替」:约 0.5 s 换一色(`period_ms` 为换色间隔)\r
+                self._set_bg_alt(500)\r
+                return\r
+            if self._st_ai == "listening":\r
+                # 长对话蓝慢闪(≈1s);单对话 PTT 蓝快闪(≈2次/s,REQ-AI-07)\r
+                period_ms = 1000\r
+                try:\r
+                    if EventMesh.publish("get_storage_config", "VC_mode") == 1:\r
+                        period_ms = 250\r
+                except Exception:\r
+                    pass\r
+                self._set_blue_blink(period_ms)\r
+                return\r
+        except Exception:\r
+            pass\r
+        # AI 交互中(聆听/思考/播放/流播)不让充电灯抢占;交互结束 idle 后恢复充电红/充满绿(REQ 冲突规避)\r
+        _skip_charge_led = 0\r
+        try:\r
+            if self._st_ai in ("listening", "thinking", "playing"):\r
+                _skip_charge_led = 1\r
+        except Exception:\r
+            pass\r
+        if not _skip_charge_led:\r
+            try:\r
+                if EventMesh.publish("get_vc_stream_active") == 1:\r
+                    _skip_charge_led = 1\r
+            except Exception:\r
+                pass\r
+        # 基础态:充电/电量/信号\r
+        try:\r
+            if not _skip_charge_led:\r
+                if self._st_charging == "full":\r
+                    self._set_green_on()\r
+                    return\r
+                if self._st_charging == "charging":\r
+                    self._set_red_on()\r
+                    return\r
+            if self._st_signal == "weak":\r
+                # 弱网:蓝绿「慢交替」,与 thinking 同节拍定义(约 1 s 换一色)\r
+                self._set_bg_alt(1000)\r
+                return\r
+        except Exception:\r
+            pass\r
+        # 空闲待机:默认灭灯(REQ-BAT-01 / GAP-005 方案甲);LED_IDLE_MODE=green_blink 可恢复旧工程绿灯慢闪\r
+        try:\r
+            idle_mode = "off"\r
+            if "vc_config" in globals() and vc_config:\r
+                _im = vc_config.get_attr("LED_IDLE_MODE", None)\r
+                if _im is not None:\r
+                    idle_mode = str(_im).strip().lower()\r
+            if idle_mode in ("green_blink", "blink", "green"):\r
+                idle_on = 1000\r
+                if "vc_config" in globals() and vc_config:\r
+                    idle_on = int(vc_config.get_int("LED_IDLE_GREEN_BLINK_MS", 1000))\r
+                idle_off = idle_on\r
+                if "vc_config" in globals() and vc_config:\r
+                    _ioff = vc_config.get_attr("LED_IDLE_GREEN_BLINK_OFF_MS", None)\r
+                    if _ioff is not None:\r
+                        try:\r
+                            idle_off = int(_ioff)\r
+                        except Exception:\r
+                            idle_off = idle_on\r
+                if idle_on < 400:\r
+                    idle_on = 400\r
+                if idle_off < 400:\r
+                    idle_off = 400\r
+                self._set_green_blink(idle_on, idle_off)\r
+            else:\r
+                self._set_off()\r
+        except Exception:\r
+            self._set_off()\r
+        if self._mode != _prev_mode:\r
+            self._led_log("apply_table", "in={}".format(_msg))\r
+\r
+    def get_led_debug_state(self, event=None, msg=None):\r
+        # 供按键/状态机打印当前 LED 聚合状态与最终模式\r
+        try:\r
+            return {\r
+                "ai": self._st_ai,\r
+                "charging": self._st_charging,\r
+                "battery": self._st_battery,\r
+                "signal": self._st_signal,\r
+                "mode": self._mode,\r
+                "period_ms": self._period_ms,\r
+            }\r
+        except Exception:\r
+            return {"mode": getattr(self, "_mode", "?")}\r
+\r
+\r
+def _default_battery_ocv_map():\r
+    # 与 mgr.py BATTERY_OCV_TABLE nix_coy_mnzo2 默认一致;可被 default_config.BATTERY_OCV_MAP 覆盖\r
+    return {\r
+        4290: 100, 4237: 95, 4179: 90, 4123: 85, 4070: 80, 4020: 75, 3963: 70, 3919: 65, 3882: 60, 3847: 55,\r
+        3818: 50, 3794: 45, 3772: 40, 3752: 35, 3733: 30, 3716: 25, 3699: 20, 3677: 15, 3652: 10, 3629: 5, 3400: 0,\r
+    }\r
+\r
+\r
+def _parse_battery_ocv_map(raw):\r
+    """解析 default_config.BATTERY_OCV_MAP:dict 或 [(mv, soc), ...]。"""\r
+    if raw is None:\r
+        return None\r
+    out = {}\r
+    try:\r
+        if isinstance(raw, dict):\r
+            pairs = raw.items()\r
+        elif isinstance(raw, (tuple, list)):\r
+            pairs = ((p[0], p[1]) for p in raw if isinstance(p, (tuple, list)) and len(p) >= 2)\r
+        else:\r
+            return None\r
+        for k, v in pairs:\r
+            out[int(k)] = int(v)\r
+        return out if out else None\r
+    except Exception:\r
+        return None\r
+\r
+\r
+def _soc_from_vbatt_mv(volt_mv, ocv_map, ocv_volts, vbatt_simple=None):\r
+    """由电压(mV) 算 SoC(0~100);配置来自 default_config / _BatteryCfg。"""\r
+    try:\r
+        v = int(volt_mv)\r
+    except Exception:\r
+        return None\r
+    try:\r
+        if vbatt_simple:\r
+            _full, _v20, _v5 = vbatt_simple\r
+            if v >= int(_full):\r
+                return 100\r
+            if v <= int(_v5):\r
+                return 0\r
+            if v <= int(_v20):\r
+                return 10\r
+            return 60\r
+    except Exception:\r
+        pass\r
+    try:\r
+        volts = ocv_volts\r
+        if not volts:\r
+            return None\r
+        pre_volt = 0\r
+        volt_not_under = 0\r
+        soc1 = 0\r
+        soc2 = 0\r
+        for vv in volts:\r
+            if v > vv:\r
+                volt_not_under = 1\r
+                soc1 = ocv_map.get(vv, 0)\r
+                soc2 = ocv_map.get(pre_volt, 0)\r
+                break\r
+            else:\r
+                pre_volt = vv\r
+        if pre_volt == 0:\r
+            return soc1\r
+        if volt_not_under == 0:\r
+            return 0\r
+        return int(soc2 - (soc2 - soc1) * (pre_volt - v) // (pre_volt - vv))\r
+    except Exception:\r
+        return None\r
+\r
+\r
+class _BatteryCfg(object):\r
+    """从 vc_config(default_config) 加载电池参数,供 AI_hw_status / get_battery / AI_dev 共用。"""\r
+    _inst = None\r
+\r
+    def __init__(self):\r
+        self.ocv_map = _default_battery_ocv_map()\r
+        self.ocv_volts = sorted(self.ocv_map.keys(), reverse=True)\r
+        self.charge_full_mv = 4290\r
+        self.low_mv = 3699\r
+        self.empty_mv = 3629\r
+        self.low_soc = 20\r
+        self.empty_soc = 5\r
+        self.vbatt_simple = None\r
+        try:\r
+            if "vc_config" in globals() and vc_config:\r
+                self.charge_full_mv = int(vc_config.get_int("VBATT_CHARGE_FULL_MV", 4290))\r
+                self.low_mv = int(vc_config.get_int("VBATT_LOW_MV", 3699))\r
+                self.empty_mv = int(vc_config.get_int("VBATT_EMPTY_MV", 3629))\r
+                self.low_soc = int(vc_config.get_int("BATTERY_LOW_SOC_PERCENT", 30))\r
+                self.empty_soc = int(vc_config.get_int("BATTERY_EMPTY_SOC_PERCENT", 10))\r
+                _ocv_cfg = _parse_battery_ocv_map(vc_config.get_attr("BATTERY_OCV_MAP", None))\r
+                if _ocv_cfg:\r
+                    self.ocv_map = _ocv_cfg\r
+                    self.ocv_volts = sorted(self.ocv_map.keys(), reverse=True)\r
+                if vc_config.get_bool("USE_SIMPLE_VBATT_THRESH", False):\r
+                    self.vbatt_simple = (self.charge_full_mv, self.low_mv, self.empty_mv)\r
+        except Exception:\r
+            pass\r
+\r
+    @classmethod\r
+    def instance(cls):\r
+        if cls._inst is None:\r
+            cls._inst = cls()\r
+        return cls._inst\r
+\r
+    def soc_from_vbatt(self, volt_mv):\r
+        return _soc_from_vbatt_mv(volt_mv, self.ocv_map, self.ocv_volts, self.vbatt_simple)\r
+\r
+\r
+class AI_hw_status(Abstract):\r
+    # 精简硬件状态采集:仅维护 LED 状态机所需的 charging/battery/signal\r
+    def __init__(self):\r
+        # 无条件首行:确认是否进入初始化(避免被异常/注册顺序误判)\r
+        try:\r
+            print("[HW] __init__ enter")\r
+        except Exception:\r
+            pass\r
+        _bcfg = _BatteryCfg.instance()\r
+        self._bcfg = _bcfg\r
+        self._ocv_map = _bcfg.ocv_map\r
+        self._ocv_volts = _bcfg.ocv_volts\r
+        self._charge_full_mv = _bcfg.charge_full_mv\r
+        self._low_soc = _bcfg.low_soc\r
+        self._empty_soc = _bcfg.empty_soc\r
+        self._vbatt_simple = _bcfg.vbatt_simple\r
+        # cached state (avoid repeat publish)\r
+        self._chg = None\r
+        self._bat = None\r
+        self._sig = None\r
+        self._usb = None\r
+        self._charge_full_latched = 0\r
+        self._charge_full_streak = 0\r
+        self._charge_full_streak_need = 2\r
+        self._timer = osTimer()\r
+        self._period_ms = 10000\r
+        self._weak_csq = 10\r
+        self._dbg = 0\r
+        try:\r
+            if "vc_config" in globals() and vc_config:\r
+                self._period_ms = vc_config.get_int("HW_STATUS_PERIOD_MS", 10000) or 10000\r
+                self._weak_csq = vc_config.get_int("SIGNAL_WEAK_CSQ", 10) or 10\r
+                self._dbg = 1 if vc_config.get_bool("HW_STATUS_DEBUG", False) else 0\r
+        except Exception:\r
+            pass\r
+        # 若开启了 LED/按键调试,也一并打开硬件监控打印,便于关联分析\r
+        try:\r
+            if "vc_config" in globals() and vc_config:\r
+                if vc_config.get_bool("LED_DEBUG", False) or vc_config.get_bool("KEY_LED_DEBUG", False):\r
+                    self._dbg = 1\r
+        except Exception:\r
+            pass\r
+\r
+        # 注意:AI_dev 也会注册 USB callback;USB 模块通常只支持一个 callback,后注册会覆盖先注册。\r
+        # 为避免“刚开始有效,后面失效”,这里不直接 setCallback,改为订阅 AI_dev 转发的 USB 状态事件,\r
+        # 并在 10 秒轮询中再读一次 GET_USB_STATE 作兜底。\r
+        try:\r
+            EventMesh.subscribe("hw_usb_state", self._on_usb_state)\r
+        except Exception:\r
+            pass\r
+\r
+        # 初始化:先立即刷新一次,再开启周期轮询(避免 one-shot/覆盖差异导致只跑一次)\r
+        try:\r
+            self._update()\r
+        except Exception:\r
+            pass\r
+        try:\r
+            self._timer.start(self._period_ms, 1, self._tick_cb)\r
+        except Exception:\r
+            pass\r
+        # 初始化打印(一次)\r
+        try:\r
+            print("[HW] init ok period_ms={} weak_csq={} usb_init={} algo={}".format(\r
+                self._period_ms,\r
+                self._weak_csq,\r
+                self._usb,\r
+                "simple" if getattr(self, "_vbatt_simple", None) else "ocv"))\r
+            if self._dbg == 1:\r
+                print("[HW] init detail vbatt_simple={}".format(getattr(self, "_vbatt_simple", None)))\r
+        except Exception:\r
+            pass\r
+\r
+    def _on_usb_state(self, event=None, msg=None):\r
+        try:\r
+            state = msg\r
+            prev = self._usb\r
+            self._usb = 1 if state == 1 else 0\r
+            if self._usb == 1 and prev != 1:\r
+                self._charge_full_latched = 0\r
+                self._charge_full_streak = 0\r
+            if self._usb == 0:\r
+                self._charge_full_latched = 0\r
+                self._charge_full_streak = 0\r
+        except Exception:\r
+            pass\r
+        try:\r
+            if self._dbg == 1:\r
+                print("[HW] usb_evt state={} usb={}".format(state, self._usb))\r
+        except Exception:\r
+            pass\r
+        try:\r
+            self._update()\r
+        except Exception:\r
+            pass\r
+\r
+    def _tick_cb(self, *args, **kwargs):\r
+        # 定时器回调直接刷新(不依赖 AI_dev 的事件线程)\r
+        try:\r
+            self._tick()\r
+        except Exception:\r
+            pass\r
+\r
+    def _tick(self, *args, **kwargs):\r
+        self._update()\r
+\r
+    def _update(self):\r
+        # 每次刷新都“同步推送一次”当前硬件基础态到 LED(不依赖变化检测)\r
+        # 目的:保证 10 秒轮询和 USB callback 都能及时刷新充电/电量/信号的基础态显示\r
+        # charging/battery\r
+        try:\r
+            v = Power.getVbatt()\r
+        except Exception:\r
+            v = None\r
+        soc = None\r
+        csq = None\r
+        rssi = None\r
+        usb_status = None\r
+        chg = "none"\r
+        bat = "normal"\r
+        sig = "normal"\r
+        try:\r
+            usb_status = self._usb\r
+            if usb_status is None:\r
+                try:\r
+                    usb_status = EventMesh.publish("GET_USB_STATE")\r
+                    if usb_status is None:\r
+                        usb_status = 0\r
+                except Exception:\r
+                    usb_status = 0\r
+            # charging:插入后先红灯;勿用瞬时 vbatt≥满电阈值直接判满(充电器接入电压即偏高)\r
+            if usb_status == 1:\r
+                if getattr(self, "_charge_full_latched", 0) == 1:\r
+                    chg = "full"\r
+                else:\r
+                    if v is not None and v >= int(self._charge_full_mv):\r
+                        self._charge_full_streak = int(getattr(self, "_charge_full_streak", 0)) + 1\r
+                        need = int(getattr(self, "_charge_full_streak_need", 2) or 2)\r
+                        if self._charge_full_streak >= need:\r
+                            self._charge_full_latched = 1\r
+                            chg = "full"\r
+                        else:\r
+                            chg = "charging"\r
+                    else:\r
+                        self._charge_full_streak = 0\r
+                        chg = "charging"\r
+            # battery level for LED table\r
+            if usb_status != 1 and v is not None:\r
+                soc = self._bcfg.soc_from_vbatt(v)\r
+                if soc is not None:\r
+                    if soc < int(self._empty_soc):\r
+                        bat = "empty"\r
+                    elif soc < int(self._low_soc):\r
+                        bat = "low"\r
+            self._chg = chg\r
+            self._bat = bat\r
+        except Exception:\r
+            pass\r
+\r
+        # signal\r
+        try:\r
+            csq = net.csqQueryPoll()\r
+            # csqQueryPoll 返回 (rssi, ber) 或 int,按保守取 rssi\r
+            try:\r
+                if isinstance(csq, (tuple, list)) and len(csq) > 0:\r
+                    rssi = csq[0]\r
+                else:\r
+                    rssi = csq\r
+            except Exception:\r
+                rssi = csq\r
+            sig = "weak" if (rssi is not None and int(rssi) >= 0 and int(rssi) < int(self._weak_csq)) else "normal"\r
+            self._sig = sig\r
+        except Exception:\r
+            pass\r
+\r
+        # 统一推送一次当前基础态(一次调用带齐字段,减少来回切换)\r
+        try:\r
+            EventMesh.publish("set_led_state", {"charging": chg, "battery": bat, "signal": sig})\r
+        except Exception:\r
+            pass\r
+\r
+        # 轮询调试打印(每次 10s tick 一次):硬件采样一行 + LED 聚合状态一行内打完\r
+        try:\r
+            if self._dbg == 1:\r
+                led = None\r
+                try:\r
+                    led = EventMesh.publish("get_led_debug_state")\r
+                except Exception:\r
+                    pass\r
+                print("[HW] usb={} vbatt_mv={} soc={} chg={} bat={} csq={} rssi={} sig={} period_ms={} led={}".format(\r
+                    usb_status, v, soc, self._chg, self._bat, csq, rssi, self._sig, self._period_ms, led))\r
+        except Exception:\r
+            pass\r
+\r
+    def _soc_from_vbatt(self, volt_mv):\r
+        return self._bcfg.soc_from_vbatt(volt_mv)\r
+\r
+\r
+def set_ipv4_datacall():\r
+       # 获取第一路的APN,ipType 等信息\r
+       pdpCtx = dataCall.getPDPContext(1)\r
+       if pdpCtx != -1:\r
+               if pdpCtx[0] != 0:\r
+                       # 如果不是用户需要的ipv4,使用如下方式配置,使用如下方式配置\r
+                       ret = dataCall.setPDPContext(1, 0,pdpCtx[1],pdpCtx[2],pdpCtx[3],pdpCtx[4])\r
+                       if ret == 0:\r
+                               print('配置成功。')\r
+                               # cfun0/1后按照配置的信息进行拨号\r
+                               net.setModemFun(0)\r
+                               net.setModemFun(1)\r
+                       else:\r
+                               print('配置失败。')\r
+               else:\r
+                       print('已经配置为IPv4')\r
+       else:\r
+               print('获取PDP Context失败。')\r
+\r
+#HEARDER+TOTALLEN+DATELEN+DATA\r
+HEARDER = "FFFE"\r
+TOTALLEN = "00000000"\r
+DATELEN = "0000"\r
+DATA = {}\r
+class AI_UART():\r
+    #串口命令通道\r
+    def __init__(self):\r
+        self.send_count=0\r
+        self.AI_uart = UART(UART.UART4, 230400, 8, 0, 1, 0)\r
+        self.AI_uart.set_callback(self.uart_call)\r
+        \r
+        EventMesh.subscribe("bt_ota_run",self.bt_ota_run)#\r
+        EventMesh.subscribe("start_ota",self.start_ota)\r
+        EventMesh.subscribe("end_ota",self.end_ota)\r
+        EventMesh.subscribe("send_bt_switch_toble",self.send_bt_switch_toble)\r
+        EventMesh.subscribe("send_ble_switch_toble",self.send_ble_switch_toble)\r
+        EventMesh.subscribe("send_ble_scan",self.send_ble_scan)\r
+        EventMesh.subscribe("send_connect_bt_mac",self.send_connect_bt_mac)\r
+        EventMesh.subscribe("send_disconnect_bt_mac",self.send_disconnect_bt_mac)\r
+        EventMesh.subscribe("send_imei_to_ble",self.send_imei_to_ble)\r
+        EventMesh.subscribe("send_get_connect_bt_state",self.send_get_connect_bt_state)\r
+        EventMesh.subscribe("recv_bt_connect_rest",self.recv_bt_connect_rest)\r
+        EventMesh.subscribe("AI_send_bt_ble_switch_toble",self.AI_send_bt_ble_switch_toble)\r
+        EventMesh.subscribe("get_bt_versions",self.get_bt_versions)\r
+        EventMesh.subscribe("adjust_volume_toBle",self.adjust_volume_toBle)\r
+        EventMesh.subscribe("clear_earphone_record",self.clear_earphone_record)\r
+        self.bt_switch = 0\r
+        self.ble_switch = 0\r
+        self.uart_timer = osTimer()\r
+        self.get_bt_connect_timer = osTimer()\r
+        self.get_bt_connect_flag = 0\r
+        self.uart_timer_count = 0\r
+        self.bt_ota_flag = 0\r
+        self.read_ack_flag = 0\r
+        self.ping_json_str = {}\r
+        self.ping_json_str_tmp =""\r
+        self.read_uart_data = ""\r
+        self.bt_versions=""\r
+        try:\r
+            uart_ok = False\r
+            if "vc_config" in globals() and vc_config:\r
+                uart_ok = vc_config.uart_supported()\r
+        except Exception:\r
+            uart_ok = False\r
+        if uart_ok:\r
+            self.uart_send_queue = Queue(100)\r
+            self.uart_send_queue_thread_id = _thread.start_new_thread(self.uart_send_queue_thread, ())\r
+\r
+            self.uart_recv_queue = Queue(100)\r
+            self.uart_recv_queue_thread_id = _thread.start_new_thread(self.uart_recv_queue_thread, ())\r
+            self.send_get_versions()#获取蓝牙版本\r
+    \r
+    def uart_send_queue_thread(self):\r
+        while True:\r
+            data = self.uart_send_queue.get()\r
+            \r
+            #EventMesh.debug_log("Info_queue:data = {}".format(data))\r
+            if data != None:\r
+                try:\r
+                    if len(data) != 0:\r
+                        print("send_queu_uart_data2:{} data:{}".format(data,data["data"]))\r
+                        # json_str  =ujson.dumps(data)\r
+                        if data["event"] == "uart_send_data":\r
+                            print("send_queu_uart_data sucess data:{} {}".format(data["data"],type(data["data"])))\r
+                            self.uart_queue_send_data(data["data"])\r
+                        if "resend_flag" in data:\r
+                            print("send_reset_cb:{} {}".format(data["resend_flag"],data["ACK_time"]))\r
+                            if data["resend_flag"] == 1:\r
+                                self.send_count = 5#重试5次\r
+                                ack_timer = 0 #单次最大等待时间\r
+                                count = 0\r
+                                if "ACK_time" in data:\r
+                                    if data["ACK_time"] == None:\r
+                                        ack_timer = 100#最大重发5次,没ack或者返回error重发\r
+                                    else:\r
+                                        ack_timer = data["ACK_time"]\r
+                                if ack_timer > 0:\r
+                                    while self.send_count:\r
+                                        \r
+                                        if self.send_count <=0 or self.read_ack_flag == 1:\r
+                                            print("4G串口重发机制退出:{} self.read_ack_flag:{} json_str:{}".format(self.send_count,self.read_ack_flag,data["data"]))\r
+                                            break\r
+                                        if count >= ack_timer:\r
+                                            count = 0\r
+                                            print("串口异常,重发:")\r
+                                            self.uart_queue_send_data(data["data"])\r
+                                            self.send_count-=1\r
+                                        count += 1\r
+                                        utime.sleep_ms(1)\r
+\r
+                except Exception as e:\r
+                    usys.print_exception(e)\r
+                utime.sleep_ms(10)\r
+    \r
+    def uart_recv_queue_thread(self):\r
+        while True:\r
+            data = self.uart_recv_queue.get()\r
+            \r
+            #EventMesh.debug_log("Info_queue:data = {}".format(data))\r
+            if data != None:\r
+                try:\r
+                    if len(data) != 0:\r
+                        print("recv_queu_uart_data2:{}".format(data))\r
+                        # json_str  =ujson.dumps(data)\r
+                        # queue_data={"event":"uart_recv_data"}\r
+                        if data["event"] == "uart_recv_data":\r
+                            print("recv_queu_uart_data sucess data:{}".format(type(data["event"])))\r
+                            self.pares_uart_data()\r
+                except Exception as e:\r
+                    usys.print_exception(e)\r
+\r
+    def send_imei_to_ble(self,event,msg):\r
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.send_imei_to_ble_cb})\r
+\r
+    def send_imei_to_ble_cb(self):\r
+        self.read_ack_flag=0\r
+        imei  = modem.getDevImei()\r
+        json_data = \\r
+        {\r
+            \r
+            "event": "imei",\r
+            "data":str(imei)\r
+           \r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("send imei:{}".format(json_str))\r
+        self.send_data(json_str)\r
+        pass\r
+    def send_connect_bt_mac(self,event,msg):\r
+        self.read_ack_flag=0\r
+        json_data = \\r
+        {\r
+            "event": "connect_bt",\r
+            "data":msg\r
+        }\r
+        json_str =ujson.dumps(json_data)\r
+        self.send_data(json_str,1,2000)\r
+        # self.send_reset_cb(json_str,0,2000) #每2s发送一次 10ms为单位\r
+        pass\r
+    def send_disconnect_bt_mac(self,event,msg):\r
+        self.read_ack_flag=0\r
+        json_data = \\r
+        {\r
+            "event": "disconnect_bt",\r
+            "data":msg\r
+        }\r
+        json_str =ujson.dumps(json_data)\r
+        self.send_data(json_str,1)\r
+        # self.send_reset_cb(json_str)\r
+\r
+        pass\r
+    def uart_timer_cb(self, *args, **kwargs):\r
+        self.uart_timer_count+=1\r
+        if self.uart_timer_count >40:\r
+            self.read_uart_data = ""\r
+        if self.uart_timer_count >200:\r
+            self.read_uart_data = ""\r
+            self.uart_timer.stop()\r
+    def split_combined_json(self,combined_str):\r
+        # 将连续的JSON字符串分割为多个独立的JSON字符串列表\r
+        #\r
+        # 参数:\r
+        #     combined_str: 多个JSON拼接的字符串\r
+        # 返回:\r
+        #     分割后的\r
+        json_list = []\r
+        start_index = 0\r
+        brace_count = 0  # 用于匹配{}括号\r
+        print("combined_str:{}".format(combined_str))\r
+        for i, char in enumerate(combined_str):\r
+            if char == '{':\r
+                brace_count += 1\r
+            elif char == '}':\r
+                brace_count -= 1\r
+                \r
+                # 当括号完全匹配(count=0)时,说明一个完整的JSON对象结束\r
+                if brace_count == 0:\r
+                    # 提取从start_index到当前位置的子串(包含当前})\r
+                    json_str = combined_str[start_index:i+1].strip()\r
+                    if json_str:  # 避免空字符串\r
+                        json_list.append(json_str)\r
+                    start_index = i + 1  # 下一个JSON的起始位置\r
+        print("json_list{}".format(json_list))\r
+        \r
+        return json_list\r
+    \r
+    def uart_call(self,para):\r
+        queue_data={"event":"uart_recv_data"}\r
+\r
+        self.uart_recv_queue.put(queue_data)\r
+        # print("uart_call start")\r
+        # EventMesh.publish("publish_AI_btnInfo",{"func":self.pares_uart_data})\r
+        # print("uart_call end")\r
+        #print("ai_uart:{}".format(para))  \r
+    #[{"event":""},{},{}]\r
+    def pares_uart_data(self):  \r
+        uart_data = self.AI_uart.read()\r
+        self.uart_timer.start(10, 1, self.uart_timer_cb)\r
+        self.uart_timer_count = 0\r
+        ack_flag = 0#是否要返回ack\r
+        print("uart_data:{}",uart_data)\r
+        uart_data_str = uart_data.decode('utf-8')\r
+    \r
+        self.read_uart_data = self.read_uart_data +uart_data_str\r
+        try:\r
+            ujson.loads(self.read_uart_data)\r
+        except Exception as e:\r
+            print("json数据不完整:self.read_uart_data{}".format(self.read_uart_data))\r
+            usys.print_exception(e)\r
+            return\r
+        self.uart_timer.stop()\r
+        self.uart_timer_count = 0\r
+        self.playload_data(self.read_uart_data)\r
+\r
+        # 分割JSON\r
+        # json_strings = self.split_combined_json(uart_data_str)\r
+        \r
+        # # 逐个解析并处理\r
+        # for i, json_str in enumerate(json_strings, 1):\r
+        #     print("第{}个JSON{}".format(i,json_str))\r
+        #     try:\r
+        #         data = ujson.loads(json_str)\r
+             \r
+        #         # 在这里添加你的业务逻辑,比如根据event处理不同事件\r
+        #         self.uart_timer.stop()\r
+        #         self.uart_timer_count = 0\r
+        #         self.playload_data(self.read_uart_data)\r
+        #         # if data.get("event") == "bt_connect_single":\r
+                    \r
+        #         # elif data.get("event") == "bt_scan_rest":\r
+                    \r
+        #     except Exception as e:\r
+        #         print("JSON解析错误:原始字符串:")\r
+        #     print("-" * 50)\r
+\r
+        \r
+        \r
+        # print("uart_data_str:{}",uart_data_str)\r
+        # if len(uart_data_str) < (HEARDER+1+1):\r
+        #     return\r
+        # head = uart_data_str[0:4]\r
+        # pack_flag = uart_data_str[4:5]\r
+        # recvflag = uart_data_str[5:6]\r
+        \r
+        \r
+        # if head == HEARDER:\r
+        #     if pack_flag == "1": #分包数据\r
+        #         if recvflag == "1": #未发送完成\r
+        #             self.ping_json_str_tmp+=uart_data_str[6:]\r
+        #             pass\r
+        #         else:\r
+        #             self.playload_data(self.ping_json_str_tmp)\r
+        #             pass\r
+        #     else:\r
+        #         self.playload_data(uart_data_str[6:])\r
+                    \r
+        # else:\r
+        #    print("uart is not FFFE:") \r
+\r
+    def playload_data(self,msg):\r
+        ack_flag=0\r
+        try:\r
+            print("playload_data:{}".format(msg))\r
+            jsload_data =  ujson.loads(msg)\r
+        #print("AI_client_VC_recv jsload_data = {}".format(jsload_data))\r
+        \r
+            if jsload_data["event"] == "AI_KEY_PRESS" :#AI按键按下\r
+                EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_key_press})  \r
+                ack_flag = 1\r
+            elif jsload_data["event"] == "BT_AI_KEY_PRESS" :#蓝牙耳机key ai对话模式不支持长按对话模式 只支持短按按键对话模式 重新定义按键\r
+                EventMesh.publish("publish_AI_btnInfo",{"func":self.BT_AI_key_press})  \r
+                ack_flag = 1\r
+            elif jsload_data["event"] == "AI_PWR_PRESS" :#电源按键按下\r
+                EventMesh.publish("publish_AI_btnInfo",{"func":self.pwk_key_release})  \r
+                ack_flag = 1\r
+            elif jsload_data["event"] == "bt_scan_rest":#扫描列表 #蓝牙停止扫描后主动向4g发送扫描列表需要发送ack\r
+                EventMesh.publish("send_bt_scan_list",jsload_data)\r
+                ack_flag=1\r
+            elif jsload_data["event"]=="bt_connect_single": #收到蓝牙主动发送过来的单个连接蓝牙耳机的连接状态 需要发送ack\r
+                EventMesh.publish("save_bt_connect_list",jsload_data)\r
+                ack_flag=1\r
+                \r
+            elif jsload_data["event"]=="need_send_bt_connect_list": #发送连接蓝牙连接记录列表 不需要发送ack\r
+                self.get_bt_connect_timer.stop()\r
+                self.get_bt_connect_flag = 0\r
+                EventMesh.publish("save_bt_connect_list",jsload_data) #先保存连接记录再发送连接耳机列表to IOT\r
+                EventMesh.publish("send_bt_connect_list")\r
+               \r
+            elif jsload_data["event"]=="get_ble_switch":\r
+                ble_switch_flag =  EventMesh.publish("get_storage_config","switch_ble_state")#ble开关 不需要发送ack\r
+                if ble_switch_flag == None:\r
+                    ble_switch_flag = "1"\r
+                EventMesh.publish("send_ble_switch_toble",str(ble_switch_flag))\r
+                \r
+                pass\r
+            elif jsload_data["event"]=="get_bt_switch":\r
+                bt_switch_flag =  EventMesh.publish("get_storage_config","switch_bt_state")#bt开关 不需要发送ack\r
+                if bt_switch_flag == None:\r
+                    bt_switch_flag = "1"\r
+                EventMesh.publish("send_bt_switch_toble",bt_switch_flag)\r
+                \r
+                pass\r
+            elif jsload_data["event"]=="get_cat1_imei": #蓝牙主动向4G获取imei  4g不需要发送ack 蓝牙收到imei需要回ack给4改\r
+                EventMesh.publish("send_imei_to_ble")\r
+                ack_flag=1\r
+                pass\r
+            elif jsload_data["event"] == "post_bt_connect_rest": #蓝牙主动蓝牙耳机连接状态 需要发送ack给蓝牙\r
+                EventMesh.publish("recv_bt_connect_rest",jsload_data)\r
+                ack_flag=1\r
+\r
+            elif jsload_data["event"] == "clear_earphone_record_ok":#清除蓝牙耳机连接记录\r
+                tts_play_str = "删除成功"\r
+                EventMesh.publish("play_tts",[tts_play_str,1])\r
+                self.read_ack_flag = 1\r
+            \r
+            elif jsload_data["event"] == "versions":\r
+                self.bt_versions = jsload_data["data"]\r
+                print("当前蓝牙版本是:{}".format(self.bt_versions))\r
+                self.read_ack_flag = 1\r
+\r
+            # elif jsload_data["event"] == "kws" :#\r
+            #     EventMesh.publish("kws_even")\r
+            elif jsload_data["event"] == "ack" :#\r
+                self.read_ack_flag = 1\r
+            if ack_flag == 1:\r
+                self.send_ack()\r
+            self.read_uart_data = ""\r
+        except Exception as e:\r
+            usys.print_exception(e)\r
+            self.read_uart_data = ""\r
+    def recv_bt_connect_rest(self,event,msg):\r
+        date_tmp = utime.localtime()\r
+        time_tmp = utime.mktime(date_tmp)\r
+        if "data" in msg:\r
+            EventMesh.publish("send_bt_connect_state_to_iot",msg)\r
+            pass\r
+    def get_bt_versions(self,event=None,msg=None):\r
+        return self.bt_versions\r
+    def clear_earphone_record(self,event,msg): #清楚所有耳机记录\r
+\r
+        json_data = \\r
+        {\r
+            "event": "clear_earphone_record",\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("clear_earphone_record{}".format(json_str))\r
+        self.send_data(json_str,1)\r
+    def adjust_volume_toBle(self,event,msg): #\r
+        self.read_ack_flag=0\r
+        volume = int(msg)\r
+        if volume <= 10:\r
+            volume = 10\r
+        json_data = \\r
+        {\r
+            "event": "adjust_volume",\r
+            "data":str(volume)\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("adjust_volume_toBle{}".format(json_str))\r
+        self.send_data(json_str,1)\r
+\r
+    def AI_send_bt_ble_switch_toble(self,event,msg):\r
+        # // {\r
+        #     //     "event":"switch_bt_state",\r
+        #     //     "data":{\r
+        #     //         "switch_bt_state":"0"\r
+        #     //     }\r
+        #     // } #格式\r
+       \r
+        self.read_ack_flag=0\r
+        if msg == 1: #AI对话开启蓝牙\r
+            json_data = \\r
+            {\r
+                \r
+                "event": "switch_ble_state",\r
+                "data":{\r
+                    "switch_ble_state":"1"\r
+                }   \r
+            \r
+            }\r
+            json_str  =ujson.dumps(json_data)\r
+            print("switch_ble_state{}".format(json_str))\r
+            self.send_data(json_str,1)\r
+\r
+        elif  msg == 2: #AI对话开启蓝牙\r
+            json_data = \\r
+            {\r
+                \r
+                "event": "switch_ble_state",\r
+                "data":{\r
+                    "switch_ble_state":"0"\r
+                }   \r
+            \r
+            }\r
+            json_str  =ujson.dumps(json_data)\r
+            print("switch_ble_state{}".format(json_str))\r
+            self.send_data(json_str,1)\r
+        elif  msg == 3: #AI对话开启蓝牙\r
+            json_data = \\r
+            {\r
+                \r
+                "event": "switch_bt_state",\r
+                "data":{\r
+                    "switch_bt_state":"1"\r
+                }   \r
+            }\r
+            json_str  =ujson.dumps(json_data)\r
+            print("switch_bt_state{}".format(json_str))\r
+            self.send_data(json_str,1)\r
+\r
+        elif  msg == 4: #AI对话开启蓝牙\r
+            json_data = \\r
+            {\r
+                \r
+                "event": "switch_bt_state",\r
+                "data":{\r
+                    "switch_bt_state":"0"\r
+                }   \r
+            \r
+            }\r
+            json_str  =ujson.dumps(json_data)\r
+            print("switch_bt_state{}".format(json_str))\r
+            self.send_data(json_str,1)\r
+\r
+    def send_bt_switch_toble(self,event,msg):\r
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.send_bt_switch_toble_cb,"data":msg})\r
+    def send_bt_switch_toble_cb(self,data):\r
+        # // {\r
+        #     //     "event":"switch_bt_state",\r
+        #     //     "data":{\r
+        #     //         "switch_bt_state":"0"\r
+        #     //     }\r
+        #     // } #格式\r
+        self.read_ack_flag=0\r
+        json_data = \\r
+        {\r
+            \r
+            "event": "switch_bt_state",\r
+            "data":{\r
+                "switch_bt_state":str(data)\r
+            }   \r
+           \r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("send_bt_switch_toble{}".format(json_str))\r
+        self.send_data(json_str,1)\r
+\r
+    def send_ble_switch_toble(self,event,msg):\r
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.send_ble_switch_toble_cb,"data":msg})\r
+    def send_ble_switch_toble_cb(self,data):\r
+        # // {\r
+        #     //     "event":"switch_ble_state",\r
+        #     //     "data":{\r
+        #     //         "switch_ble_state":"0"\r
+        #     //     }\r
+        #     // } #格式\r
+    \r
+        self.read_ack_flag=0\r
+        json_data = \\r
+        {\r
+            "event": "switch_ble_state",\r
+            "data":{\r
+                "switch_ble_state":str(data)\r
+            }   \r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("send_ble_switch_toble{}".format(json_str))\r
+        self.send_data(json_str,1)    \r
+\r
+    # 重发机制\r
+    def send_reset_cb(self,json_str,need_resend = None,timeOut=None):\r
+        print("send_reset_cb:{} {} {}".format(json_str,need_resend,timeOut))\r
+        if timeOut == None:\r
+            self.send_count = 100#最大重发5次,没ack或者返回error重发\r
+        else:\r
+            self.send_count = timeOut\r
+\r
+        while self.send_count:\r
+            self.send_count-=1\r
+            utime.sleep_ms(10)\r
+            if self.send_count <=0 or self.read_ack_flag == 1:\r
+                print("4G串口重发机制退出:{} self.read_ack_flag:{} json_str:{}".format(self.send_count,self.read_ack_flag,json_str))\r
+                break\r
+            if self.send_count%(self.send_count//10) == 0:\r
+                print("串口异常,重发:")\r
+                self.send_data(json_str)\r
+\r
+        pass\r
+    def send_get_connect_bt_state(self,event,msg):\r
+        json_data = \\r
+        {\r
+            \r
+            "event": "get_connect_bt_state"\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("send_get_connect_bt_state{}".format(json_str))\r
+        self.send_data(json_str)\r
+        self.get_bt_connect_flag = 1\r
+        self.get_bt_connect_timer.start(2000,0,self.get_bt_connect_timer_cb)\r
+    def get_bt_connect_timer_cb(self,*agrc):\r
+        if self.get_bt_connect_flag == 1:\r
+            EventMesh.publish("send_bt_connect_list")\r
+            pass\r
+        self.get_bt_connect_flag = 0\r
+        pass\r
+    def send_ble_scan(self,event,msg):\r
+        self.read_ack_flag=0\r
+        # // {\r
+        #     //     "event":"send_ble_scan",\r
+        #     //     "data":{\r
+        #     //         "send_ble_scan":"0"\r
+        #     //     }\r
+        #     // } #格式\r
+        json_data = \\r
+        {\r
+            \r
+            "event": "bt_scan",\r
+            "data":msg \r
+           \r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("send_ble_scan{}".format(json_str))\r
+        self.send_data(json_str,1)\r
+        # self.send_reset_cb(json_str)\r
+\r
+        print("bt_send_end")\r
+    def send_data(self,str_data,need_resend = None,ACK_time =None):\r
+        try:\r
+            if not ("vc_config" in globals() and vc_config and vc_config.uart_supported()):\r
+                return\r
+        except Exception:\r
+            return\r
+        if self.bt_ota_flag == 1:\r
+            print("ota 中不需要上传")\r
+            return\r
+        print("send_data{} {} {}".format(str_data,need_resend,ACK_time))\r
+        queue_data={"event":"uart_send_data","data":str_data}\r
+        if need_resend != None:\r
+            queue_data={"event":"uart_send_data","data":str_data,"resend_flag":1,"ACK_time":ACK_time}\r
+\r
+        self.uart_send_queue.put(queue_data)\r
+    def uart_queue_send_data(self,str_data):\r
+        if self.bt_ota_flag == 1:\r
+            return\r
+        #EventMesh.publish("wakeup_BT")\r
+        print("bt_seuart_queue_send_data start {}".format(str_data))\r
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
+        self.AI_uart.write(str_data)\r
+        print("bt_seuart_queue_send_data end {}".format(str_data))\r
+    def led_r_open(self,event=None,msg=None):\r
+        json_data = \\r
+        {\r
+            \r
+            "event": "led_r_open",\r
+                \r
+           \r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("led_r_open{}".format(json_str))\r
+        self.send_data(json_str)\r
+    def led_g_open(self,event=None,msg=None):\r
+        json_data = \\r
+        {\r
+\r
+            "event": "led_g_open",\r
+\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("led_g_open{}".format(json_str))\r
+        self.send_data(json_str)\r
+    def led_b_open(self,event=None,msg=None):\r
+        json_data = \\r
+        {\r
+\r
+            "event": "led_b_open",\r
+   \r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("led_b_open{}".format(json_str))\r
+        self.send_data(json_str)\r
+    def led_rgb_open(self,event=None,msg=None):\r
+        json_data = \\r
+        {\r
+\r
+            "event": "led_rgb_open",\r
+\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("led_rgb_open{}".format(json_str))\r
+        self.send_data(json_str)\r
+    def led_close(self,event=None,msg=None):\r
+        json_data = \\r
+        {\r
+\r
+            "event": "led_close",\r
+\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("led_close{}".format(json_str))\r
+        self.send_data(json_str)\r
+        \r
+    def send_ack(self):\r
+        json_data = \\r
+        {\r
+\r
+            "event": "ack",\r
+\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("send_ack{}".format(json_str))\r
+        self.send_data(json_str)\r
+    def AI_key_press(self):\r
+        self.check_rest_timer_count = 0\r
+        print("AI_key_press")\r
+        EventMesh.publish("AI_key_press")\r
+    def BT_AI_key_press(self):\r
+        self.check_rest_timer_count = 0\r
+        print("BT_AI_key_press")\r
+        EventMesh.publish("BT_AI_key_press")\r
+\r
+    def pwk_key_release(self):\r
+        self.check_rest_timer_count = 0\r
+        #print("pwk_key_release")\r
+        EventMesh.publish("pwk_key_release")\r
+    def send_get_versions(self,event=None,msg=None):\r
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
+        #self.read_ack_flag = 0\r
+        json_data = \\r
+        {\r
+            \r
+            "event": "get_versions",\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        print("send_get_versions {}".format(json_str))\r
+        self.send_data(json_str,1)\r
+        #self.AI_uart.write(json_str)\r
+        count =1000\r
+        while True:\r
+            count -=1\r
+            if count <=0 or self.read_ack_flag == 1:\r
+                break\r
+            utime.sleep_ms(1)\r
+\r
+        pass\r
+    def start_ota(self,event=None,msg=None):#通过串口给蓝牙升级\r
+        #EventMesh.publish("wakeup_BT") \r
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
+        print("start_ota!!!")\r
+        print("start_ota!!!")\r
+        json_data = \\r
+        {\r
+            "event": "ota_start",\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        self.AI_uart.write(json_str)\r
+        print(json_str)\r
+        count =100\r
+        while True:\r
+            count -=1\r
+            if count <=0 or self.read_ack_flag == 1:\r
+                break\r
+            utime.sleep_ms(1)\r
+        print("start_ota count:{}!!!".format(count))\r
+\r
+    def end_ota(self,event=None,msg=None):#通过串口给蓝牙升级\r
+        #EventMesh.publish("wakeup_BT") \r
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
+        print("end_ota!!!")\r
+        print("end_ota!!!")\r
+        json_data = \\r
+        {\r
+            "event": "ota_end",\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        self.AI_uart.write(json_str)\r
+        print(json_str)\r
+        count =100\r
+        while True:\r
+            count -=1\r
+            if count <=0 or self.read_ack_flag == 1:\r
+                break\r
+            utime.sleep_ms(1)\r
+        print("end_ota count:{}!!!".format(count))\r
+\r
+    def bt_ota_run(self,event=None,msg=None):#通过串口给蓝牙升级\r
+        # EventMesh.publish("wakeup_BT") \r
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
+        print("bt_ota_run!!!:{}".format(msg))\r
+        print("bt_ota_run!!!")\r
+        print("bt_ota_run!!!")\r
+        self.bt_ota_flag = 1\r
+        self.read_ack_flag = 0\r
+        \r
+        json_data = \\r
+        {\r
+            "event": "ota_start",\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        self.AI_uart.write(json_str)\r
+        print(json_str)\r
+        count =2000#等20秒,擦除flash\r
+        while True:\r
+            count -=1\r
+            if count <=0 or self.read_ack_flag == 1:\r
+                break\r
+            utime.sleep_ms(10)\r
+            EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
+        print("ota_start:count:{}".format(count))\r
+        url = msg[0]\r
+        bt_size = msg[1]\r
+        headers_T = {"Content-Type":"application/octet-stream"}\r
+        response = request.get(url = url,headers=headers_T,timeout = 120,decode = False)\r
+        offset = 0\r
+        checksum = 0\r
+        for i in response.content:\r
+            EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
+            \r
+            \r
+            data = base64.b64encode(i)\r
+            send_count = 5#最大重发5次,没ack或者返回error重发\r
+            while 1:\r
+                send_count-=1\r
+                self.read_ack_flag = 0\r
+                checksum = 0\r
+                # 对每个字节进行异或运算\r
+                for byte in i:\r
+                    checksum ^= byte  # 等价于 checksum = checksum ^ byte\r
+                json_data = \\r
+                {\r
+                    "event": "ota_data",\r
+                    "update":data,\r
+                    "offset":offset,\r
+                    "checksum":checksum \r
+                }\r
+                print("offset:{},checksum:{}".format(offset,checksum))\r
+                json_str  =ujson.dumps(json_data)\r
+                self.AI_uart.write(json_str)\r
+                #utime.sleep_ms(100)\r
+                count =300\r
+                while True:\r
+                    count -=1\r
+                    if count <=0 or self.read_ack_flag != 0 :\r
+                        break\r
+                    utime.sleep_ms(10)\r
+                print("ota_data:{} count:{}".format(len(json_str),count))\r
+                if  self.read_ack_flag == 1:\r
+                    break\r
+                if send_count <=0 :\r
+                    return -1\r
+            offset+=len(i)\r
+        if offset < bt_size:\r
+            return -1\r
+        self.read_ack_flag = 0\r
+        json_data = \\r
+        {\r
+            "event": "ota_end",\r
+        }\r
+        json_str  =ujson.dumps(json_data)\r
+        self.AI_uart.write(json_str)\r
+        \r
+        count =100\r
+        while True:\r
+            count -=1\r
+            if count <=0 or self.read_ack_flag == 1:\r
+                break\r
+            utime.sleep_ms(1)\r
+        self.bt_ota_flag = 0\r
+        return 0\r
+audio_buf = ['chaobingfatis', 'chenggong', 'didianguanji', 'IMEIhao', 'jianquancuowu', 'key_prompt', 'liuliangtishi', 'Low_power_warning', 'R_CHARGE_FULL', 'R_CHARGE_IN', 'R_CHARGE_OUT', 'R_READ_TIME_0', 'R_READ_TIME_1', 'R_READ_TIME_2', 'R_READ_TIME_3', 'R_READ_TIME_4', 'R_READ_TIME_5', 'R_READ_TIME_6', 'R_READ_TIME_7', 'R_READ_TIME_8', 'R_READ_TIME_9', 'R_READ_TIME_A', 'R_READ_TIME_B', 'R_READ_TIME_C', 'R_READ_TIME_D', 'R_READ_TIME_E', 'R_READ_TIME_F', 'R_READ_TIME_G', 'R_READ_TIME_H', 'R_READ_TIME_I', 'R_READ_TIME_J', 'R_READ_TIME_K', 'R_READ_TIME_L', 'R_READ_TIME_M', 'R_READ_TIME_N', 'R_READ_TIME_O', 'R_READ_TIME_P', 'R_READ_TIME_Q', 'R_READ_TIME_R', 'R_READ_TIME_S', 'R_READ_TIME_T', 'R_READ_TIME_U', 'R_READ_TIME_V', 'R_READ_TIME_W', 'R_READ_TIME_X', 'R_READ_TIME_Y', 'R_READ_TIME_Z', 'send_tone', 'shouhou', 'xinhaoruo1', 'zhengzaiguanji']\r
+class AI_dev(Abstract):\r
+    # AI\r
+    def __init__(self):\r
+        # if(dev_type != "7PIN"):\r
+        #     set_ipv4_datacall()\r
+        self.is_wdt = 0 #是否开启看门狗\r
+        #AT+QDAI=1,0,0,1,0,0,1,1PCM音频输出设置为主模式、短同步、16 kHz采样率和256 kHz时钟频率,采样位深为16bit,使用的时间间隙数量为1,并且使用第一个时间间隙。\r
+        try:\r
+            bt_pm_en = False\r
+            pin_bt_on = None\r
+            pin_mot = None\r
+            qdai_cfg = None\r
+            if "vc_config" in globals() and vc_config:\r
+                bt_pm_en = bool(vc_config.get_attr("BT_POWER_MOTOR_ENABLED", False))\r
+                pin_bt_on = vc_config.get_attr("PIN_BT_ON_GPIO", None)\r
+                pin_mot = vc_config.get_attr("PIN_MOT_GPIO", None)\r
+                qdai_cfg = vc_config.get_attr("AUDIO_QDAI_CFG", None)\r
+        except Exception:\r
+            bt_pm_en = False\r
+            pin_bt_on = None\r
+            pin_mot = None\r
+            qdai_cfg = None\r
+\r
+        if bt_pm_en:\r
+            try:\r
+                if pin_bt_on:\r
+                    self.PIN_BT_ON = Pin(getattr(Pin, pin_bt_on), Pin.OUT, Pin.PULL_DISABLE, 0)\r
+                if pin_mot:\r
+                    self.PIN_MOT = Pin(getattr(Pin, pin_mot), Pin.OUT, Pin.PULL_DISABLE, 1)\r
+            except Exception:\r
+                pass\r
+            try:\r
+                utime.sleep_ms(200)\r
+                if hasattr(self, "PIN_MOT"):\r
+                    self.PIN_MOT.write(0)\r
+                if hasattr(self, "PIN_BT_ON"):\r
+                    self.PIN_BT_ON.write(1)\r
+            except Exception:\r
+                pass\r
+            try:\r
+                if isinstance(qdai_cfg, (tuple, list)) and len(qdai_cfg) == 8:\r
+                    audio.set_qdai_cfg(*qdai_cfg)  # 用iis做音频输入输出\r
+            except Exception:\r
+                pass\r
+        self.KeyAI_status = 0\r
+        self.KeyAI_timer_count = 0\r
+        self.KeyAI_long_press = 0\r
+        self.KeySos_long_press = 0\r
+        self.KeyAI_press = 0\r
+        self.last_ai_key_time = 0#让ai按键不要频繁调用\r
+        if self.is_wdt == 1:\r
+            self.wdt = WDT(30) #开启看门狗\r
+        # self.wdt_timer = osTimer()\r
+        # self.wdt_timer.start(5000, 1, self.wdt_timer_cb)\r
+        \r
+        count = 0\r
+        while True:\r
+            count += 1\r
+            if count > 20 :\r
+                break\r
+            data = dataCall.getInfo(1, 0)\r
+            print("data call INFO {}".format(data))\r
+            if data[2][0]:\r
+                self.network_state = 1\r
+                break\r
+            print("等待网络 {}...".format(count))\r
+            utime.sleep_ms(500)\r
+        self.KeyAI_timer = osTimer()\r
+        self.KeySOS_timer = osTimer()\r
+        self.play_imei_timer = osTimer()\r
+        self.play_imei_timer_count=0\r
+        self.KeySOS_timer_count = 0\r
+        self.KeySOS_timer_flag = 0\r
+        self._single_power_ai_merged = False\r
+        self._pwk_shutdown_ticks = 30  # 100ms 节拍,默认 3s\r
+        try:\r
+            ai_gpio = "GPIO22"\r
+            pwk_gpio = "GPIO26"\r
+            bt_wakeup_en = False\r
+            wakeup_cta1_gpio = None\r
+            wakeup_bt_gpio = None\r
+            if "vc_config" in globals() and vc_config:\r
+                raw_ai = vc_config.get_attr("EXTINT_AI_GPIO", "GPIO22")\r
+                if raw_ai is None or raw_ai == "":\r
+                    ai_gpio = None\r
+                else:\r
+                    ai_gpio = raw_ai\r
+                pwk_gpio = vc_config.get_attr("PWK_KEY_GPIO", "GPIO26") or "GPIO26"\r
+                bt_wakeup_en = bool(vc_config.get_attr("BT_WAKEUP_4G_ENABLED", False))\r
+                wakeup_cta1_gpio = vc_config.get_attr("EXTINT_WAKEUP_CTA1_GPIO", None)\r
+                wakeup_bt_gpio = vc_config.get_attr("PIN_WAKEUP_BT_GPIO", None)\r
+                try:\r
+                    self._single_power_ai_merged = bool(\r
+                        vc_config.get_bool("SINGLE_POWER_KEY_AI_MERGED", False)\r
+                    ) or (ai_gpio is None)\r
+                except Exception:\r
+                    self._single_power_ai_merged = ai_gpio is None\r
+                try:\r
+                    _pwk_ms = int(vc_config.get_int("PWK_LONG_PRESS_SHUTDOWN_MS", 3000))\r
+                except Exception:\r
+                    _pwk_ms = 3000\r
+                self._pwk_shutdown_ticks = max(1, (_pwk_ms + 99) // 100)\r
+        except Exception:\r
+            ai_gpio = "GPIO22"\r
+            pwk_gpio = "GPIO26"\r
+            bt_wakeup_en = False\r
+            wakeup_cta1_gpio = None\r
+            wakeup_bt_gpio = None\r
+\r
+        if ai_gpio:\r
+            self.extint_AI = ExtInt(\r
+                getattr(ExtInt, ai_gpio),\r
+                ExtInt.IRQ_RISING_FALLING,\r
+                ExtInt.PULL_PU,\r
+                self.keyAI_extint_cb,\r
+            )\r
+            self.extint_AI.enable()\r
+        else:\r
+            self.extint_AI = None\r
+\r
+        self.pwk_key = ExtInt(getattr(ExtInt, pwk_gpio), ExtInt.IRQ_RISING_FALLING, ExtInt.PULL_PU, self.pwk_extint_cb)\r
+\r
+        if bt_wakeup_en:\r
+            try:\r
+                if wakeup_cta1_gpio:\r
+                    self.extint_wakeup_cta1 = ExtInt(getattr(ExtInt, wakeup_cta1_gpio), ExtInt.IRQ_RISING, ExtInt.PULL_DISABLE, self.wakeup_extint_cb)\r
+                if wakeup_bt_gpio:\r
+                    self.PIN_wakeup_bt = Pin(getattr(Pin, wakeup_bt_gpio), Pin.OUT, Pin.PULL_DISABLE, 0)\r
+                self.extint_wakeup_cta1_timer = osTimer()\r
+                if hasattr(self, "extint_wakeup_cta1"):\r
+                    self.extint_wakeup_cta1.enable()\r
+            except Exception:\r
+                pass\r
+\r
+        self.pwk_key_status = 0\r
+        self.pwk_key_timer_count = 0\r
+        self.pwk_key_long_press = 0\r
+        self.pwk_key_press = 0\r
+        self.pwk_key_timer = osTimer()\r
+        self.pwk_is_double_timer = osTimer()\r
+        self.double_click_threshold = 1000#双击间隔时间\r
+        self.pwk_press_count = 0#按下次数\r
+        self.last_key_time = 0          # 上一次有效触发时间\r
+        self.key_debounce_interval = 20 # 防抖窗口20ms,标准值\r
+        #self.pwk_key = ExtInt(ExtInt.GPIO26, ExtInt.IRQ_RISING_FALLING, ExtInt.PULL_DISABLE, self.pwk_extint_cb)\r
+        \r
+        self.pwk_key.enable() \r
+        print("AI_dev:1")\r
+        self.btnInfo_queue = Queue(100)\r
+        self.btnInfo_queue_count = 0\r
+        _thread.stack_size(16*1024)\r
+        self.btnInfo_queue_thread_id = _thread.start_new_thread(self.btnInfo_queue_thread, ())\r
+        _thread.stack_size(8*1024)\r
+        EventMesh.subscribe("publish_AI_btnInfo",self.publish_AI_btnInfo)\r
+        EventMesh.subscribe("get_battery",self.get_battery)\r
+        EventMesh.subscribe("audio_montage_play",self.audio_montage_play)\r
+        # EventMesh.subscribe("paly_iccid_imei",self.paly_iccid_imei)\r
+        EventMesh.subscribe("paly_device_res_error",self.paly_device_res_error)\r
+        EventMesh.subscribe("Power_off",self.pwk_key_Long_Press)\r
+        EventMesh.subscribe("wakeup_BT",self.wakeup_BT)\r
+        EventMesh.subscribe("sleep_BT",self.sleep_BT)\r
+        EventMesh.subscribe("sys_sleep_cb",self.sys_sleep_cb)#系统休眠前回调\r
+        EventMesh.subscribe("sys_up_cb",self.sys_up_cb)#系统唤醒后回调\r
+        \r
+        EventMesh.subscribe("GET_USB_STATE",self.get_usb_state)\r
+        EventMesh.subscribe("SET_SPEECH_RATE",self.set_speech_rate)\r
+        EventMesh.subscribe("rest_TB",self.rest_TB)\r
+        \r
+        print("AI_dev:2")\r
+        self.pk = PowerKey()\r
+        self.pk.powerKeyEventRegister(self.powerKey_callback)\r
+        self.usb = USB()\r
+        self.usb.setCallback(self.usb_event_cb)\r
+        self.usb_status = self.usb.getStatus()\r
+        self.usb_charge_flag = 0\r
+        self._usb_evt_last_ms = 0\r
+        self._usb_evt_last_state = -1\r
+        self.Low_power_warning_timer = 0\r
+        self.AI_dev_timer = osTimer()\r
+        self.AI_dev_timer.start(10000, 1, self.AI_dev_timer_cb)\r
+        print("AI_dev:AI_dev_timer")\r
+        self.aud = audio.Audio(0)\r
+        self.Low_power_warning_flag = 0\r
+        self._low_warn_last_ms = 0\r
+        self._empty_shutdown_done = 0\r
+        self.VC_TTS_volume =  EventMesh.publish("get_storage_config","VC_TTS_volume")#音量配置\r
+        if self.VC_TTS_volume == None:\r
+            self.VC_TTS_volume = 100\r
+        self.aud.setVolume(self.VC_TTS_volume //10)\r
+        EventMesh.publish("set_storage_config",["VC_TTS_volume",self.VC_TTS_volume])\r
+        #self.aud.play(1,0,'U:/static/xianyikaiji.mp3')#开机音频\r
+        #EventMesh.publish("audio_montage_play",["xianyikaiji.mp3"])\r
+        #EventMesh.publish("publish_AI_btnInfo",{"func":self.ntptime_cb})\r
+        self.check_rest_timer_count = 0\r
+        try:\r
+            if "vc_config" in globals() and vc_config and vc_config.uart_supported():\r
+                self.ai_uart = AI_UART()\r
+        except Exception:\r
+            pass\r
+\r
+        self.Keycall1_press_count = 0\r
+        self.ble_switch_flag_time = osTimer()\r
+        self.ble_switch_flag_time.start(20000, 0, self.ble_switch_flag_time_init)\r
+        # AI_hw_status 先于本模块初始化,开机已插 USB 时需补同步一次\r
+        try:\r
+            if int(self.usb_status or 0) == 1:\r
+                EventMesh.publish("hw_usb_state", 1)\r
+                EventMesh.publish("publish_AI_btnInfo", {"func": self.usb_event, "data": 1})\r
+        except Exception:\r
+            pass\r
+    def ble_switch_flag_time_init(self,*args):\r
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.ble_switch_flag_time_cb})\r
+    def ble_switch_flag_time_cb(self,*args):\r
+        bt_switch_flag =  EventMesh.publish("get_storage_config","switch_bt_state")#bt开关\r
+        if bt_switch_flag == None:\r
+            bt_switch_flag = "1"\r
+        EventMesh.publish("send_bt_switch_toble",str(bt_switch_flag))\r
+        # EventMesh.publish("send_imei_to_ble")\r
+        ble_switch_flag =  EventMesh.publish("get_storage_config","switch_ble_state")#ble开关\r
+        if ble_switch_flag == None:\r
+            ble_switch_flag = "1"\r
+        EventMesh.publish("send_ble_switch_toble",str(ble_switch_flag))\r
+        vc_tts_volume = EventMesh.publish("get_storage_config","VC_TTS_volume")\r
+        if vc_tts_volume == None:\r
+            vc_tts_volume=100\r
+        # EventMesh.publish("adjust_volume_toBle",str(vc_tts_volume))\r
+\r
+        pass\r
+        print("ble_switch_flag_time_cb!!!!!!!")\r
+    def ntptime_cb(self):\r
+        #ntptime.settime(8)\r
+        print("timer:{}!!!!!!!!\r\n".format(utime.localtime()))\r
+    def usb_event_cb(self, state):\r
+        # USB 插拔抖动过滤(日志中 powerKey 与 usb_evt 0/1 连跳会导致误播 OUT/IN)\r
+        try:\r
+            now = utime.ticks_ms()\r
+            last_ms = getattr(self, "_usb_evt_last_ms", 0)\r
+            last_st = getattr(self, "_usb_evt_last_state", -1)\r
+            if last_ms and utime.ticks_diff(now, last_ms) < 800 and last_st != state:\r
+                return\r
+            self._usb_evt_last_ms = now\r
+            self._usb_evt_last_state = state\r
+        except Exception:\r
+            pass\r
+        # 转发给精简硬件监控(AI_hw_status),避免 USB callback 被覆盖导致“后面失效”\r
+        try:\r
+            EventMesh.publish("hw_usb_state", state)\r
+        except Exception:\r
+            pass\r
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.usb_event,"data":state})\r
+\r
+    def usb_event(self, state):    \r
+        status = state\r
+        self.usb_charge_flag = 0\r
+        if status == 1:#插入USB\r
+            print("charge R_CHARGE_in")\r
+            self.usb_status = 1\r
+            VC_satrt_flag = EventMesh.publish("get_VC_satrt_flag")#获取vc是否在运行\r
+            if VC_satrt_flag == None:\r
+                VC_satrt_flag = 0\r
+            \r
+            voice_record_satrt_flag = EventMesh.publish("get_voice_record_flag")#获取静默或者语音留言是否在运行\r
+            if voice_record_satrt_flag == None:\r
+                voice_record_satrt_flag = 0\r
+            if VC_satrt_flag == 0 and voice_record_satrt_flag == 0:\r
+                self.aud.stop()\r
+                #EventMesh.publish("flicker_led_red")#充电闪灯\r
+                #self.aud.play(1,1,'U:/static/R_CHARGE_IN.mp3')\r
+                try:\r
+                    if not ("vc_config" in globals() and vc_config and vc_config.get_bool("USE_MIN_HW_STATUS", True)):\r
+                        EventMesh.publish("set_led_state", {"charging": "charging"})\r
+                except Exception:\r
+                    pass\r
+                EventMesh.publish("audio_montage_play",["R_CHARGE_IN.mp3"])\r
+            self.Low_power_warning_flag  = 0\r
+            try:\r
+                self._empty_shutdown_done = 0\r
+            except Exception:\r
+                pass\r
+            pass\r
+        elif status == 0:#拨出usbs\r
+            self.usb_status = 0\r
+            VC_satrt_flag = EventMesh.publish("get_VC_satrt_flag")#获取vc是否在运行\r
+            if VC_satrt_flag == None:\r
+                VC_satrt_flag = 0\r
+            voice_record_satrt_flag = EventMesh.publish("get_voice_record_flag")#获取静默或者语音留言是否在运行\r
+            if voice_record_satrt_flag == None:\r
+                voice_record_satrt_flag = 0\r
+            if VC_satrt_flag == 0 and voice_record_satrt_flag == 0:\r
+                self.aud.stop()\r
+                try:\r
+                    if not ("vc_config" in globals() and vc_config and vc_config.get_bool("USE_MIN_HW_STATUS", True)):\r
+                        EventMesh.publish("set_led_state", {"charging": "none"})\r
+                except Exception:\r
+                    pass\r
+                EventMesh.publish("audio_montage_play",["R_CHARGE_OUT.mp3"])\r
+                #self.aud.play(1,1,'U:/static/R_CHARGE_OUT.mp3')\r
+    def get_usb_state(self,event,msg):\r
+        return self.usb_status\r
+    \r
+    def set_speech_rate(self,event,msg):\r
+        #msg的取值是0.5~2.0\r
+        #而语速初始值是0,范围是-50~50\r
+        rate = 0\r
+        if msg == 0.5:\r
+            rate = -50\r
+        elif msg < 1:\r
+            rate = (msg - 1)* 100\r
+        elif msg >= 1:\r
+            rate = (msg - 1)* 50\r
+        rate = round(rate)\r
+        print("速率已经设置为:{}".format(rate))\r
+        EventMesh.publish("rate_change",rate)\r
+        return rate\r
+    def btnInfo_queue_thread(self):\r
+        while True:\r
+            data = self.btnInfo_queue.get()\r
+            self.btnInfo_queue_count-=1\r
+            #EventMesh.debug_log("Info_queue:data = {}".format(data))\r
+            if data != None:\r
+                try:\r
+                    if "data" in data :\r
+                        data["func"](data["data"])\r
+                    else :\r
+                        data["func"]()\r
+                except Exception as e:\r
+                    #EventMesh.debug_log("btnInfo_queue:ERROR:{}".format(e))\r
+                    if "data" in data :\r
+                        print(data["data"])\r
+                    usys.print_exception(e)\r
+    def publish_AI_btnInfo(self,event,msg):\r
+        #print("publish_AI_btnInfo 1")\r
+        self.btnInfo_queue.put(msg)\r
+        self.btnInfo_queue_count+=1\r
+        if self.btnInfo_queue_count > 80:\r
+            self.btnInfo_queue_count =0\r
+            print("事件处理线程队列出问题,重启!!!!!!!!!!!!!!")\r
+            print("线程:{} 队列:{}".format(_thread.threadIsRunning(self.btnInfo_queue_thread_id),self.btnInfo_queue.size()))\r
+            # if _thread.threadIsRunning(self.btnInfo_queue_thread_id):\r
+            #     _thread.stop_thread(self.btnInfo_queue_thread_id)\r
+            # _thread.stack_size(10*1024)\r
+            # self.btnInfo_queue_thread_id = _thread.start_new_thread(self.btnInfo_queue_thread, ())\r
+            # _thread.stack_size(8*1024)\r
+            # self.btnInfo_queue.clear()\r
+            # self.btnInfo_queue = Queue(100)\r
+            EventMesh.publish("VC_error") #播放报错\r
+            utime.sleep_ms(1000)\r
+            self.aud.stopPlayStream()\r
+            self.aud.stopAll()\r
+            Power.powerRestart()\r
+            EventMesh.publish("set_storage_config",["powerRestart",1])\r
+        #print("publish_AI_btnInfo 2")\r
+    \r
+    def sys_sleep_cb(self,event=None,msg=None):#休眠前回调\r
+        try:\r
+            if not ("vc_config" in globals() and vc_config and vc_config.get_bool("BT_WAKEUP_4G_ENABLED", False)):\r
+                return\r
+        except Exception:\r
+            return\r
+        print("sys_sleep_cb!!!!!!!!!!!!!!!")\r
+        EventMesh.publish("sleep_BT")#休眠蓝牙\r
+        self.PIN_wakeup_bt.write(0)\r
+    def sys_up_cb(self,event=None,msg=None):#唤醒前回调\r
+        try:\r
+            if not ("vc_config" in globals() and vc_config and vc_config.get_bool("BT_WAKEUP_4G_ENABLED", False)):\r
+                return\r
+        except Exception:\r
+            return\r
+        print("sys_up_cb!!!!!!!!!!!!!!!")\r
+        self.wakeup_BT()\r
+        self.PIN_wakeup_bt.write(1)\r
+    def rest_TB(self,event=None,msg=None):\r
+        print("rest_TB!!!!!!!!!!!!!!!")\r
+        self.PIN_BT_ON.write(0)\r
+        utime.sleep_ms(100)\r
+        self.PIN_BT_ON.write(1)\r
+    def wakeup_BT(self,event=None,msg=None):#4G唤醒蓝牙\r
+        try:\r
+            if not ("vc_config" in globals() and vc_config and vc_config.get_bool("BT_WAKEUP_4G_ENABLED", False)):\r
+                return\r
+        except Exception:\r
+            return\r
+        print("wakeup_BT!!!!!!!!!!!!!!!")\r
+        wakeup_cta1_io = self.extint_wakeup_cta1.read_level()\r
+        if wakeup_cta1_io == 1:\r
+            return 1\r
+        self.PIN_wakeup_bt.write(0)\r
+        count = 5\r
+        while(True):\r
+            count-=1\r
+            if count<=0:\r
+                return -1\r
+            wakeup_cta1_io = self.extint_wakeup_cta1.read_level()\r
+            print("bt io状态:{}".format(wakeup_cta1_io))\r
+            if wakeup_cta1_io == 1:\r
+                self.PIN_wakeup_bt.write(1)\r
+                return 1\r
+            utime.sleep_ms(100)\r
+            if count%2 == 0:\r
+                self.PIN_wakeup_bt.write(1)\r
+            else:\r
+                self.PIN_wakeup_bt.write(0)\r
+\r
+        \r
+    def sleep_BT(self,event=None,msg=None):#使蓝牙休眠\r
+        try:\r
+            if not ("vc_config" in globals() and vc_config and vc_config.get_bool("BT_WAKEUP_4G_ENABLED", False)):\r
+                return\r
+        except Exception:\r
+            return\r
+        print("sleep_BT!!!!!")\r
+        print("sleep_BT!!!!!")\r
+        print("sleep_BT!!!!!")\r
+        print("sleep_BT!!!!!")\r
+        self.PIN_wakeup_bt.write(0)\r
+        count = 300\r
+        while(True):\r
+            count-=1\r
+            if count<=0:\r
+                return -1\r
+            if self.extint_wakeup_cta1.read_level() == 0:\r
+                return 1\r
+            utime.sleep_ms(1)\r
+   \r
+   \r
+    def wakeup_extint_cb(self,args):#蓝牙唤醒4g\r
+        try:\r
+            if not ("vc_config" in globals() and vc_config and vc_config.get_bool("BT_WAKEUP_4G_ENABLED", False)):\r
+                return\r
+        except Exception:\r
+            return\r
+        print("wakeup_extint_cb:!!!!!!!!!")\r
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
+        self.PIN_wakeup_bt.write(1)\r
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.extint_wakeup_cta1_cb})\r
+    def extint_wakeup_cta1_cb(self):\r
+        #self.extint_wakeup_cta1_timer.start(100, 1, self.extint_wakeup_cta1_timer_cb)\r
+        pass\r
+    def extint_wakeup_cta1_timer_cb(self, *args, **kwargs):\r
+        if self.extint_wakeup_cta1.read_level() == 0:\r
+            self.extint_wakeup_cta1_timer.stop()\r
+            #self.PIN_wakeup_bt.write(0)\r
+\r
+    def powerKey_callback(self, status):\r
+        print("powerKey_callback:{}".format(status))\r
+   \r
+\r
+    def pwk_extint_cb(self,args):#pwk\r
+        current_time = 0\r
+        interval = 0\r
+\r
+        try:\r
+            #按键电平判断\r
+            press_level = 0\r
+            current_level = self.pwk_key.read_level()\r
+            if current_level != press_level:\r
+                return  # 非按下状态,直接返回\r
+\r
+            #消抖处理\r
+            current_time = utime.ticks_ms()\r
+            if utime.ticks_diff(current_time, self.last_key_time) < self.key_debounce_interval:\r
+                return\r
+            self.last_key_time = current_time  # 更新最后按键时间\r
+            print("pwk 按键按下")\r
+\r
+            #仅处理"按下瞬间"事件(避免重复触发)\r
+            if len(args) < 2:\r
+                print("args参数不足,跳过")\r
+                return\r
+            self.pwk_key_status = int(args[1])\r
+            if self.pwk_key_status != 1:\r
+                return\r
+\r
+            #双击计数逻辑\r
+            self.pwk_press_count += 1\r
+            current_time = utime.ticks_ms()\r
+\r
+            #第一次按下(启动长按定时器+双击等待)\r
+            if self.pwk_press_count == 1:\r
+                self.pwk_first_press_time = current_time  # 记录首次按下时间\r
+                self.pwk_key_long_press = 0               # 重置长按标记\r
+                self.pwk_key_timer_count = 0              # 重置定时器计数\r
+                self.pwk_key_press = 1                    # 标记按键按下\r
+                \r
+                # 停止旧定时器,启动长按/单击判定定时器(100ms单次触发,循环靠回调自启)\r
+                self.pwk_key_timer.stop()\r
+                self.pwk_key_timer.start(100, 1, self.pwk_key_timer_cb)\r
+                print("第一次按下,等待第二次按下")\r
+                if getattr(self, "_single_power_ai_merged", False):\r
+                    self._merged_pwk_ai_press_start()\r
+\r
+            #第二次按下(双击判断)\r
+            elif self.pwk_press_count == 2:\r
+                # 计算两次按下间隔\r
+                interval = utime.ticks_diff(current_time, self.pwk_first_press_time)\r
+                \r
+                #符合双击阈值:执行双击逻辑\r
+                if interval <= self.double_click_threshold:\r
+                    self.pwk_key_timer.stop()  # 停止长按定时器(关键:避免触发长按)\r
+                    self.pwk_key_double_press() # 执行双击业务\r
+                    print("双击生效")\r
+                    # 重置所有状态\r
+                    self.pwk_press_count = 0\r
+                    self.pwk_key_press = 0\r
+                    self.pwk_key_timer_count = 0\r
+\r
+                #间隔过长:视为新的第一次按下\r
+                else:\r
+                    self.pwk_first_press_time = current_time  # 更新首次按下时间\r
+                    self.pwk_press_count = 1                  # 重置为第一次按下\r
+                    print("间隔过长,视为新的第一次按下")\r
+\r
+            #按下次数超过2次(防计数溢出)\r
+            else:\r
+                self.pwk_press_count = 0\r
+                self.pwk_key_timer.stop()\r
+                print("按键次数过多,重置计数")\r
+\r
+        except Exception as e:\r
+            # 异常时重置所有状态,避免卡死\r
+            self.pwk_press_count = 0\r
+            self.pwk_key_press = 0\r
+            self.pwk_key_timer_count = 0\r
+            self.pwk_key_timer.stop()\r
+            usys.print_exception(e)\r
+\r
+\r
+    def pwk_is_double_judge(self, *args):\r
+        # if self.press_count == 1:  # 仅一次按下,判定为单击\r
+            \r
+        # 重置计数(无论何种情况,超时后清零)\r
+        self.pwk_press_count = 0\r
+\r
+\r
+    def pwk_key_timer_cb(self, *args, **kwargs):\r
+        EventMesh.publish("set_wait_first_flag",0)\r
+        EventMesh.publish("set_history_board_flag",0)\r
+        EventMesh.publish("set_play_new_voice_state",0)\r
+        EventMesh.publish("set_history_voice_flag",0)\r
+        EventMesh.publish("set_wait_voice_flag",0)\r
+        EventMesh.publish("set_stop_play",0)\r
+    #     EventMesh.publish("publish_AI_btnInfo",{"func":self.pwk_key_timer_cb_})\r
+    # def pwk_key_timer_cb_(self) :\r
+        try:\r
+            self.pwk_key_status = self.pwk_key.read_level()\r
+            self.pwk_key_timer_count +=1\r
+            if self.pwk_key_timer_count >= self._pwk_shutdown_ticks:\r
+                #print("pwk_key_long_press {}".format(self.pwk_key_long_press))\r
+                if self.pwk_key_long_press == 0:\r
+                        self.pwk_key_long_press = 1\r
+                        #EventMesh.publish("AI_key_long_press")\r
+                        # EventMesh.publish("audio_montage_play",["zhengzaiguanji.mp3"])\r
+                        self.aud.play(1,0,'U:/static/zhengzaiguanji.mp3')#关机音频\r
+                        EventMesh.publish("set_led_state", {"battery": "empty"})\r
+                        utime.sleep_ms(2000)\r
+                        self.aud.stopPlayStream()\r
+                        self.aud.stopAll()\r
+                        Power.powerDown()\r
+                        #print("AI_key_release")\r
+                        #EventMesh.publish("pwk_key_long_press")\r
+                        #EventMesh.publish("publish_AI_btnInfo",{"func":self.pwk_key_Long_Press})  \r
+            if self.pwk_key_status == 1:#松开\r
+                #print("pwk_key_timer_cb:{}".format(self.pwk_key_timer_count))\r
+                try:\r
+                    key_count = vc_config.get_int("KEY_RELEASE_DEBOUNCE_COUNT", 5)\r
+                except Exception:\r
+                    key_count = 5\r
+                if self.pwk_key_timer_count >= key_count: #防抖200ms\r
+                    #EventMesh.publish("AI_key_release")\r
+                    EventMesh.publish("publish_AI_btnInfo",{"func":self.pwk_key_release})\r
+                    self.pwk_key.enable()\r
+                    self.pwk_key_timer_count = 0\r
+                    self.pwk_key_press = 0\r
+                    self.pwk_press_count = 0#按下次数\r
+                    self.pwk_key_timer.stop()\r
+            \r
+        except Exception as e: \r
+            usys.print_exception(e) \r
+            \r
+    def _ai_int_disable_for_hold(self):\r
+        if self.extint_AI is not None:\r
+            self.extint_AI.disable()\r
+\r
+    def _ai_int_enable_after_hold(self):\r
+        if self.extint_AI is not None:\r
+            self.extint_AI.enable()\r
+\r
+    def _key_ai_read_level(self):\r
+        if self.extint_AI is not None:\r
+            return self.extint_AI.read_level()\r
+        return self.pwk_key.read_level()\r
+\r
+    def _merged_pwk_ai_press_start(self):\r
+        """单电源键:按下瞬间走原 AI 键按下链路(松手走 AI_key_release,不按电源短按 Close)。"""\r
+        try:\r
+            current_time = utime.ticks_ms()\r
+            if utime.ticks_diff(current_time, self.last_ai_key_time) < 1:\r
+                return\r
+            self.last_ai_key_time = current_time\r
+            if EventMesh.publish("get_wait_voice_flag") == 1:\r
+                print("播放未读")\r
+                EventMesh.publish("set_wait_voicerupt_flag", 1)\r
+                return\r
+            if EventMesh.publish("get_recv_voice") == 1:\r
+                print("收到留言")\r
+                EventMesh.publish("set_voicerupt_flag", 1)\r
+                return\r
+            if self.KeyAI_press == 0:\r
+                EventMesh.publish(constants.EVENT.SYSTEM_UP)\r
+                if EventMesh.publish("get_play_new_voice_state") == 1:\r
+                    EventMesh.publish("set_play_new_voice_state", 0)\r
+                EventMesh.publish("set_VC_stop_play_audio_flag", 1)\r
+                self._ai_int_disable_for_hold()\r
+                self.KeyAI_press = 1\r
+                self.KeyAI_long_press = 0\r
+                self.KeyAI_timer.start(100, 1, self.KeyAI_timer_cb)\r
+                self.KeyAI_timer_count = 0\r
+                EventMesh.publish("publish_AI_btnInfo", {"func": self.AI_key_press})\r
+        except Exception as e:\r
+            usys.print_exception(e)\r
+\r
+    def keyAI_extint_cb(self,args):#call 2\r
+        try:\r
+            if self.extint_AI is None:\r
+                return\r
+            #print("AI_btn:keyAI_extint_cb{}".format(args[1]))\r
+            #定义最短触发间隔\r
+            MIN_TRIGGER_INTERVAL = 1  # 可根据需求调整,单位ms\r
+            #记录上次按键触发时间(初始化放类的__init__里:self.last_ai_key_time = 0)\r
+            current_time = utime.ticks_ms()  # 获取当前毫秒时间\r
+            #重复按下,直接返回\r
+            if utime.ticks_diff(current_time, self.last_ai_key_time) < MIN_TRIGGER_INTERVAL:\r
+                print("AI按键触发过频,忽略本次操作")\r
+                return\r
+            #更新上次触发时间\r
+            self.last_ai_key_time = current_time\r
+\r
+            self.KeyAI_status = int(args[1])\r
+            if self.KeyAI_status == 0:\r
+                self.KeyAI_status = 1\r
+            else:\r
+                self.KeyAI_status = 0\r
+            \r
+            if self.KeyAI_status == 0:#按下\r
+                if EventMesh.publish("get_wait_voice_flag") == 1:\r
+                    print("播放未读")\r
+                    EventMesh.publish("set_wait_voicerupt_flag",1)\r
+                    return\r
+                if EventMesh.publish("get_recv_voice") == 1:#当处于收到留言的时候\r
+                    print("收到留言")\r
+                    EventMesh.publish("set_voicerupt_flag",1)\r
+                    return\r
+                if self.KeyAI_press == 0 :\r
+                    EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
+                    if EventMesh.publish("get_play_new_voice_state") == 1:\r
+                        EventMesh.publish("set_play_new_voice_state",0)\r
+                    \r
+                    EventMesh.publish("set_VC_stop_play_audio_flag",1)#按键按下不允许播放之前下发的音频数据\r
+                    self._ai_int_disable_for_hold()\r
+                    self.KeyAI_press = 1\r
+                    self.KeyAI_long_press = 0\r
+                    self.KeyAI_timer.start(100, 1, self.KeyAI_timer_cb)\r
+                    self.KeyAI_timer_count = 0\r
+                    #EventMesh.publish("AI_key_press")\r
+                    # if dev_type == "7PIN":\r
+                        # self.aud.aud_tone_play(10,100)#播放按键音\r
+                        \r
+                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_key_press})  \r
+        except Exception as e: \r
+            usys.print_exception(e)\r
+    def KeyAI_timer_cb(self, *args, **kwargs):\r
+    #     EventMesh.publish("publish_AI_btnInfo",{"func":self.KeyAI_timer_cb_})\r
+    # def KeyAI_timer_cb_(self):\r
+        try:\r
+            self.KeyAI_status = self._key_ai_read_level()\r
+            self.KeyAI_timer_count +=1\r
+            if self.KeyAI_timer_count >= 10*1 : #长按1s \r
+               \r
+                if self.KeyAI_long_press == 0:\r
+                    self.KeyAI_long_press = 1\r
+                    #EventMesh.publish("AI_key_long_press")\r
+                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_key_long_press})  \r
+            try:\r
+                _sos_on = vc_config.get_bool("SOS_ENABLED", True)\r
+            except Exception:\r
+                _sos_on = True\r
+            if _sos_on and self.KeyAI_timer_count >= 10*3 : #长按3s 上一次是短按则拨打sos紧急联系人\r
+                print("long sos phone:{}".format(self.KeySOS_timer_flag))\r
+                \r
+                if self.KeySOS_timer_flag == 1:\r
+                    self.KeySOS_timer_flag=0\r
+                    self.KeySos_long_press = 1\r
+                    self.KeySOS_timer.stop()\r
+                    get_sos_flag = EventMesh.publish("get_sos_flag")\r
+                    if get_sos_flag == 0:\r
+                        EventMesh.publish("set_sos_info_call")\r
+                    pass\r
+\r
+            elif self.KeyAI_timer_count >= 10*8:#关机\r
+                try:\r
+                    allow_power_off = vc_config.get_bool("AI_LONG_PRESS_POWER_OFF_ENABLED", False)\r
+                except Exception:\r
+                    allow_power_off = False\r
+                if allow_power_off:\r
+                    EventMesh.publish("audio_montage_play",["zhengzaiguanji.mp3"])\r
+                    #self.aud.play(1,0,'U:/static/zhengzaiguanji.mp3')#关机音频\r
+                    EventMesh.publish("set_led_state", {"battery": "empty"})\r
+                    utime.sleep_ms(2000)\r
+                    self.aud.stopPlayStream()\r
+                    print("stopAll:8")\r
+                    self.aud.stopAll()\r
+                    Power.powerDown()\r
+            if self.KeyAI_status == 1:#松开\r
+                #print("KeyAI_timer_cb:{}".format(self.KeyAI_timer_count))\r
+                try:\r
+                    key_count = vc_config.get_int("KEY_RELEASE_DEBOUNCE_COUNT", 5)\r
+                except Exception:\r
+                    key_count = 5\r
+                if self.KeyAI_timer_count >= key_count: #防抖200ms\r
+                    if self.KeySOS_timer_flag == 1:\r
+                        self.KeySOS_timer_flag = 0\r
+                        self.KeySos_long_press = 0\r
+                        self.KeySOS_timer.stop()\r
+                        print("KeySOS_timer_flag!!!!!!!!!!!!!!!")  #短按两次需要清除标识\r
+\r
+                    if self.KeyAI_long_press == 0:\r
+                        try:\r
+                            _sos_on = vc_config.get_bool("SOS_ENABLED", True)\r
+                        except Exception:\r
+                            _sos_on = True\r
+                        if _sos_on and self.KeySOS_timer_flag == 0:\r
+                            self.KeySOS_timer_flag = 1\r
+                            self.KeySos_long_press = 0\r
+                            self.KeySOS_timer.start(5000, 0, self.KeySos_timer_cb) #短按开启5s定时器 5s之内没有长按3s则清除标识 长按3s拨打sos紧急联系人\r
+                    else:\r
+                        self.KeySOS_timer_flag = 0\r
+                        self.KeySos_long_press = 0\r
+                        self.KeySOS_timer.stop()\r
+                        \r
+                    #EventMesh.publish("AI_key_release")\r
+                    EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_key_release})  \r
+                    self._ai_int_enable_after_hold()\r
+                    self.KeyAI_timer.stop()\r
+                    self.KeyAI_timer_count = 0\r
+                    self.KeyAI_press = 0\r
+                    self.KeyAI_long_press=0\r
+            \r
+        except Exception as e: \r
+            usys.print_exception(e) \r
+       \r
+    def AI_key_press(self):\r
+        # EventMesh.publish("wakeup_BT")\r
+        \r
+        # if dev_type == "7PIN" or dev_type == "7PIN_coze" :\r
+        #     # self.aud.aud_tone_play(10,60)#播放按键音\r
+        #     EventMesh.publish("audio_montage_play",["chenggong.mp3"])  \r
+        self.check_rest_timer_count = 0\r
+        print("AI_key_press")\r
+        EventMesh.publish("AI_key_press")\r
+    def KeySos_timer_cb(self,*argc):\r
+        print("KeySos_timer_cb!!!!!!!!!!{} {}".format(self.KeySos_long_press,self.KeySOS_timer_flag))\r
+        \r
+        if self.KeySos_long_press == 1 and self.KeySOS_timer_flag == 1:  #清空sos标识\r
+            self.KeySos_long_press=0\r
+            self.KeySOS_timer_flag=0\r
+           \r
+        self.KeySos_long_press=0\r
+        self.KeySOS_timer_flag = 0\r
+        return\r
+        self.KeySOS_timer_flag = 0\r
+        if self.KeySOS_timer_count >= 3:\r
+            self.KeySOS_timer_count = 0\r
+            get_sos_flag = EventMesh.publish("get_sos_flag")\r
+            if get_sos_flag == 0:\r
+                EventMesh.publish("set_sos_info_call")\r
+        self.KeySOS_timer_count = 0\r
+        pass\r
+\r
+    def AI_key_long_press(self):\r
+        self.check_rest_timer_count = 0\r
+        print("AI_key_long_press")\r
+\r
+        EventMesh.publish("AI_key_long_press")\r
+        \r
+    def AI_key_release(self):\r
+        print("AI_key_release")\r
+        # self.KeySOS_timer_count +=1\r
+        # print("self.KeySOS_timer_count:{} {}".format(self.KeySOS_timer_count,self.KeySOS_timer_flag))\r
+        # if self.KeySOS_timer_flag == 0:\r
+        #     self.KeySOS_timer_flag = 1\r
+        #     self.KeySOS_timer.start(5000, 0, self.KeySos_timer_cb)\r
+        EventMesh.publish("AI_key_release")\r
+        \r
+    def pwk_key_double_press(self):\r
+        self.check_rest_timer_count = 0\r
+        try:\r
+            if getattr(self, "_single_power_ai_merged", False):\r
+                try:\r
+                    self.KeyAI_timer.stop()\r
+                except Exception:\r
+                    pass\r
+                self.KeyAI_timer_count = 0\r
+                self.KeyAI_press = 0\r
+                self.KeyAI_long_press = 0\r
+                self._ai_int_enable_after_hold()\r
+        except Exception:\r
+            pass\r
+        print("pwk_key_double_press")\r
+        EventMesh.publish("pwk_key_double_press")\r
+\r
+    def pwk_key_Long_Press(self,event=None,msg=None):\r
+        self.check_rest_timer_count = 0\r
+        print("pwk_key_long_press!!!!!!!")\r
+        print("pwk_key_long_press!!!!!!!")\r
+        \r
+        # EventMesh.publish("paly_iccid_imei")\r
+        # return\r
+        EventMesh.publish("audio_montage_play",["zhengzaiguanji.mp3"])\r
+        #self.aud.play(1,0,'U:/static/zhengzaiguanji.mp3')#关机音频\r
+        EventMesh.publish("set_led_state", {"battery": "empty"})\r
+        utime.sleep_ms(3000)\r
+        self.aud.stopPlayStream()\r
+        self.aud.stopAll()\r
+        Power.powerDown()\r
+        #print("AI_key_release")\r
+        #EventMesh.publish("pwk_key_long_press")\r
+        \r
+    def pwk_key_release(self):\r
+        self.check_rest_timer_count = 0\r
+        #print("pwk_key_release")\r
+        EventMesh.publish("pwk_key_release")\r
+\r
+    \r
+    def wdt_timer_cb(self, *args, **kwargs):\r
+        if self.is_wdt == 1:\r
+            self.wdt.feed()\r
+\r
+    def AI_dev_timer_cb(self, *args, **kwargs):\r
+        #print("AI_dev_timer_cb")\r
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.AI_dev_timer_event})  \r
+    def _battery_empty_shutdown_seq(self):\r
+        """REQ-BAT-03:红快闪 3 次 → 灭灯 → 关机提示音 → 关机(btn 线程执行)。"""\r
+        try:\r
+            EventMesh.publish("battery_priority_mute_vc")\r
+        except Exception:\r
+            pass\r
+        try:\r
+            n, ms = 3, 500\r
+            if "vc_config" in globals() and vc_config:\r
+                n = int(vc_config.get_int("BATTERY_EMPTY_FLASH_COUNT", 3))\r
+                ms = int(vc_config.get_int("BATTERY_EMPTY_FLASH_PERIOD_MS", 500))\r
+            EventMesh.publish("set_led_state", {"alert": "red_flash_3"})\r
+            utime.sleep_ms(n * ms * 2 + 200)\r
+            EventMesh.publish("set_led_state", {"ai_state": "idle", "battery": "normal", "charging": "none"})\r
+        except Exception:\r
+            pass\r
+        try:\r
+            EventMesh.publish("audio_montage_play", ["didianguanji.mp3"])\r
+            utime.sleep_ms(4500)\r
+            self.aud.stopPlayStream()\r
+            self.aud.stopAll()\r
+        except Exception:\r
+            pass\r
+        try:\r
+            Power.powerDown()\r
+        except Exception:\r
+            pass\r
+\r
+    def AI_dev_timer_event(self):\r
+        #EventMesh.publish("audio_montage_play",["liuliangtishi.mp3"])\r
+        EventMesh.publish("send_power_init_data")\r
+        if self.is_wdt == 1:\r
+            self.wdt.feed()\r
+        VC_satrt_flag = EventMesh.publish("get_VC_satrt_flag")#获取vc是否在运行\r
+        if VC_satrt_flag == None:\r
+            VC_satrt_flag = 0\r
+        voice_record_satrt_flag = EventMesh.publish("get_voice_record_flag")#获取静默或者语音留言是否在运行\r
+        if voice_record_satrt_flag == None:\r
+            voice_record_satrt_flag = 0\r
+        Vbatt = Power.getVbatt()\r
+        if self.usb_status == 1 and VC_satrt_flag == 0 and voice_record_satrt_flag == 0:\r
+            try:\r
+                if not ("vc_config" in globals() and vc_config and vc_config.get_bool("USE_MIN_HW_STATUS", True)):\r
+                    EventMesh.publish("set_led_state", {"charging": "charging"})#充电红灯常亮\r
+            except Exception:\r
+                pass\r
+        _bcfg = _BatteryCfg.instance()\r
+        if self.usb_status == 1 and Vbatt >= _bcfg.charge_full_mv and VC_satrt_flag == 0 and voice_record_satrt_flag == 0:\r
+            try:\r
+                if not ("vc_config" in globals() and vc_config and vc_config.get_bool("USE_MIN_HW_STATUS", True)):\r
+                    EventMesh.publish("set_led_state", {"charging": "full"})\r
+            except Exception:\r
+                pass\r
+            if self.usb_charge_flag == 0 :\r
+                EventMesh.publish("audio_montage_play",["R_CHARGE_FULL.mp3"])\r
+                #self.aud.play(1,0,'U:/static/R_CHARGE_FULL.mp3')#充电已满\r
+                self.usb_charge_flag = 1\r
+        try:\r
+            low_soc = int(_bcfg.low_soc)\r
+            empty_soc = int(_bcfg.empty_soc)\r
+        except Exception:\r
+            low_soc, empty_soc = 30, 10\r
+        warn_interval_ms = 300000\r
+        try:\r
+            if "vc_config" in globals() and vc_config:\r
+                warn_interval_ms = int(vc_config.get_int("BATTERY_LOW_WARN_INTERVAL_MS", 300000))\r
+        except Exception:\r
+            pass\r
+        soc = None\r
+        try:\r
+            soc = self.get_battery()\r
+        except Exception:\r
+            soc = None\r
+        if self.usb_status == 0 and soc is not None:\r
+            empty_by_soc = soc < empty_soc\r
+            empty_by_mv = Vbatt is not None and Vbatt < int(_bcfg.empty_mv)\r
+            if empty_by_soc or empty_by_mv:\r
+                if getattr(self, "_empty_shutdown_done", 0) == 0:\r
+                    self._empty_shutdown_done = 1\r
+                    try:\r
+                        print("[BAT] empty shutdown soc={} vbatt={}".format(soc, Vbatt))\r
+                    except Exception:\r
+                        pass\r
+                    EventMesh.publish("publish_AI_btnInfo", {"func": self._battery_empty_shutdown_seq})\r
+            elif soc < low_soc:\r
+                if voice_record_satrt_flag == 0:\r
+                    try:\r
+                        now = utime.ticks_ms()\r
+                        last = getattr(self, "_low_warn_last_ms", 0)\r
+                        if (not last) or utime.ticks_diff(now, last) >= warn_interval_ms:\r
+                            self._low_warn_last_ms = now\r
+                            try:\r
+                                print("[BAT] low warn soc={} interval_ms={}".format(soc, warn_interval_ms))\r
+                            except Exception:\r
+                                pass\r
+                            EventMesh.publish("audio_montage_play", ["Low_power_warning.mp3"])\r
+                    except Exception:\r
+                        pass\r
+            else:\r
+                try:\r
+                    self._low_warn_last_ms = 0\r
+                    self._empty_shutdown_done = 0\r
+                except Exception:\r
+                    pass\r
+                if VC_satrt_flag == 0 and Vbatt is not None and Vbatt >= int(_bcfg.low_mv):\r
+                    try:\r
+                        if not ("vc_config" in globals() and vc_config and vc_config.get_bool("USE_MIN_HW_STATUS", True)):\r
+                            EventMesh.publish("set_led_state", {"charging": "none", "battery": "normal"})\r
+                    except Exception:\r
+                        pass\r
+\r
+        self.check_rest_timer_count += 1\r
+        if self.check_rest_timer_count >= 8640:#24小时没按键,则关机\r
+            self.pwk_key_Long_Press()\r
+        \r
+    def get_battery(self, event=None, msg=None):\r
+        # 返回 int 型 SoC(0~100):便于 TTS/上报/比较,且 OCV 插值本身比裸电压稳;不做小数避免频繁跳变\r
+        try:\r
+            Vbatt = Power.getVbatt()\r
+        except Exception:\r
+            return 0\r
+        soc = _BatteryCfg.instance().soc_from_vbatt(Vbatt)\r
+        if soc is None:\r
+            return 0\r
+        if soc < 0:\r
+            return 0\r
+        if soc > 100:\r
+            return 100\r
+        return int(soc)\r
+    def _audio_is_battery_priority(self, msg):\r
+        try:\r
+            for i in (msg or []):\r
+                n = str(i)\r
+                if ("Low_power" in n) or ("didianguanji" in n):\r
+                    return True\r
+        except Exception:\r
+            pass\r
+        return False\r
+\r
+    def audio_montage_play(self,event,msg):\r
+        self.wakeup_BT()\r
+        bat_pri = self._audio_is_battery_priority(msg)\r
+        if bat_pri:\r
+            try:\r
+                EventMesh.publish("battery_priority_mute_vc")\r
+            except Exception:\r
+                pass\r
+        else:\r
+            # 仅服务端 TTS PCM 在播时跳过;低电/关机提示优先级更高(见上)\r
+            try:\r
+                if EventMesh.publish("get_vc_tts_playback_active") == 1:\r
+                    print("[audio-arb] skip local montage_play during tts_pcm")\r
+                    return\r
+            except Exception:\r
+                pass\r
+        self.aud.stopPlayStream()\r
+        self.aud.stopAll()\r
+        str_data='mp3files='\r
+        for i in msg:\r
+            try:\r
+                str_tmp = i.replace(".mp3","")\r
+                str_data+="{}.mp3+".format(audio_buf.index(str_tmp))\r
+            except Exception as e:\r
+                    usys.print_exception(e)    \r
+        str_data = str_data[:-1]\r
+        print("audio_montage_play:{}".format(str_data))\r
+        self.aud.montage_play('U:/static/audio.bin',1,1,str_data)\r
+        self.play_imei_timer_count=1\r
+        self.play_imei_timer.stop()\r
+        self.play_imei_timer.start(1000,1,self.play_imei_timer_cb)\r
+        \r
+    def paly_iccid_imei(self,event,msg):\r
+        \r
+        self.wakeup_BT()\r
+        self.aud.stopPlayStream()\r
+        self.aud.stopAll()\r
+        str_data = 'mp3files={}.mp3+'.format(audio_buf.index("liuliangtishi"))#流量提示\r
+        imei  = modem.getDevImei()\r
+        iccid = sim.getIccid()\r
+        print("paly_iccid_imei:ICCID:{}".format(iccid))\r
+        if iccid != -1 and len(iccid) > 5:\r
+            iccid = iccid.upper()\r
+            if len(iccid) <20:\r
+                for i in range(20-len(iccid)):\r
+                    iccid = "0"+iccid\r
+            str_data += '{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+'.format(audio_buf.index("R_READ_TIME_{}".format(iccid[0]))\\r
+                ,audio_buf.index("R_READ_TIME_{}".format(iccid[1])),audio_buf.index("R_READ_TIME_{}".format(iccid[2])),audio_buf.index("R_READ_TIME_{}".format(iccid[3])),\\r
+                audio_buf.index("R_READ_TIME_{}".format(iccid[4])),audio_buf.index("R_READ_TIME_{}".format(iccid[5])),audio_buf.index("R_READ_TIME_{}".format(iccid[6])),audio_buf.index("R_READ_TIME_{}".format(iccid[7])),\\r
+                audio_buf.index("R_READ_TIME_{}".format(iccid[8])),audio_buf.index("R_READ_TIME_{}".format(iccid[9])),audio_buf.index("R_READ_TIME_{}".format(iccid[10])),audio_buf.index("R_READ_TIME_{}".format(iccid[11])),\\r
+                audio_buf.index("R_READ_TIME_{}".format(iccid[12])),audio_buf.index("R_READ_TIME_{}".format(iccid[13])),audio_buf.index("R_READ_TIME_{}".format(iccid[14])),audio_buf.index("R_READ_TIME_{}".format(iccid[15])),\\r
+                audio_buf.index("R_READ_TIME_{}".format(iccid[16])),audio_buf.index("R_READ_TIME_{}".format(iccid[17])),audio_buf.index("R_READ_TIME_{}".format(iccid[18])),audio_buf.index("R_READ_TIME_{}".format(iccid[19])))\r
+        str_data += '{}.mp3+'.format(audio_buf.index("shouhou"))#可联系售后解决,联系方式见售后客服电话是400-886-8777或到微信公众号柒牌SEVENBRAND自行充值:\r
+        self.aud.montage_play('U:/static/audio.bin',1,1,str_data)\r
+        #开定时器不让系统休眠\r
+        self.play_imei_timer_count=1\r
+        self.play_imei_timer.stop()\r
+        self.play_imei_timer.start(1000,1,self.play_imei_timer_cb)\r
+\r
+    def play_imei_timer_cb(self,event=None,msg=None):\r
+        if self.play_imei_timer_count == 1:\r
+            if EventMesh.publish("get_audio_play_state") == 0:\r
+                print("音频文件播放完成")\r
+                self.play_imei_timer_count=0\r
+                self.play_imei_timer.stop()\r
+                return\r
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
+\r
+    #鉴权出错,播放imei号\r
+    def paly_device_res_error(self,event,msg):\r
+        self.wakeup_BT()\r
+        EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠\r
+        str_data = 'mp3files={}.mp3+'.format(audio_buf.index("jianquancuowu"))#AI功能异常,请联系客户查询,设备号为:\r
+        imei  = modem.getDevImei()\r
+        imei = imei.upper()\r
+        str_data += '{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3+{}.mp3'.format(audio_buf.index("R_READ_TIME_{}".format(imei[0]))\\r
+                ,audio_buf.index("R_READ_TIME_{}".format(imei[1])),audio_buf.index("R_READ_TIME_{}".format(imei[2])),audio_buf.index("R_READ_TIME_{}".format(imei[3])),\\r
+                audio_buf.index("R_READ_TIME_{}".format(imei[4])),audio_buf.index("R_READ_TIME_{}".format(imei[5])),audio_buf.index("R_READ_TIME_{}".format(imei[6])),audio_buf.index("R_READ_TIME_{}".format(imei[7])),\\r
+                audio_buf.index("R_READ_TIME_{}".format(imei[8])),audio_buf.index("R_READ_TIME_{}".format(imei[9])),audio_buf.index("R_READ_TIME_{}".format(imei[10])),audio_buf.index("R_READ_TIME_{}".format(imei[11])),\\r
+                audio_buf.index("R_READ_TIME_{}".format(imei[12])),audio_buf.index("R_READ_TIME_{}".format(imei[13])),audio_buf.index("R_READ_TIME_{}".format(imei[14])))\r
+        #print("str_data:{}".format(str_data))\r
+        self.aud.montage_play('U:/static/audio.bin',1,1,str_data)\r
+        self.play_imei_timer_count=1\r
+        self.play_imei_timer.stop()\r
+        self.play_imei_timer.start(1000,1,self.play_imei_timer_cb)\r
+\r
+#存储信息\r
+class getStorageDataManager(Abstract):#获取内存数据\r
+        def __init__(self):\r
+            self.config_data = {}\r
+            check = ql_fs.path_exists("/usr/static/storage_file/config.json")\r
+            if check == True:\r
+                with open("/usr/static/storage_file/config.json","r") as fd:\r
+                    self.config_data = ujson.load(fd)\r
+            else :\r
+                self.config_data = {'old_time': 0, 'flip_sw': 0}\r
+                pass\r
+\r
+        def post_processor_after_instantiation(self):\r
+            EventMesh.subscribe("set_storage_data", self.set_storage_data)\r
+            EventMesh.subscribe("set_storage_config", self.set_storage_config)\r
+            EventMesh.subscribe("get_storage_data", self.get_storage_data)\r
+            EventMesh.subscribe("get_storage_config", self.get_storage_config)\r
+\r
+        def set_storage_data(self, event, msg): \r
+            # EventMesh.debug_log("set_storage_data:{}".format(msg))\r
+            if msg != None:\r
+                self.config_data = msg\r
+                with open("/usr/static/storage_file/config.json",'w+',encoding="utf-8") as fd:\r
+                    ujson.dump(self.config_data,fd)\r
+        \r
+\r
+        def set_storage_config(self,event,msg): \r
+            # EventMesh.debug_log("set_storage_key_data1:{}".format(msg))\r
+            if len(msg) >= 2:\r
+                self.config_data[msg[0]]=msg[1]\r
+                # EventMesh.debug_log("set_storage_key_data2:{}".format(self.config_data[msg[0]]))\r
+                EventMesh.publish("publish_message_event_ui",{"func":self.set_storage_save,"data":0})\r
+        def set_storage_save(self, data): \r
+            with open("/usr/static/storage_file/config.json",'w+',encoding="utf-8") as fd:\r
+                ujson.dump(self.config_data,fd)\r
+        def get_storage_data(self, event, msg): \r
+            # EventMesh.debug_log("get_storage_data:{}".format(self.config_data))\r
+            return self.config_data \r
+        def get_storage_config(self,event,msg):\r
+            # EventMesh.debug_log("get_storage_key_data1:{}".format(msg))\r
+            b = msg in self.config_data.keys()\r
+            if b == True:\r
+                # EventMesh.debug_log("get_storage_key_data2:{}".format(self.config_data[msg]))\r
+                return self.config_data[msg]\r
+            else :\r
+                return None\r
+            # try:\r
+            #     return self.config_data[msg]\r
+            # except:\r
+            #     EventMesh.debug_log("get_storage_key_data:none key:{}",msg)\r
+            #     return None\r
+#设备资源授权平台\r
+class device_res_author(Abstract):\r
+   \r
+    def __init__(self) :\r
+        print("device_res_author __init__ start !!!!!!!!!!!!!!!!!")\r
+        self.deviceId = modem.getDevImei()#"876226002022574"\r
+        self.SHA1_BLOCK_SIZE = 64\r
+        self.PAD_BLOCK_SIZE = 16\r
+        self.SHA256 = uhashlib.sha256\r
+        self.pad = lambda s: s + chr(self.PAD_BLOCK_SIZE - len(s) % self.PAD_BLOCK_SIZE) * (self.PAD_BLOCK_SIZE - len(s) % self.PAD_BLOCK_SIZE)\r
+        # 除去补16字节的多余字符\r
+        self.unpad = lambda s: s[:-s[-1]]\r
+        # 设备鉴权:优先 default_config / usr/default_config 的 PRODUCT_PID、PRODUCT_KEY\r
+        self.pid = "cqxJZi5CNgzjS3mX21Zw"\r
+        self.key = b"uKLrChwrxA1LklgBc3VwawYjUkbT3BgX"\r
+        try:\r
+            if "vc_config" in globals() and vc_config:\r
+                _pid = vc_config.get_attr("PRODUCT_PID", None)\r
+                if _pid:\r
+                    self.pid = str(_pid)\r
+                _key = vc_config.get_attr("PRODUCT_KEY", None)\r
+                if _key is not None and _key != "":\r
+                    self.key = _key if isinstance(_key, bytes) else str(_key).encode()\r
+        except Exception:\r
+            pass\r
+        self.dev_res_timer = osTimer()\r
+        self.dev_res_timer.start(20000, 1, self.dev_timer_cb)\r
+        self.dev_res_timerCount = 0\r
+        self.ud_token_timer = osTimer()\r
+        self.ud_token_timer.start(10000, 1, self.ud_token_timer_cb)\r
+        self.ud_token_timer_Count = 0\r
+        self.dev_token = None\r
+        self.res_error_count = 0\r
+        self.ntptime_flag = 0\r
+        EventMesh.subscribe("get_dev_token", self.get_dev_token)\r
+        EventMesh.subscribe("update_dev_token", self.update_dev_token)\r
+        EventMesh.subscribe("dev_active", self.dev_active)\r
+        EventMesh.subscribe("dev_sign_in", self.dev_sign_in)\r
+        EventMesh.subscribe("dev_NTP", self.dev_NTP)\r
+        EventMesh.subscribe("post_iccid_user", self.post_iccid_user)\r
+        # self.post_iccidd_token_timer = osTimer()\r
+        # self.post_iccidd_token/_timer.start(10000, 0, self.post_iccidd_token_timer_cb)\r
+        self.connect_web_flag = 0\r
+        #EventMesh.publish_async("dev_NTP",thread_size = 8192)\r
+\r
+        # count = 0\r
+        # while True:\r
+        #     if self.ntptime_flag == 1:\r
+        #         break\r
+        #     utime.sleep_ms(1)\r
+        #     count+=1\r
+        #     if count>600:\r
+        #         break\r
+        # EventMesh.publish_async("dev_active",thread_size = 8192)\r
+        # EventMesh.publish_async("dev_sign_in",thread_size = 8192)\r
+\r
+        self.dev_NTP()#获取时间\r
+        self.dev_active() #设备激活\r
+        self.dev_sign_in() #设备登入\r
+\r
+        \r
+        print("device_res_author __init__ end !!!!!!!!!!!!!!!!!")\r
+    def post_iccidd_token_timer_cb(self,*argc):\r
+        EventMesh.publish("post_iccid_user")\r
+\r
+    def get_dev_token(self, event, msg): \r
+        return self.dev_token\r
+    #网络校时\r
+    def dev_NTP(self, event=None, msg=None):\r
+        response = None\r
+        try:\r
+            print("dev_NTP1")\r
+            url = constants.DMP_BASE + "/dev/time"\r
+            print("dev_NTP:{}".format(url))\r
+            response = request.get(url,timeout=5)\r
+            data = response.json()\r
+            print("dev_NTP:{}".format(data))\r
+            if data:\r
+                try:\r
+                    timeSecond = data["data"]["timestamp"]//1000\r
+                    \r
+                    next_alarm_time = utime.localtime(timeSecond)\r
+                    print("timeSecond {} next_alarm_time {}".format(timeSecond,next_alarm_time))\r
+                    machine.RTC().datetime((next_alarm_time[0],next_alarm_time[1],next_alarm_time[2],next_alarm_time[6],next_alarm_time[3],next_alarm_time[4],next_alarm_time[5],0))\r
+                    self.ntptime_flag  = 1\r
+                except Exception as e:\r
+                        usys.print_exception(e)\r
+        except Exception as e:\r
+            usys.print_exception(e)\r
+        if response != None:\r
+            response.close() \r
+        print("dev_NTP end")\r
+    def dev_timer_cb(self, *args): #设备资源管理鉴权平台定时器\r
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.dev_timer_cb_}) \r
+\r
+    def dev_timer_cb_(self):    \r
+        #if EventMesh.DEV_RES_MANAGE_AUTHEN_PLATFORM == 1:\r
+        try:\r
+            if self.dev_token == None:\r
+                if self.ntptime_flag == 0:\r
+                    self.ntptime_flag  = 1\r
+                    EventMesh.publish_async("dev_NTP",thread_size = 8192)\r
+                    #EventMesh.publish("publish_AI_btnInfo",{"func":self.dev_NTP})\r
+                print("timer:{}!!!!!!!!\r\n".format(utime.localtime()))\r
+                EventMesh.publish_async("dev_active",thread_size = 8192)\r
+                EventMesh.publish_async("dev_sign_in",thread_size = 8192)\r
+                # self.dev_active() #设备激活\r
+                # self.dev_sign_in() #设备登入\r
+                self.dev_res_timerCount+=1\r
+                if self.dev_res_timerCount > 3: #开机一分钟内没有获取到密码和token 等下一次更新令牌在走登入 激活流程\r
+                    self.dev_res_timerCount=0\r
+                    self.dev_res_timer.stop()\r
+        except Exception as e:\r
+            usys.print_exception(e)\r
+    def ud_token_timer_cb(self, *args): \r
+        EventMesh.publish("publish_AI_btnInfo",{"func":self.ud_token_timer_cb_}) \r
+\r
+    def ud_token_timer_cb_(self):   \r
+        try:\r
+            #print("ud_token_timer_!!!!")\r
+            self.ud_token_timer_Count+=1\r
+            if self.ud_token_timer_Count >= 720: #定时10*3000s时更新token\r
+                self.ud_token_timer_Count = 0\r
+                EventMesh.publish_async("dev_sign_in",thread_size = 8192)\r
+        except Exception as e:\r
+            usys.print_exception(e)    \r
+    def get_sim_Status(self):\r
+        return sim.getStatus() \r
+    def HMAC(self,k, m):\r
+    \r
+        KEY_BLOCK = k + (b'\0' * (self.SHA1_BLOCK_SIZE - len(k)))\r
+        KEY_INNER = bytes((x ^ 0x36) for x in KEY_BLOCK)\r
+        KEY_OUTER = bytes((x ^ 0x5C) for x in KEY_BLOCK)\r
+        inner_message = KEY_INNER + m\r
+        outer_message = KEY_OUTER + self.SHA256(inner_message).digest()\r
+        return self.SHA256(outer_message)\r
+    def get_hmac_sha256(self,k, m):\r
+        data = self.HMAC(k, m).digest()\r
+        # print("HMAC(k, m).digest():", data)\r
+        # print("HMAC(k, m).digest():", ubinascii.hexlify(data))\r
+        hex_data = ubinascii.hexlify(data)\r
+        hex_data = hex_data.decode('ascii')\r
+        return hex_data\r
+    # 加密函数\r
+    def aes_CBC_Encrypt(self,data, key, iv):  # CBC模式的加密函数,data为明文,key为16字节密钥,iv为16字节的偏移量\r
+        key = key.encode('utf-8')\r
+        iv = iv.encode('utf-8')  # CBC 模式下的偏移量\r
+        data = self.pad(data)  # 补位\r
+        data = data.encode('utf-8')\r
+        aes = ucryptolib.aes(key,ucryptolib.MODE_CBC,iv)\r
+        result = base64.b64encode(aes.encrypt(data))\r
+        return str(result, 'utf-8')  # 以字符串的形式返回\r
+\r
+    # 解密函数\r
+    def aes_CBC_Decrypt(self,data, key, iv):  # CBC模式的解密函数,data为密文,key为16字节密钥\r
+        key = key.encode('utf-8')\r
+        iv = iv.encode('utf-8')\r
+        \r
+        aes = ucryptolib.aes(key,ucryptolib.MODE_CBC,iv) #CBC模式\r
+\r
+        # decrypt AES解密  B64decode为base64 转码\r
+        result = aes.decrypt(base64.b64decode(data))\r
+        result = self.unpad(result)  # 除去补16字节的多余字符\r
+        return str(result, 'utf-8')  # 以字符串的形式返回\r
+    #激活接口 没有密码走激活\r
+    def dev_active(self, event=None, msg=None):\r
+        signal = net.csqQueryPoll()\r
+        if signal == -1 or self.get_sim_Status() != 1:#网络不正常\r
+            print("dev_active 无 SIM卡!!!!!!!!\r\n")\r
+            return\r
+        print("dev_active 1\r\n")\r
+        # 签名内容: = deviceId+pid+timeStamp\r
+        dev_pass = ""\r
+        dev_pass = EventMesh.publish("get_storage_config","dev_pass")\r
+        print("dev_pass:{}\r\n".format(dev_pass))\r
+        if dev_pass == None:\r
+            dev_pass = ""\r
+        if dev_pass == "" or dev_pass == None:\r
+            try:\r
+                date_tmp = utime.localtime() #获取当前时间\r
+                print("date_tmp:{}\r\n".format(date_tmp)) #转换时间戳s\r
+                timeStamp = (utime.mktime(date_tmp))*1000#获取时间戳ms\r
+                deviceId = self.deviceId #"876226002022574" #以imei号\r
+                pid = self.pid\r
+                key = self.key\r
+                sign_data = "{}{}{}".format(deviceId,pid,timeStamp)  #签名内容\r
+                print("sign_data:{}\r\n".format(sign_data))\r
+                sha256_hex = self.get_hmac_sha256(key,sign_data)\r
+                print("sha256_hex:{}\r\n".format(sha256_hex))\r
+\r
+                data = {\r
+                    "deviceId":"{}".format(deviceId), #设备名\r
+                    "pid":pid, #产品PID\r
+                    "sign":"{}".format(sha256_hex), #HMACSHA256签名后的数据\r
+                    "timeStamp":timeStamp, #要以ms的形式\r
+                    "signMethod":"HMACSHA256" #签名方式 HMACSHA256\r
+                }\r
+                print(ujson.dumps(data))\r
+                headers_T ={"Content-Type":"application/json"}\r
+                url = constants.DMP_BASE + "/dev/auth/active"\r
+                print("dev_active:{}".format(url))\r
+                data =ujson.dumps(data)\r
+                response = None\r
+                response_result = 0\r
+                try:\r
+                    response = request.put(url,data=data,headers = headers_T,timeout=5)\r
+                    response_result = 1\r
+                except Exception as e:\r
+                    usys.print_exception(e)   \r
+                    print("put error!!!!!\r\n")\r
+                    response_result = 0\r
+                \r
+                IV = "" #偏移量\r
+                PASSWORD= "" #服务器返回后的加密内容 密码\r
+                if response_result == 1: #访问成功ip\r
+                    if response.status_code == 200:\r
+                        print("headers:{}".format(response.headers))\r
+                        content = response.content\r
+                        c = next(content)\r
+                        # "api.xcloudtech.com"\r
+                        # d_server_port = 9000\r
+                        data =  ujson.loads(c)\r
+                        # data = response.json()\r
+                        print("data-{}".format(data))\r
+                        if data != None:\r
+                            if "success" in data:\r
+                                if data['success'] == True:\r
+                                    if "data" in data:\r
+                                        data_tmp = data["data"]\r
+                                        if "nonce" in data_tmp: #偏移量\r
+                                            IV = data_tmp["nonce"]\r
+                                        if "password" in data_tmp:\r
+                                            PASSWORD = data_tmp["password"]\r
+                                            # EventMesh.publish("set_storage_config",["dev_pass",PASS])\r
+                                if data["code"] == 20001:#鉴权成功\r
+                                    pass\r
+                                elif data["code"] > 50000:#\r
+                                    self.res_error_count+=1\r
+                                    if self.res_error_count>=3:\r
+                                        self.res_error_count = 0\r
+                                        \r
+                                        EventMesh.publish("paly_device_res_error")\r
+                                    if data["code"] == 50003:#时间错误\r
+                                        self.ntptime_flag  = 0\r
+                        if PASSWORD != None and IV != None:\r
+                            if len(PASSWORD) != 0 and len(IV) != 0:\r
+                                password = PASSWORD\r
+                                iv = IV\r
+                                #解密 之后保存本地下一次登入做使用\r
+                                decrypt_data = self.aes_CBC_Decrypt(password, self.key, iv) #对服务器下发的密码进行解密\r
+                                print("decrypt_data {}".format(decrypt_data))\r
+                                EventMesh.publish("set_storage_config",["dev_pass",decrypt_data])    \r
+                    print("IV:{}\r\n PASS:{}".format(IV,PASSWORD))\r
+                if response != None:\r
+                    response.close()  \r
+            except Exception as e:\r
+                usys.print_exception(e)   \r
+            print("dev_active end\r\n")\r
+            return [response_result,PASSWORD,IV]\r
+    #随机生成16个字节的字符串作为加密的IV 偏移量\r
+    def generate_random_string(self,length=16): \r
+        random_str = ""\r
+        chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'\r
+        for i in range(length):\r
+            random_str+=chars[random.randint(0,length)]\r
+        return random_str\r
+\r
+    #设备登入 没有Token 走登入获取token\r
+    def dev_sign_in(self, event=None, msg=None):\r
+    \r
+\r
+        signal = net.csqQueryPoll()\r
+        if signal == -1 or self.get_sim_Status() != 1:#网络不正常\r
+            print("dev_sign_in 无 SIM卡!!!!!!!!\r\n")\r
+            return\r
+        print("dev_sign_in 1")\r
+        token = ""\r
+        try:\r
+            token = self.dev_token\r
+            dev_pass = EventMesh.publish("get_storage_config","dev_pass") \r
+            #print("token:{}\r\n".format(token))\r
+            if token == None:\r
+                token = ""\r
+            if dev_pass == None:\r
+                dev_pass = ""\r
+            if len(dev_pass) == 0:#没有密码\r
+                return\r
+            #if token == None or token == "":\r
+            if 1: #返回结果成功\r
+                #获取本地保存的密码\r
+                decrypt_data=EventMesh.publish("get_storage_config","dev_pass")\r
+                # decrypt_data = self.aes_CBC_Decrypt(password, self.key, iv) #对服务器下发的密码进行解密\r
+                print("decrypt_data {}".format(decrypt_data))\r
+                #随机生成16个字符串 设备生成做加密使用\r
+                random_string = self.generate_random_string(16) #随机的16个字符串\r
+                enc_iv = random_string #加密的IV偏移量\r
+                #设备端进行密码加密\r
+                encrypt_data = self.aes_CBC_Encrypt(decrypt_data, self.key, enc_iv)\r
+                print("encrypt_data {}".format(encrypt_data))\r
+\r
+                #登入数据\r
+                deviceId = self.deviceId\r
+                sign_in_data = {\r
+                    "deviceId":"{}".format(deviceId), #设备ID\r
+                    "password":"{}".format(encrypt_data), #加密的数据\r
+                    "nonce":"{}".format(random_string) #随机的16个字符串\r
+                }\r
+                print(ujson.dumps(sign_in_data))\r
+                sign_url =None\r
+                sign_url = constants.DMP_BASE + "/dev/auth/sign_in"\r
+                #获取验证码\r
+                #url = "http://dmp.qipai.com.cn:30461/dev/device/bind-code/860132066060377"\r
+                # token = EventMesh.publish("get_dev_token")\r
+                # headers_T = {"Content-Type":"application/x-www-form-urlencoded","Accept":"*/*","bz-dt":"{}".format(token)}\r
+                # response = request.get(url,headers = headers_T,timeout=5)\r
+                # data = response.json()\r
+                print("dev_sign_in:{}".format(sign_url))\r
+                headers_T ={"Content-Type":"application/json"}\r
+                response_result = 0\r
+                response = 0\r
+                try:\r
+                    response = request.post(sign_url,data=ujson.dumps(sign_in_data),headers = headers_T,timeout=5)\r
+                    response_result = 1\r
+                except Exception as e:\r
+                    usys.print_exception(e)   \r
+                    print("post error!!!!!\r\n")\r
+                    response_result = 0\r
+                # {'msg': '', 'code': 20001, 'success': True, \r
+                # 'data': {'deviceId': '876226002022574', \r
+                # 'token': '95436a9e09fd0ff89feb8825257dca982f'}\r
+                # }\r
+                \r
+                #self.dev_token = ""\r
+                if response_result == 1:\r
+                    if response.status_code == 200:\r
+                        content = response.content\r
+                        c = next(content)\r
+                        data =  ujson.loads(c)\r
+                        print("data-{}".format(data))\r
+                        if data != None:\r
+                            if "success" in data:\r
+                                if data['success'] == True: #登入成功\r
+                                    if "data" in data:\r
+                                        data_tmp = data["data"]\r
+                                        if "token" in data_tmp: #token\r
+                                            self.dev_token = data_tmp["token"]\r
+                                else:#报错清空密钥\r
+                                    print("登录失败,清空密钥")\r
+                                    EventMesh.publish("set_storage_config",["dev_pass",""])\r
+                                if data["code"] == 50003:#时间错误\r
+                                    self.ntptime_flag  = 0\r
+                              \r
+                print("token:{}\r\n".format(self.dev_token))\r
+                if response != None:\r
+                    response.close()\r
+                pass\r
+        except Exception as e:\r
+                    print("post error!!!!!\r\n")\r
+                    response_result = 0\r
+        if EventMesh.publish("get_dev_token") != None:\r
+            if len(EventMesh.publish("get_dev_token")) != 0:\r
+                if self.connect_web_flag == 0:\r
+                    EventMesh.publish("post_iccid_user")\r
+                self.connect_web_flag = 1\r
+        print("dev_sign_in end")\r
+            #ICCID上传\r
+    def post_iccid_user(self,event=None,msg=None):\r
+        response = None\r
+        try:\r
+            iccid = sim.getIccid()\r
+            if iccid != -1 and len(iccid) > 5:\r
+                print("pots_ICCID:{}".format(iccid))\r
+                #Headers = {'Transfer-Encodong':'chunked'}\r
+                token = EventMesh.publish("get_dev_token")\r
+                headers_T = None\r
+                if token:\r
+                    headers_T = {"qp-dt":"{}".format(token)}\r
+                #http://dmp.raisound.com/bz-api/dev/iccid?deviceId=866610001099679&iccid=89860002192307889057\r
+                url =None\r
+                url = (constants.DMP_BASE + "/dev/iccid?deviceId={}&iccid={}").format(modem.getDevImei(),iccid)\r
+                print("{} H:{}".format(url,headers_T))\r
+                response = request.post(url,headers = headers_T)\r
+                if response.status_code == 200:\r
+                    data = response.json()\r
+                    print("pots_ICCID:{}".format(data))\r
+                else:\r
+                    print("pots_ICCID  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
+    #获取新的令牌 更新token\r
+    def update_dev_token(self, event=None, msg=None):\r
+        print("update_dev_token")\r
+        try:\r
+            dev_pass = ""\r
+            dev_pass = EventMesh.publish("get_storage_config","dev_pass")\r
+            \r
+            if dev_pass == None:\r
+                dev_pass = ""\r
+            if len(dev_pass) == 0: #没有密码走激活\r
+                print("no dev_pass:{}\r\n".format(dev_pass))\r
+                EventMesh.publish("dev_active")\r
+            token = self.dev_token\r
+            if token == None:\r
+                token = ""\r
+            if len(token) == 0: #没有token走登入\r
+                print("no dev_token:{}\r\n".format(token))\r
+                #self.dev_sign_in()\r
+                EventMesh.publish("dev_sign_in")\r
+            dev_tokenip =None\r
+            dev_tokenip = constants.DMP_BASE + "/dev/auth/token"\r
+                     \r
+            token = self.dev_token\r
+            if token == None:\r
+                token = ""\r
+            if len(token) != 0:\r
+                response_result=0\r
+                response = None\r
+                headers_T = {"bz-dt":"{}".format(token)}\r
+                try:\r
+                    response = request.get(dev_tokenip,headers = headers_T,timeout=5)\r
+                    response_result = 1\r
+                except Exception as e:\r
+                    print("get error!!!!!\r\n")\r
+                    response_result = 0\r
+                if response_result == 1:\r
+                    if response.status_code == 200:\r
+                        try:\r
+                            headers_data = response.headers\r
+                            # print("headers_data!!!!! headers_data:{}\r\n".format(headers_data))\r
+                            if "bz-dt" in headers_data: #bz-dt字段是否在头部数据里面 如果有就需要更新\r
+                                bz_dt = headers_data["bz-dt"] \r
+                                self.dev_token = bz_dt.replace(" ", "") #去掉所有的空格符\r
+                                print("update_dev_token:{}".format(self.dev_token))\r
+                        except Exception as e:\r
+                            print("headers_data!!!!! error\r\n")\r
+                if response != None:\r
+                    response.close()\r
+        except Exception as e:\r
+            usys.print_exception(e)   \r
+        print("update_dev_token end")\r
+\r
+\r
+\r
+        \r
+#OTA升级\r
+class OTA():\r
+    def __init__(self):\r
+        #EventMesh.subscribe("min_sys_ota",self.min_sys_ota)\r
+        self.downloadFileVoURL1= ""\r
+        self.downloadFileVoURL2= ""\r
+        self.downloadFileVoURLBT=""\r
+        self.BT_ota_size =0\r
+        self.ota_task_id  = None\r
+    #蓝牙升级升级,返回0,没有升级;返回,1有升级,正在执行升级;返回2,电压不够\r
+    def bt_ota(self,event=None,msg=None):\r
+        print("bt_ota_ota")\r
+        signal = net.csqQueryPoll()\r
+        if signal == -1 or sim.getStatus()  != 1:#网络不正常\r
+            print("bt_ota 无 SIM卡!!!!!!!!\r\n")\r
+            return 0\r
+        # return 0\r
+        response = None\r
+        ret_str =0\r
+\r
+        self.downloadFileVoURLBT=""\r
+        try:\r
+            #sourceVersion = versions.Build_Time\r
+            # sourceVersion = "7PIN-"+sourceVersion[:10]+"-"+sourceVersion[11:-3]\r
+            # print("sourceVersion:{}".format(sourceVersion))\r
+            # sourceVersion = sourceVersion.replace(" ", "0")\r
+            # print("sourceVersion2:{}".format(sourceVersion))\r
+            sourceVersion = version_util.get_unified_version()\r
+            print("sourceVersion:{}".format(sourceVersion))\r
+            bt_versions = EventMesh.publish("get_bt_versions")\r
+            if bt_versions == None:\r
+                bt_versions=""\r
+            print("bt_versions:{}".format(bt_versions))\r
+            #url = "http://iot.api.thisiot.com/ota/task?deviceId=860132060348836&sourceVersion=2024-12-20-15:35-1"\r
+            url =None\r
+            url = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}".format(modem.getDevImei(),sourceVersion)\r
+            print(url)\r
+            response = request.get(url,timeout=5)\r
+            if response.status_code == 200:\r
+                data = response.json()\r
+                print("OTA升级请求:{}".format(data))\r
+                \r
+                if data:\r
+                    try:\r
+                        if data["code"] == 20007:#有新版本\r
+                            if data["data"]["targetVersion"]  != bt_versions and "fileList"in data["data"]:\r
+                                #if len(data["data"]["fileList"]) == 2:\r
+                                    for fileList in data["data"]["fileList"]:\r
+                                        \r
+                                        if fileList["fileName"] == "update.ufw":\r
+                                            self.BT_ota_size = fileList["size"]\r
+                                            self.downloadFileVoURLBT = fileList["url"]\r
+                        if data["code"] == 20006:#已是最新版本\r
+                            ret_str = 0\r
+                                    \r
+                    except Exception as e:\r
+                            usys.print_exception(e)\r
+            else:\r
+                print("bt_ota get error")\r
+            response.close()\r
+        except Exception as e:\r
+            usys.print_exception(e)\r
+            if response != None:\r
+                response.close()\r
+        \r
+        print("downloadFileVoURLBT{}".format(self.downloadFileVoURLBT))\r
+        print("bt_ota end") \r
+        if self.downloadFileVoURLBT !="":\r
+            battery = EventMesh.publish("get_battery")\r
+            if battery>=40:\r
+                self.ota_timer = osTimer()\r
+                #self.ota_timer.start(15000, 0, self.ota_timer_cb)\r
+                if self.ota_task_id ==None:\r
+                    self.ota_task_id = _thread.start_new_thread(self.ota_task,())\r
+                ret_str =  1  \r
+            else:\r
+                ret_str =  2\r
+        return ret_str\r
+    #小系统升级,返回0,没有升级;返回,1有升级,正在执行升级;返回2,电压不够\r
+    def min_sys_ota(self,event=None,msg=None):\r
+        print("min_sys_ota")\r
+        signal = net.csqQueryPoll()\r
+        if signal == -1 or sim.getStatus()  != 1:#网络不正常\r
+            print("min_sys_ota 无 SIM卡!!!!!!!!\r\n")\r
+            return 0\r
+        # return 0\r
+        response = None\r
+        ret_str =0\r
+        self.downloadFileVoURL1= ""\r
+        self.downloadFileVoURL2= ""\r
+        #self.downloadFileVoURLBT=""\r
+        try:\r
+            sourceVersion = version_util.get_unified_version()\r
+            print("sourceVersion:{}".format(sourceVersion))\r
+            url =None\r
+            url = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}".format(modem.getDevImei(),sourceVersion)\r
+            print(url)\r
+            response = request.get(url,timeout=5)\r
+            if response.status_code == 200:\r
+                data = response.json()\r
+                print("OTA升级请求:{}".format(data))\r
+                \r
+                if data:\r
+                    try:\r
+                        if data["code"] == 20007:#有新版本\r
+                            if "fileList"in data["data"]:\r
+                                #if len(data["data"]["fileList"]) == 2:\r
+                                    for fileList in data["data"]["fileList"]:\r
+                                        if fileList["fileName"] == "dfota_2.bin":\r
+                                            self.downloadFileVoURL2 = fileList["url"]\r
+                                        elif fileList["fileName"] == "dfota_1.bin":\r
+                                            self.downloadFileVoURL1 = fileList["url"]\r
+                                        elif fileList["fileName"] == "update.ufw":\r
+                                            self.BT_ota_size = fileList["size"]\r
+                                            self.downloadFileVoURLBT = fileList["url"]\r
+                        if data["code"] == 20006:#已是最新版本\r
+                            ret_str = 0\r
+                                    \r
+                    except Exception as e:\r
+                            usys.print_exception(e)\r
+            else:\r
+                print("min_sys_ota get error")\r
+            response.close()\r
+        except Exception as e:\r
+            usys.print_exception(e)\r
+            if response != None:\r
+                response.close()\r
+        \r
+        print("min_sys_ota:downloadFileVoURL1:{},\ndownloadFileVoURL2:{},\ndownloadFileVoURLBT{}".format(self.downloadFileVoURL1,self.downloadFileVoURL2,self.downloadFileVoURLBT))\r
+        print("min_sys_ota end") \r
+        if self.downloadFileVoURL2!="" and self.downloadFileVoURL1!="" :\r
+            battery = EventMesh.publish("get_battery")\r
+            if battery>=40:\r
+                self.ota_timer = osTimer()\r
+                #self.ota_timer.start(15000, 0, self.ota_timer_cb)\r
+                if self.ota_task_id ==None:\r
+                    self.ota_task_id = _thread.start_new_thread(self.ota_task,())\r
+                ret_str =  1  \r
+            else:\r
+                ret_str =  2\r
+        return ret_str\r
+    \r
+    # def ota_timer_cb(self, *args, **kwargs):\r
+    #     EventMesh.publish("publish_AI_btnInfo",{"func":self.usb_event})\r
+\r
+    def ota_task(self):\r
+        import fota\r
+        utime.sleep_ms(15000)\r
+        count = 30#最多等待30秒\r
+        while 1:\r
+            count-=1\r
+            VC_satrt_flag = EventMesh.publish("get_VC_satrt_flag")#获取vc是否在运行\r
+            if VC_satrt_flag == None:\r
+                VC_satrt_flag = 0\r
+            voice_record_satrt_flag = EventMesh.publish("get_voice_record_flag")#获取静默或者语音留言是否在运行\r
+            if voice_record_satrt_flag == None:\r
+                voice_record_satrt_flag = 0\r
+            if VC_satrt_flag == 0 or count<=0 or voice_record_satrt_flag == 0:\r
+                break\r
+            utime.sleep_ms(100)\r
+        if self.downloadFileVoURLBT != "" and self.BT_ota_size != 0:\r
+            count = 5 #重试5次\r
+            while 1:\r
+                count -=1\r
+                ret = EventMesh.publish("bt_ota_run",[self.downloadFileVoURLBT,self.BT_ota_size ]) #升级蓝牙\r
+\r
+                if ret == 0 or count <=0:\r
+                    break\r
+                utime.sleep_ms(1000)\r
+            utime.sleep_ms(1000)\r
+            #EventMesh.publish("rest_TB") #复位蓝牙\r
+            utime.sleep_ms(50000)\r
+            if self.downloadFileVoURL1 == "" and self.downloadFileVoURL2 == "": #cat1没有升级的时候,要重启设备\r
+                Power.powerRestart()\r
+        if self.downloadFileVoURL1 != "" and self.downloadFileVoURL2 != "":\r
+            fota_obj = fota() \r
+            fota_obj.httpDownload(url1=self.downloadFileVoURL1,url2=self.downloadFileVoURL2)\r
+    #ota升级结果返回,开机时调用一次即可\r
+    def ota_result(self,event=None,msg=None):\r
+        response = None\r
+        try:\r
+            currentVersion = version_util.get_unified_version()\r
+            print("currentVersion:{}".format(currentVersion))\r
+            url = (constants.DMP_BASE + "/ota/result?deviceId={}&currentVersion={}").format(modem.getDevImei(),currentVersion)\r
+            print(url)\r
+            response = request.post(url,timeout=5)\r
+            if response.status_code == 200:\r
+                data = response.json()\r
+                print("OTA升级结果上报:{}".format(data))\r
+            else:\r
+                print("ota_result  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
+    #ICCID上传\r
+    def pots_ICCID(self,event=None,msg=None):\r
+        response = None\r
+        try:\r
+            iccid = sim.getIccid()\r
+            if iccid != -1 and len(iccid) > 5:\r
+                print("pots_ICCID:{}".format(iccid))\r
+                #Headers = {'Transfer-Encodong':'chunked'}\r
+                token = EventMesh.publish("get_dev_token")\r
+                headers_T = None\r
+                if token:\r
+                    headers_T = {"bz-dt":"{}".format(token)}\r
+                #http://dmp.raisound.com/bz-api/dev/iccid?deviceId=866610001099679&iccid=89860002192307889057\r
+                url =None\r
+                url = (constants.DMP_BASE + "/dev/iccid?deviceId={}&iccid={}").format(modem.getDevImei(),iccid)\r
+                print("{} H:{}".format(url,headers_T))\r
+                response = request.post(url,headers = headers_T,timeout=5)\r
+                if response.status_code == 200:\r
+                    data = response.json()\r
+                    print("pots_ICCID:{}".format(data))\r
+                else:\r
+                    print("pots_ICCID  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
+# 原 VC_State_* 整型已移除;会话/轮次见 VCTurnState / VCStateMachine(含 tts_rx_phase、speech_uplink_open)。\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
+\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
+        try:\r
+            self._vc_intent_selftest_once()\r
+        except Exception as e:\r
+            usys.print_exception(e)\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._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 _vc_cmd_diag(self, tag, text):\r
+        """本地指令诊断日志(保留,便于确认表驱动匹配情况)。"""\r
+        try:\r
+            print("[VC][cmd] {} type={} len={} repr={}".format(\r
+                tag, type(text).__name__, len(text) if text else 0, repr(text)[:72]))\r
+            if vc_text_intent is None:\r
+                print("[VC][cmd] {} vc_text_intent import=None".format(tag))\r
+                return\r
+            nt = vc_text_intent.normalize_vc_command_text(text)\r
+            print("[VC][cmd] {} norm_len={} norm_repr={}".format(tag, len(nt), repr(nt)[:72]))\r
+            if nt:\r
+                print("[VC][cmd] {} find 查询={} 信号={} 版本={}".format(\r
+                    tag, nt.find("查询"), nt.find("信号"), nt.find("版本")))\r
+                for rule in vc_text_intent._INTENT_RULES:\r
+                    if rule.get("id") == "query_signal":\r
+                        subs = rule.get("all", ())\r
+                        if subs:\r
+                            kw = subs[0]\r
+                            print("[VC][cmd] {} rule_kw0 repr={} kw_in_norm={}".format(\r
+                                tag, repr(kw), nt.find(kw)))\r
+                        break\r
+        except Exception as e:\r
+            usys.print_exception(e)\r
+\r
+    def _vc_intent_selftest_once(self):\r
+        if getattr(self, "_vc_intent_selftest_done", 0):\r
+            return\r
+        self._vc_intent_selftest_done = 1\r
+        try:\r
+            print("[VC][cmd] selftest start")\r
+            if vc_text_intent is None:\r
+                print("[VC][cmd] selftest FAIL vc_text_intent=None")\r
+                return\r
+            sample = "查询信号"\r
+            r = vc_text_intent.match_vc_command(sample, {})\r
+            print("[VC][cmd] selftest sample={} match={}".format(repr(sample), r))\r
+            self._vc_cmd_diag("selftest", sample)\r
+        except Exception as e:\r
+            usys.print_exception(e)\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
+            print("[VC][cmd] 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
+            print("[VC][cmd] match_vc_command r is None")\r
+            self._vc_cmd_diag("match_fail", text)\r
+            return None\r
+        print("[VC][cmd] match intent={}".format(r.get("intent")))\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
+            print("[VC][cmd] needs_payload skip intent={}".format(r.get("intent")))\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
+        self._vc_cmd_diag("check_text_in", text)\r
+        if len(text) > 60:\r
+            print("[VC][cmd] check_text skip len>60")\r
+            return -1\r
+        try:\r
+            if not isinstance(text, str):\r
+                print("[VC][cmd] check_text skip not str type={}".format(type(text).__name__))\r
+                return -1\r
+            ret = self._apply_vc_text_intent(text, None, False)\r
+            if ret is not None:\r
+                print("[VC][cmd] check_text table ret={}".format(ret))\r
+                return ret\r
+            print("[VC][cmd] check_text no_match ret=-1")\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
+        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 _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
+            if getattr(self.fsm, "await_played", False):\r
+                # PTT(VC_mode==1) 也必须走本段:旧逻辑整段包在 VC_mode!=1 内会导致 tts.completed 后仍 await_played,\r
+                # 无人触发 VC_Play_End,绿灯/playing 卡死至按键。\r
+                # 不能只用 stream_is_empty:队列仍可能有待播数据时提前关播会“没声”;in/out 字节在分帧下可能未完全对齐,\r
+                # 在已 tts.completed 且队列已空时,以服务端结束下发 + PCM 空为准。\r
+                is_pcm_empty = (self.play_pcm == None) or (self.play_pcm != None and self.play_pcm.stream_is_empty() == 1)\r
+                try:\r
+                    q_empty = self.AI_audio_queue.empty()\r
+                except Exception:\r
+                    q_empty = True\r
+                try:\r
+                    in_len = getattr(self, "tts_in_data_len", 0)\r
+                    out_len = getattr(self, "tts_out_data_len", 0)\r
+                except Exception:\r
+                    in_len, out_len = 0, 0\r
+                try:\r
+                    tts_done_ms = getattr(self.fsm, "last_tts_completed_ms", 0)\r
+                except Exception:\r
+                    tts_done_ms = 0\r
+                drained_strict = (in_len > 0 and out_len >= in_len and q_empty)\r
+                drained_after_server = (bool(tts_done_ms) and q_empty)\r
+                drained = drained_strict or drained_after_server\r
+                # 8s 超时:兜底认为播放结束(避免播放状态卡死)\r
+                too_long = False\r
+                try:\r
+                    t0 = getattr(self.fsm, "last_tts_completed_ms", 0)\r
+                    if t0:\r
+                        too_long = utime.ticks_diff(utime.ticks_ms(), t0) > 8000\r
+                except Exception:\r
+                    pass\r
+                if (is_pcm_empty and drained) or too_long:\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
+        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
+                                                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
+                                        #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
+        \r
+def num_str_to_chinese(num_str):\r
+    # 数字与中文映射(含零)\r
+    num_map = {\r
+        '0': '零', '1': '一', '2': '二', '3': '三', '4': '四',\r
+        '5': '五', '6': '六', '7': '七', '8': '八', '9': '九'\r
+    }\r
+    chinese = ''\r
+    # 逐位转换,拼接单位\r
+    for char in num_str:\r
+        if char in num_map:\r
+            digit = num_map[char]\r
+            chinese += digit\r
+        else:\r
+            chinese += char\r
+    \r
+    return chinese\r
+\r
index bde4de0..4360c21 100644 (file)
-# 设备侧默认配置(freeze 内置)。
-#
-# 加载优先级(由 vc_config.py 执行):
-# 1) usr/default_config.py(或 usr/defualt_config.py)
-# 2) 本文件 default_config.py
-
-# 当前设备类型(编译期间确定;现场仅可用 usr/default_config.py 覆盖)
-DEV_TYPE = "7PIN"
-
-# 是否加载 quecgnss 并在定位管理器初始化时调用 gnssEnable(0)。
-# 无 GNSS 硬件的机型(如部分吊坠)可置 False,避免 import quecgnss 或底层驱动异常/阻塞。
-GNSS_ENABLED = False
-
-# 为 True 时,protocol 模块 import 过程会打印 [proto.import] 阶段标记(配合 qp 日志口排查卡点)。
-PROTOCOL_IMPORT_TRACE = True
-
-# 当前设备配置(仅保留“设备差异项”,不要放运行态变量)
-# led_gpio 使用 Pin 属性名(字符串),由 vc_config.py 转成 Pin.GPIOxx 常量。
-LED_GPIO = ("GPIO36", "GPIO44", "GPIO43")  # (R, G, B)
-
-# LED 电平极性(是否“反向”点亮)。
-# - 默认 LED_ACTIVE_LOW=False:高电平点亮(write(1)=亮)
-# - 若你的硬件是低电平点亮(write(0)=亮),将 LED_ACTIVE_LOW=True
-# - 若仅某一路反相,用 LED_ACTIVE_LOW_RGB 单独配置(按 LED_GPIO 顺序 R/G/B)
-LED_ACTIVE_LOW = False
-LED_ACTIVE_LOW_RGB = None  # 例如 (True, False, False) 表示仅红灯反相
-# LED_ACTIVE_LOW_RGB = (True, False, False)
-
-# idle 绿灯长慢闪:常亮 / 熄灭(ms);仅配 LED_IDLE_GREEN_BLINK_MS 时灭灯时长与常亮相同(对称慢闪)
-LED_IDLE_GREEN_BLINK_MS = 1000
-LED_IDLE_GREEN_BLINK_OFF_MS = 2000
-
-# 是否支持 UART 相关能力(例如 7PIN_1_5 才允许走串口)
-UART_SUPPORTED = False
-
-# 语言/地区(如 zh/en),用于少量文案/逻辑差异
-LANG = "zh"
-
-# --------------------
-# 硬件差异项(由每个设备的 default_config 固定)
-# --------------------
-LED_DEBUG = True
-LED_STATE_DEBUG = True
-KEY_LED_DEBUG = True
-HW_STATUS_DEBUG = True
-
-# --------------------
-# 电池 OCV / 硬件状态轮询(AI_hw_status,可在 usr/default_config.py 覆盖)
-# --------------------
-# True 时用三档电压门限估算 SoC,忽略 BATTERY_OCV_MAP
-USE_SIMPLE_VBATT_THRESH = False
-VBATT_CHARGE_FULL_MV = 4290   # USB 在位且电压≥此值视为充满(get_battery / AI_hw_status / AI_dev 共用)
-VBATT_LOW_MV = 3699           # 低电电压门限;simple 模式与 AI_dev 低电提示用
-VBATT_EMPTY_MV = 3629         # 空电电压门限;AI_dev 低电关机等
-BATTERY_LOW_SOC_PERCENT = 30  # OCV 模式:soc < 此值 → battery=low(REQ-BAT-02:10%~30%)
-BATTERY_EMPTY_SOC_PERCENT = 10  # OCV 模式:soc < 此值 → battery=empty(REQ-BAT-03:<10%)
-BATTERY_LOW_WARN_INTERVAL_MS = 300000  # REQ-BAT-02:低电语音每 5 分钟
-BATTERY_EMPTY_FLASH_COUNT = 3       # REQ-BAT-03:红快闪次数
-BATTERY_EMPTY_FLASH_PERIOD_MS = 500  # REQ-BAT-03:约 2 次/秒
-HW_STATUS_PERIOD_MS = 10000
-SIGNAL_WEAK_CSQ = 10
-
-# 开路电压(mV) -> SoC(%),按电压线性插值;None 则用 ai_VC 内置默认表
-BATTERY_OCV_MAP = {
-    4290: 100, 4237: 95, 4179: 90, 4123: 85, 4070: 80, 4020: 75, 3963: 70, 3919: 65, 3882: 60, 3847: 55,
-    3818: 50, 3794: 45, 3772: 40, 3752: 35, 3733: 30, 3716: 25, 3699: 20, 3677: 15, 3652: 10, 3629: 5, 3400: 0,
-}
-
-# 是否需要初始化蓝牙电源/马达/音频IIS配置等(原先仅 7PIN_1_5)
-BT_POWER_MOTOR_ENABLED = False
-PIN_BT_ON_GPIO = None      # 例如 "GPIO31"
-PIN_MOT_GPIO = None
-# audio.set_qdai_cfg(...) 参数;若为 None 则不调用
-AUDIO_QDAI_CFG = None      # 例如 (1,0,0,2,0,1,1,1)
-
-# motor.py(motorEventManager):马达输出脚与节拍。MOTOR_GPIO 为 None 时依次尝试 PIN_MOT_GPIO、默认 GPIO36。
-MOTOR_PIN_GPIO = "GPIO20"          # 例如 "GPIO36";无马达硬件可保持 None
-MOTOR_TICK_MS = 100
-MOTOR_ACTIVE_LOW = False
-MOTOR_BOOT_VIBRATE_MS = 200 # 马达开机震动时间 200ms
-
-
-# AI 按键中断与电源键中断引脚(ExtInt.GPIOxx)
-EXTINT_AI_GPIO = "GPIO22"   # ai按键
-PWK_KEY_GPIO = "GPIO26"     # 侧边power按键 , 模式切换
-
-#btn_device_ec800m.py
-BTN_WECHAT_GPIO = None
-BTN_SOS_GPIO = None
-
-
-# 蓝牙唤醒 4G/4G 唤醒蓝牙相关(原先仅 7PIN_1_5)
-BT_WAKEUP_4G_ENABLED = False
-EXTINT_WAKEUP_CTA1_GPIO = None  # 例如 "GPIO22"
-PIN_WAKEUP_BT_GPIO = None       # 例如 "GPIO21"
-
-# --------------------
-# 按键/防抖差异
-# --------------------
-
-# 释放防抖阈值(KeyAI/pwk):原代码部分设备用 3,其他用 5
-KEY_RELEASE_DEBOUNCE_COUNT = 5
-
-# 是否允许 AI 键长按 8 秒触发关机逻辑(原 jswm/jswm_BOX)
-AI_LONG_PRESS_POWER_OFF_ENABLED = False
-
-# 是否启用 SOS:False 时禁用 AI 键长按 3s 拨紧急号、以及按键板上的 SOS 长按拨号入口
-SOS_ENABLED = False
-
-# VC WebSocket 文本保活 ping 间隔(秒)。原逻辑约每 4 秒一次,空闲功耗偏高;拉大间隔可降低唤醒与发包频率
-VC_WS_PING_INTERVAL_SEC = 55
-
-# --------------------
-# VC 空闲/超时(ai_VC._1S_timer_cb_ 每秒累计)
-# --------------------
-# 长对话:无有效业务下行累计达此秒数后结束会话;代码侧最小 5
-VC_IDLE_AUTO_CLOSE_SEC = 20
-# 单对话侧预留(当前与 idle+深睡统一处理,与 7PIN_1_5 对齐)
-VC_IDLE_AUTO_CLOSE_SEC_PTT = 0
-# 业务 idle 累计达此秒数触发深睡断开;代码侧最小 10;0 关闭请改 ENABLE
-VC_IDLE_DEEP_SLEEP_SEC = 60
-VC_IDLE_DEEP_SLEEP_ENABLE = 1
-
-# --------------------
-# 开机提示/语音参数默认值(与具体产品绑定,编译期决定)
-# --------------------
-
-# 若为 None 或 POWER_ON_TONE_ENABLED=False,则不开机播报提示词
-POWER_ON_TONE_ENABLED = True
-POWER_ON_TONE_DEFAULT = "你好,我来了"
-
-# VAD 静音阈值默认值(ms)
-VAD_SILENCE_DEFAULT = 300
-
-# 默认音色配置(与 ai_VC.py 中格式保持一致)
-VC_TTS_VOICE_TYPE_DEFAULT = None  # dict 或 None(None 表示沿用 ai_VC.py 内置兜底/存储值)
-VC_TTS_VOICE_KEY_DEFAULT = None   # 例如 "男"/"童"
-
-# VC WebSocket 路径(ws://{AMP_SERVER}/{VC_WS_PATH}...)
-VC_WS_PATH = "vc/agent"
-
-# 是否支持蓝牙 OTA(原先仅 7PIN_1_5 会检查 bt_ota)
-BT_OTA_ENABLED = False
-
-# 是否在“开始上传语音”时播放成功提示音(原逻辑:非 7PIN/7PIN_1_5/7PIN_coze 才播)
-UPLOAD_START_TONE_ENABLED = False
-
-# 正常待机 idle 灯效:off=灭灯(REQ-BAT-01);green_blink=旧工程绿灯慢闪
-LED_IDLE_MODE = "off"
-
-# LLM thinking 阶段 LED 策略:flicker_blue_green / open_red_green
-THINKING_LED_MODE = "flicker_blue_green"
-
-# 收到 vad.active 时是否强制切回蓝灯
-VAD_ACTIVE_BLUE_LED = True
-
-# 本地指令:切换对话模式时的提示语(是什么语言就写什么)
-CMD_TOGGLE_MODE_TO_PTT_TTS = "已切换到单对话模式"
-CMD_TOGGLE_MODE_TO_LONG_TTS = "已切换到长对话模式"
-
-# 蓝牙/语音留言类本地指令是否启用(原先仅 7PIN_1_5)
-BT_VOICE_FEATURES_ENABLED = False
-
-# “关机”语音指令是否仅关闭对话(不触发 Power_off)。原逻辑:7PIN/7PIN_coze/7PIN_1_5 只 Close
-POWEROFF_CMD_CLOSE_ONLY = True
-
-# 关键词唤醒时在 PTT 模式下播报的提示语
-KWS_PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "
-
-# 对话智能体/工作流默认配置(编译期确定;可在 usr/default_config.py 覆盖)
-# 结构示例:
-# DIALOG_BOT_OPTION = {"bot_id":"...", "workflow_id":"...", "use_workflow":True}
-DIALOG_BOT_OPTION = None
-
-# VC Start 请求里 dialog_attributes.cfg.bot_option(与 DIALOG_BOT_OPTION 二选一或叠加:后者可再覆盖)
-# 允许空字符串 "";未定义键时沿用下方默认值;若两者都为空则自动 use_workflow=False
-VC_BOT_ID = "7462617881076351002"
-VC_WORKFLOW_ID = "7462621757327196187"
-
-# dialog_attributes.provider 覆盖(None 表示沿用存储配置 dialog_attributes_provider)
-DIALOG_PROVIDER_DEFAULT = None
-
-# 是否从 VC_TTS_voice_type 的 key 列表生成 ASR 热词
-KEYWORDS_FROM_VOICE_TYPE = False
-
-# --------------------
-# 按键音/提示音策略(设备差异项)
-# --------------------
-
-# AI 键按下时:若设置了该文件路径,则在“无 VC 流播放中”时播放该提示音(例如 "U:/static/start.mp3")
-KEY_PRESS_TONE_FILE = None
-
-# AI 键按下时:延迟播放按键音(ms),规避蓝牙刚连上不出声等(与 7PIN_1_5 一致)
-KEY_PRESS_TONE_DELAY_MS = 300
-
-# 单对话长按开麦:开录提示音(None=不播,流畅优先;可设 U:/static/start.mp3)
-RECORD_START_TONE_FILE = None
-RECORD_START_TONE_GAP_MS = 180
-# 单对话松手后延迟(ms)再发 audio.completed,利于尾包/ASR 收敛
-PTT_RELEASE_END_SPEECH_DELAY_MS = 200
-# 单对话打断:发 cancel 后等待 canceled 的最长时间(ms),超时仍开录
-PTT_CANCEL_WAIT_MS = 2500
-# PTT 本地硬静音后等待 codec/DMA 收尾(ms),减轻打断尾音
-PTT_POST_MUTE_MS = 30
-
-# PTT 提示语(单对话模式,未长按时的提示;是什么语言就写什么)
-PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "
-
+# -*- coding: utf-8 -*-\r
+# k8 产品 — 设备侧默认配置(编译期 freeze 进固件)\r
+#\r
+# 加载优先级(vc_config.py):\r
+#   1) usr/default_config.py(现场覆盖)\r
+#   2) 本文件(由 new_clean.bat 按 product_k8.ini 的 DEV_TYPE 拷贝到 core/default_config.py)\r
+#\r
+# 约定:仅放「机型/产品差异」常量,勿放运行态变量。\r
+\r
+# =============================================================================\r
+# 1. 产品与区域\r
+# =============================================================================\r
+\r
+# 硬件平台类型(与 products/configs/<DEV_TYPE>/ 目录名一致)\r
+DEV_TYPE = "7PIN"\r
+\r
+# 文案/逻辑用语言(如 zh / en)\r
+LANG = "zh"\r
+\r
+# 设备鉴权(device_res_author:HMAC 签名与 AES 加解密)产品名“小量博士”\r
+PRODUCT_PID = "DieBeXahZHLrjSnORbgN"\r
+PRODUCT_KEY = "FuXG4ptpGbzvKbdB4vpUypisbENgy2nB"  # 32 字节,运行时转为 bytes\r
+\r
+# =============================================================================\r
+# 2. GPIO 引脚\r
+# =============================================================================\r
+\r
+# --- 2.1 三色 LED(Pin 属性名字符串,vc_config 转为 Pin.GPIOxx)---\r
+LED_GPIO = ("GPIO36", "GPIO44", "GPIO43")  # (R, G, B)\r
+\r
+# 点亮电平:False=高电平亮;True=低电平亮;仅某路反相用 LED_ACTIVE_LOW_RGB\r
+LED_ACTIVE_LOW = False\r
+LED_ACTIVE_LOW_RGB = None  # 例 (True, False, False) 仅红灯反相\r
+\r
+# --- 2.2 按键 ExtInt ---\r
+EXTINT_AI_GPIO = "GPIO22"   # AI 键\r
+PWK_KEY_GPIO = "GPIO26"     # 电源键(短按/双击切模式等)\r
+\r
+# btn_device_ec800m:扩展按键(本机型未用)\r
+BTN_WECHAT_GPIO = None\r
+BTN_SOS_GPIO = None\r
+\r
+# --- 2.3 马达(motor.py / motorEventManager)---\r
+MOTOR_PIN_GPIO = "GPIO20"   # None 表示无马达\r
+MOTOR_TICK_MS = 100\r
+MOTOR_ACTIVE_LOW = False\r
+MOTOR_BOOT_VIBRATE_MS = 200  # 开机震动时长(ms)\r
+\r
+# --- 2.4 蓝牙电源 / 音频 IIS(本机型关闭)---\r
+BT_POWER_MOTOR_ENABLED = False\r
+PIN_BT_ON_GPIO = None\r
+PIN_MOT_GPIO = None\r
+AUDIO_QDAI_CFG = None  # 非 None 时调用 audio.set_qdai_cfg(...)\r
+\r
+# --- 2.5 蓝牙↔4G 唤醒(本机型关闭)---\r
+BT_WAKEUP_4G_ENABLED = False\r
+EXTINT_WAKEUP_CTA1_GPIO = None\r
+PIN_WAKEUP_BT_GPIO = None\r
+\r
+# =============================================================================\r
+# 3. LED 灯效策略(AI_led / ai_VC,对齐交互规范)\r
+# =============================================================================\r
+\r
+# 正常待机 idle:off=灭灯(REQ-BAT-01);green_blink=旧工程绿灯慢闪\r
+LED_IDLE_MODE = "off"\r
+\r
+# green_blink 模式下的亮/灭时长(ms);LED_IDLE_MODE=off 时不生效\r
+LED_IDLE_GREEN_BLINK_MS = 1000\r
+LED_IDLE_GREEN_BLINK_OFF_MS = 2000\r
+\r
+# LLM 思考阶段:flicker_blue_green | open_red_green\r
+THINKING_LED_MODE = "flicker_blue_green"\r
+\r
+# 收到 vad.active 是否强制回蓝灯(长对话)\r
+VAD_ACTIVE_BLUE_LED = True\r
+\r
+# 收音 listening:长对话蓝慢闪(1000ms)、单对话蓝快闪(250ms) 由 VC_mode 在 AI_led 内切换,无需本文件单独配置\r
+\r
+# =============================================================================\r
+# 4. 电池与硬件状态(AI_hw_status / AI_dev)\r
+# =============================================================================\r
+\r
+HW_STATUS_PERIOD_MS = 10000\r
+SIGNAL_WEAK_CSQ = 10  # 0≤CSQ<此值 → signal=weak(REQ-ERR-02)\r
+\r
+# SoC 百分比门限(OCV 模式,对齐 REQ-BAT-02/03)\r
+BATTERY_LOW_SOC_PERCENT = 30\r
+BATTERY_EMPTY_SOC_PERCENT = 10\r
+BATTERY_LOW_WARN_INTERVAL_MS = 300000  # 低电语音间隔 5min\r
+BATTERY_EMPTY_FLASH_COUNT = 3\r
+BATTERY_EMPTY_FLASH_PERIOD_MS = 500    # 空电红快闪约 2 次/秒\r
+\r
+# 电压门限(mV):充满 / 低电 / 空电(与 get_battery、simple 模式共用)\r
+USE_SIMPLE_VBATT_THRESH = False  # True 时忽略 BATTERY_OCV_MAP,仅用三档电压\r
+VBATT_CHARGE_FULL_MV = 4290\r
+VBATT_LOW_MV = 3699\r
+VBATT_EMPTY_MV = 3629\r
+\r
+# 开路电压(mV)→SoC(%) 插值表;None 则用 ai_VC 内置默认表\r
+BATTERY_OCV_MAP = {\r
+    4290: 100, 4237: 95, 4179: 90, 4123: 85, 4070: 80, 4020: 75, 3963: 70, 3919: 65, 3882: 60, 3847: 55,\r
+    3818: 50, 3794: 45, 3772: 40, 3752: 35, 3733: 30, 3716: 25, 3699: 20, 3677: 15, 3652: 10, 3629: 5, 3400: 0,\r
+}\r
+\r
+# =============================================================================\r
+# 5. 可选外设与协议\r
+# =============================================================================\r
+\r
+GNSS_ENABLED = False           # 无 GNSS 硬件时 False,避免 import quecgnss\r
+UART_SUPPORTED = False\r
+BT_OTA_ENABLED = False\r
+UPLOAD_START_TONE_ENABLED = False  # 开始上传语音时是否播成功提示音\r
+\r
+# =============================================================================\r
+# 6. 按键逻辑(AI_dev / btn_device)\r
+# =============================================================================\r
+\r
+KEY_RELEASE_DEBOUNCE_COUNT = 5\r
+AI_LONG_PRESS_POWER_OFF_ENABLED = False  # AI 键长按 8s 关机\r
+SOS_ENABLED = False                      # AI 长按 3s SOS、板载 SOS 入口\r
+\r
+# =============================================================================\r
+# 7. VC 会话 / WebSocket / 空闲与休眠\r
+# =============================================================================\r
+\r
+# 语音 WS 路径段(无首尾斜杠);完整 URL 见 default_srv.AMP_SERVER + 文首拼接说明\r
+VC_WS_PATH = "vc/agent"\r
+VC_WS_PING_INTERVAL_SEC = 55  # 文本 ping 间隔,过大影响保活,过小费电\r
+\r
+# 长对话:无有效业务下行累计达此秒数结束会话(代码最小 5)\r
+VC_IDLE_AUTO_CLOSE_SEC = 20\r
+VC_IDLE_AUTO_CLOSE_SEC_PTT = 0  # 单对话预留,当前与 idle 统一处理\r
+\r
+# 业务 idle 达此秒数触发深睡断开(最小 10;ENABLE=0 关闭)\r
+VC_IDLE_DEEP_SLEEP_SEC = 60\r
+VC_IDLE_DEEP_SLEEP_ENABLE = 1\r
+\r
+VAD_SILENCE_DEFAULT = 300  # VAD 静音阈值(ms)\r
+\r
+# =============================================================================\r
+# 8. 语音对话 / TTS / 智能体\r
+# =============================================================================\r
+\r
+POWER_ON_TONE_ENABLED = True\r
+POWER_ON_TONE_DEFAULT = "你好,我来了"\r
+\r
+VC_TTS_VOICE_TYPE_DEFAULT = None   # dict 或 None\r
+VC_TTS_VOICE_KEY_DEFAULT = None    # 如 "男" / "童"\r
+\r
+DIALOG_BOT_OPTION = None           # {"bot_id", "workflow_id", "use_workflow"}\r
+VC_BOT_ID = "7462617881076351002"\r
+VC_WORKFLOW_ID = "7462621757327196187"\r
+DIALOG_PROVIDER_DEFAULT = None\r
+KEYWORDS_FROM_VOICE_TYPE = False\r
+\r
+BT_VOICE_FEATURES_ENABLED = False\r
+\r
+# =============================================================================\r
+# 9. 本地指令与固定话术\r
+# =============================================================================\r
+\r
+# protocol.ai_cmd_handle:云端 NLP 扩展(会议纪要 / 实时翻译 / 历史留言)\r
+NLP_EXTENDED_CMDS = False\r
+\r
+CMD_TOGGLE_MODE_TO_PTT_TTS = "已切换到单对话模式"\r
+CMD_TOGGLE_MODE_TO_LONG_TTS = "已切换到长对话模式"\r
+POWEROFF_CMD_CLOSE_ONLY = False     # 「关机」指令仅 Close,不真断电\r
+\r
+KWS_PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "\r
+PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "\r
+\r
+# =============================================================================\r
+# 10. 按键音 / 开录音 / PTT 时序(k8:开录仅以灯提示,不播专用开录音)\r
+# =============================================================================\r
+\r
+KEY_PRESS_TONE_FILE = None         # AI 短按操作音;None=不播\r
+KEY_PRESS_TONE_DELAY_MS = 300\r
+\r
+RECORD_START_TONE_FILE = None      # 单对话长按开录提示音;None=仅蓝快闪\r
+RECORD_START_TONE_GAP_MS = 180\r
+\r
+PTT_RELEASE_END_SPEECH_DELAY_MS = 200   # 松手后发 audio.completed 前延迟\r
+PTT_CANCEL_WAIT_MS = 2500               # 打断后等 canceled 最长时间\r
+PTT_POST_MUTE_MS = 30                   # 本地硬静音后 codec 收尾\r
+\r
+# =============================================================================\r
+# 11. 调试开关(量产可全 False)\r
+# =============================================================================\r
+\r
+LED_DEBUG = True\r
+LED_STATE_DEBUG = True\r
+KEY_LED_DEBUG = True\r
+HW_STATUS_DEBUG = True
\ No newline at end of file
index 5ae3122..c333e1a 100644 (file)
@@ -1,34 +1,99 @@
-# 设备侧 Server 配置(freeze 内置)。\r
+# 设备侧 Server 配置(编译期由 product_*.ini 的 SRV_TYPE 拷贝为 core/default_srv.py 并 freeze)。\r
 #\r
-# 注意:\r
-# - `constants.py` 中固定不变的常量(事件名/枚举)继续保留在 constants.py。\r
-# - 本文件统一承载“协议/服务器相关的可配置项”,并支持 usr 覆盖。\r
+# =============================================================================\r
+# 加载与修改方式(改环境时按此检查,避免漏改)\r
+# =============================================================================\r
+#\r
+# 编译:\r
+#   product_k8.ini 中 SRV_TYPE=beike_dev\r
+#     → new_clean.bat 拷贝本文件 → core/default_srv.py → 打进固件\r
+#\r
+# 运行时优先级(srv_config.py):\r
+#   1) usr/default_srv.py(现场覆盖,无需重编)\r
+#   2) freeze 内置 core/default_srv.py\r
+#\r
+# 启动时注入 constants(constants.py 仅读取下列 3 项到模块常量):\r
+#   DMP_BASE, AMP_SERVER, IOT_WS_URL  →  constants.DMP_BASE / AMP_SERVER / IOT_WS_URL\r
+#\r
+# 与 default_config.py 的分工(勿混在同一文件改):\r
+#   - 本文件:环境 / 域名 / 主机(随 SRV_TYPE 变,如 beike_dev ↔ beike)\r
+#   - default_config:产品能力(随 DEV_TYPE/k8 变),含 VC_WS_PATH、VC_BOT_ID 等\r
+#\r
+# =============================================================================\r
+# URL 拼接总览(改 VC 联调时两项都要对)\r
+# =============================================================================\r
+#\r
+# 【语音对话 WebSocket — AI_VC.websocket_connect_VC】\r
+#   主机:constants.AMP_SERVER(本文件 AMP_SERVER)\r
+#         → ai_VC 赋给 self.server_ip(可被存储 server_ip / 云端下发覆盖)\r
+#   路径:default_config.VC_WS_PATH(vc_config,如 k8 为 "vc/agent")\r
+#   查询:is_auth、设备 token(get_dev_token)\r
+#\r
+#   拼接结果:\r
+#     req_str = "{VC_WS_PATH}?is_auth={is_auth}"\r
+#     有 token:  ws://{AMP_SERVER}/{req_str}&dt={token}\r
+#     无 token:  ws://{AMP_SERVER}/{req_str}\r
+#\r
+#   示例(beike_dev + k8):\r
+#     ws://118.145.158.207:22080/vc/agent?is_auth=...&dt=...\r
+#\r
+# 【IoT 设备云 WebSocket — protocol.py】\r
+#   完整 URL 已在本文件写死为 IOT_WS_URL,仅追加 token:\r
+#     {IOT_WS_URL}?bz-dt={token}\r
+#   示例:ws://dmp-dev.raisound.com/iot-api/ws-hub/dev/v1?bz-dt=...\r
+#\r
+# 【DMP HTTP — ai_VC 内认证/校时/ICCID/OTA 结果等】\r
+#   前缀:constants.DMP_BASE(本文件 DMP_BASE,无尾部斜杠)\r
+#   路径:ai_VC 内目前为字面量 "/dev/..."(与下方 DMP_*_PATH 取值应对齐)\r
+#   示例:{DMP_BASE}/dev/time 、{DMP_BASE}/dev/auth/active\r
+#\r
+# 【OTA 检查任务 — ai_VC OTA 查询】\r
+#   当前 ai_VC 使用完整 URL 字面量(应与 OTA_TASK_URL_FMT 一致):\r
+#     OTA_TASK_URL_FMT.format(imei, sourceVersion)\r
+#   OTA 结果上报:{DMP_BASE}/ota/result?deviceId=...(路径与 OTA_RESULT_PATH_FMT 应对齐)\r
+#\r
+# 【AMP 健康检查 — ai_VC 错误上报】\r
+#   使用 self.server_ip(同 AMP_SERVER)+ 硬编码路径:\r
+#     http://{server_ip}/health?task_id={...}\r
+#   与 AMP_HEALTH_URL_FMT 形式一致(第一参数为 host:port,无 http 前缀)\r
+#\r
+# 注意:下方 DMP_*_PATH / OTA_*_FMT / AMP_HEALTH_URL_FMT 为**集中文档与目标值**;\r
+# constants 启动时尚未自动读取这些路径常量;修改后若 ai_VC 仍写死字符串,需同步改代码或接 srv_config.get_attr。\r
+#\r
+# =============================================================================\r
 \r
-# ---- 环境选择(服务器地址由 srv_configs/<SERVER_ENV>.py 承载)----\r
-# 服务器环境配置,切换只需修改 SERVER_ENV\r
-# "beike" = 北科测试, "kxc" = 科学城demo\r
+# ---- 环境标识(仅注释/运维用;constants.SERVER_ENV 会读取)----\r
 SERVER_ENV = "beike_dev"\r
 \r
-# ---- 默认环境地址(即使没有 srv_configs/<env>.py 也能工作)----\r
-# 这里默认给出 beike_dev 的地址;若要切换环境,建议:\r
-# - 修改 SERVER_ENV\r
-# - 同时提供对应的 srv_configs/<SERVER_ENV>.py(或 usr 覆盖)\r
+# ---- 三套「根地址」(constants.py 启动时加载,改环境必改)----\r
+\r
+# DMP 业务 HTTP 根路径(无尾斜杠)。用于 DMP_BASE + "/dev/..." 、"/ota/result?..."\r
 DMP_BASE = "http://dmp-dev.raisound.com/dmp-api"\r
+\r
+# 语音对话 AMP 主机 host:port(无 ws://)。用于 ws://{AMP_SERVER}/{VC_WS_PATH}...\r
+# 须与 default_config.VC_WS_PATH 联调;勿漏改 VC_WS_PATH(在 configs/k8/default_config.py)\r
 AMP_SERVER = "118.145.158.207:22080"\r
+\r
+# IoT 长连接完整 WebSocket URL(含 ws://)。protocol 仅追加 ?bz-dt=token\r
 IOT_WS_URL = "ws://dmp-dev.raisound.com/iot-api/ws-hub/dev/v1"\r
 \r
-# ---- DMP/认证/时间同步等(默认使用 constants.DMP_BASE 拼接)----\r
-DMP_TIME_PATH = "/dev/time"\r
-DMP_ACTIVE_PATH = "/dev/auth/active"\r
-DMP_SIGN_IN_PATH = "/dev/auth/sign_in"\r
-DMP_TOKEN_PATH = "/dev/auth/token"\r
-DMP_ICCID_PATH = "/dev/iccid"\r
-DMP_DEV_CONFIG_PATH_FMT = "/dev/config/{}"  # deviceId\r
+# ---- DMP 子路径(相对 DMP_BASE;与 ai_VC 内硬编码路径保持一致)----\r
+\r
+DMP_TIME_PATH = "/dev/time"                    # 校时 GET  → dev_NTP\r
+DMP_ACTIVE_PATH = "/dev/auth/active"           # 激活 POST\r
+DMP_SIGN_IN_PATH = "/dev/auth/sign_in"        # 登录\r
+DMP_TOKEN_PATH = "/dev/auth/token"             # 取 token → get_dev_token 链路\r
+DMP_ICCID_PATH = "/dev/iccid"                 # ICCID 上报(带 query)\r
+DMP_DEV_CONFIG_PATH_FMT = "/dev/config/{}"     # 设备配置,format(deviceId)\r
 \r
-# ---- OTA(现网有的走 DMP_BASE,有的走 qipai 域名;先集中成可配项)----\r
+# ---- OTA ----\r
+\r
+# 完整 URL 模板;占位符 {} 依次为 deviceId(IMEI)、sourceVersion\r
 OTA_TASK_URL_FMT = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}"\r
+\r
+# 相对 DMP_BASE 的路径模板(上报 OTA 结果)\r
 OTA_RESULT_PATH_FMT = "/ota/result?deviceId={}&currentVersion={}"\r
 \r
-# ---- AMP/健康检查等 ----\r
-AMP_HEALTH_URL_FMT = "http://{}/health?task_id={}"\r
+# ---- AMP 健康检查(host 用 AMP_SERVER / server_ip,非 DMP_BASE)----\r
 \r
+AMP_HEALTH_URL_FMT = "http://{}/health?task_id={}"  # .format(server_ip, task_id)\r
index 6ff926c..4360c21 100644 (file)
-# 设备分支配置:jswm\r
-# 仅用于编译/打包阶段选择,不作为运行时加载入口。\r
+# -*- coding: utf-8 -*-\r
+# k8 产品 — 设备侧默认配置(编译期 freeze 进固件)\r
+#\r
+# 加载优先级(vc_config.py):\r
+#   1) usr/default_config.py(现场覆盖)\r
+#   2) 本文件(由 new_clean.bat 按 product_k8.ini 的 DEV_TYPE 拷贝到 core/default_config.py)\r
+#\r
+# 约定:仅放「机型/产品差异」常量,勿放运行态变量。\r
 \r
-DEV_TYPE = "jswm"\r
+# =============================================================================\r
+# 1. 产品与区域\r
+# =============================================================================\r
 \r
-LED_GPIO = ("GPIO36", "GPIO44", "GPIO21")  # (R, G, B)\r
-UART_SUPPORTED = False\r
+# 硬件平台类型(与 products/configs/<DEV_TYPE>/ 目录名一致)\r
+DEV_TYPE = "7PIN"\r
+\r
+# 文案/逻辑用语言(如 zh / en)\r
 LANG = "zh"\r
 \r
+# 设备鉴权(device_res_author:HMAC 签名与 AES 加解密)产品名“小量博士”\r
+PRODUCT_PID = "DieBeXahZHLrjSnORbgN"\r
+PRODUCT_KEY = "FuXG4ptpGbzvKbdB4vpUypisbENgy2nB"  # 32 字节,运行时转为 bytes\r
+\r
+# =============================================================================\r
+# 2. GPIO 引脚\r
+# =============================================================================\r
+\r
+# --- 2.1 三色 LED(Pin 属性名字符串,vc_config 转为 Pin.GPIOxx)---\r
+LED_GPIO = ("GPIO36", "GPIO44", "GPIO43")  # (R, G, B)\r
+\r
+# 点亮电平:False=高电平亮;True=低电平亮;仅某路反相用 LED_ACTIVE_LOW_RGB\r
+LED_ACTIVE_LOW = False\r
+LED_ACTIVE_LOW_RGB = None  # 例 (True, False, False) 仅红灯反相\r
+\r
+# --- 2.2 按键 ExtInt ---\r
+EXTINT_AI_GPIO = "GPIO22"   # AI 键\r
+PWK_KEY_GPIO = "GPIO26"     # 电源键(短按/双击切模式等)\r
+\r
+# btn_device_ec800m:扩展按键(本机型未用)\r
+BTN_WECHAT_GPIO = None\r
+BTN_SOS_GPIO = None\r
+\r
+# --- 2.3 马达(motor.py / motorEventManager)---\r
+MOTOR_PIN_GPIO = "GPIO20"   # None 表示无马达\r
+MOTOR_TICK_MS = 100\r
+MOTOR_ACTIVE_LOW = False\r
+MOTOR_BOOT_VIBRATE_MS = 200  # 开机震动时长(ms)\r
+\r
+# --- 2.4 蓝牙电源 / 音频 IIS(本机型关闭)---\r
 BT_POWER_MOTOR_ENABLED = False\r
 PIN_BT_ON_GPIO = None\r
 PIN_MOT_GPIO = None\r
-AUDIO_QDAI_CFG = None\r
-\r
-EXTINT_AI_GPIO = "GPIO26"\r
-PWK_KEY_GPIO = "GPIO19"\r
+AUDIO_QDAI_CFG = None  # 非 None 时调用 audio.set_qdai_cfg(...)\r
 \r
+# --- 2.5 蓝牙↔4G 唤醒(本机型关闭)---\r
 BT_WAKEUP_4G_ENABLED = False\r
 EXTINT_WAKEUP_CTA1_GPIO = None\r
 PIN_WAKEUP_BT_GPIO = None\r
 \r
-# ---- 按键/防抖差异 ----\r
+# =============================================================================\r
+# 3. LED 灯效策略(AI_led / ai_VC,对齐交互规范)\r
+# =============================================================================\r
+\r
+# 正常待机 idle:off=灭灯(REQ-BAT-01);green_blink=旧工程绿灯慢闪\r
+LED_IDLE_MODE = "off"\r
+\r
+# green_blink 模式下的亮/灭时长(ms);LED_IDLE_MODE=off 时不生效\r
+LED_IDLE_GREEN_BLINK_MS = 1000\r
+LED_IDLE_GREEN_BLINK_OFF_MS = 2000\r
+\r
+# LLM 思考阶段:flicker_blue_green | open_red_green\r
+THINKING_LED_MODE = "flicker_blue_green"\r
+\r
+# 收到 vad.active 是否强制回蓝灯(长对话)\r
+VAD_ACTIVE_BLUE_LED = True\r
+\r
+# 收音 listening:长对话蓝慢闪(1000ms)、单对话蓝快闪(250ms) 由 VC_mode 在 AI_led 内切换,无需本文件单独配置\r
+\r
+# =============================================================================\r
+# 4. 电池与硬件状态(AI_hw_status / AI_dev)\r
+# =============================================================================\r
+\r
+HW_STATUS_PERIOD_MS = 10000\r
+SIGNAL_WEAK_CSQ = 10  # 0≤CSQ<此值 → signal=weak(REQ-ERR-02)\r
+\r
+# SoC 百分比门限(OCV 模式,对齐 REQ-BAT-02/03)\r
+BATTERY_LOW_SOC_PERCENT = 30\r
+BATTERY_EMPTY_SOC_PERCENT = 10\r
+BATTERY_LOW_WARN_INTERVAL_MS = 300000  # 低电语音间隔 5min\r
+BATTERY_EMPTY_FLASH_COUNT = 3\r
+BATTERY_EMPTY_FLASH_PERIOD_MS = 500    # 空电红快闪约 2 次/秒\r
+\r
+# 电压门限(mV):充满 / 低电 / 空电(与 get_battery、simple 模式共用)\r
+USE_SIMPLE_VBATT_THRESH = False  # True 时忽略 BATTERY_OCV_MAP,仅用三档电压\r
+VBATT_CHARGE_FULL_MV = 4290\r
+VBATT_LOW_MV = 3699\r
+VBATT_EMPTY_MV = 3629\r
+\r
+# 开路电压(mV)→SoC(%) 插值表;None 则用 ai_VC 内置默认表\r
+BATTERY_OCV_MAP = {\r
+    4290: 100, 4237: 95, 4179: 90, 4123: 85, 4070: 80, 4020: 75, 3963: 70, 3919: 65, 3882: 60, 3847: 55,\r
+    3818: 50, 3794: 45, 3772: 40, 3752: 35, 3733: 30, 3716: 25, 3699: 20, 3677: 15, 3652: 10, 3629: 5, 3400: 0,\r
+}\r
+\r
+# =============================================================================\r
+# 5. 可选外设与协议\r
+# =============================================================================\r
+\r
+GNSS_ENABLED = False           # 无 GNSS 硬件时 False,避免 import quecgnss\r
+UART_SUPPORTED = False\r
+BT_OTA_ENABLED = False\r
+UPLOAD_START_TONE_ENABLED = False  # 开始上传语音时是否播成功提示音\r
+\r
+# =============================================================================\r
+# 6. 按键逻辑(AI_dev / btn_device)\r
+# =============================================================================\r
+\r
 KEY_RELEASE_DEBOUNCE_COUNT = 5\r
-AI_LONG_PRESS_POWER_OFF_ENABLED = True\r
+AI_LONG_PRESS_POWER_OFF_ENABLED = False  # AI 键长按 8s 关机\r
+SOS_ENABLED = False                      # AI 长按 3s SOS、板载 SOS 入口\r
 \r
-# ---- 开机提示/语音参数默认值 ----\r
-POWER_ON_TONE_ENABLED = True\r
-POWER_ON_TONE_DEFAULT = "我来陪你聊天了"\r
-VAD_SILENCE_DEFAULT = 300\r
-VC_TTS_VOICE_TYPE_DEFAULT = None\r
-VC_TTS_VOICE_KEY_DEFAULT = None\r
+# =============================================================================\r
+# 7. VC 会话 / WebSocket / 空闲与休眠\r
+# =============================================================================\r
 \r
-# VC WebSocket 路径(ws://{AMP_SERVER}/{VC_WS_PATH}...)\r
+# 语音 WS 路径段(无首尾斜杠);完整 URL 见 default_srv.AMP_SERVER + 文首拼接说明\r
 VC_WS_PATH = "vc/agent"\r
+VC_WS_PING_INTERVAL_SEC = 55  # 文本 ping 间隔,过大影响保活,过小费电\r
+\r
+# 长对话:无有效业务下行累计达此秒数结束会话(代码最小 5)\r
+VC_IDLE_AUTO_CLOSE_SEC = 20\r
+VC_IDLE_AUTO_CLOSE_SEC_PTT = 0  # 单对话预留,当前与 idle 统一处理\r
+\r
+# 业务 idle 达此秒数触发深睡断开(最小 10;ENABLE=0 关闭)\r
+VC_IDLE_DEEP_SLEEP_SEC = 60\r
+VC_IDLE_DEEP_SLEEP_ENABLE = 1\r
+\r
+VAD_SILENCE_DEFAULT = 300  # VAD 静音阈值(ms)\r
+\r
+# =============================================================================\r
+# 8. 语音对话 / TTS / 智能体\r
+# =============================================================================\r
+\r
+POWER_ON_TONE_ENABLED = True\r
+POWER_ON_TONE_DEFAULT = "你好,我来了"\r
+\r
+VC_TTS_VOICE_TYPE_DEFAULT = None   # dict 或 None\r
+VC_TTS_VOICE_KEY_DEFAULT = None    # 如 "男" / "童"\r
+\r
+DIALOG_BOT_OPTION = None           # {"bot_id", "workflow_id", "use_workflow"}\r
+VC_BOT_ID = "7462617881076351002"\r
+VC_WORKFLOW_ID = "7462621757327196187"\r
+DIALOG_PROVIDER_DEFAULT = None\r
+KEYWORDS_FROM_VOICE_TYPE = False\r
+\r
+BT_VOICE_FEATURES_ENABLED = False\r
+\r
+# =============================================================================\r
+# 9. 本地指令与固定话术\r
+# =============================================================================\r
+\r
+# protocol.ai_cmd_handle:云端 NLP 扩展(会议纪要 / 实时翻译 / 历史留言)\r
+NLP_EXTENDED_CMDS = False\r
 \r
-# 是否支持蓝牙 OTA\r
-BT_OTA_ENABLED = False\r
-UPLOAD_START_TONE_ENABLED = True\r
-THINKING_LED_MODE = "open_red_green"\r
-VAD_ACTIVE_BLUE_LED = False\r
 CMD_TOGGLE_MODE_TO_PTT_TTS = "已切换到单对话模式"\r
 CMD_TOGGLE_MODE_TO_LONG_TTS = "已切换到长对话模式"\r
-BT_VOICE_FEATURES_ENABLED = False\r
-POWEROFF_CMD_CLOSE_ONLY = False\r
-KWS_PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "\r
-DIALOG_BOT_OPTION = None\r
-DIALOG_PROVIDER_DEFAULT = None\r
-KEYWORDS_FROM_VOICE_TYPE = True\r
+POWEROFF_CMD_CLOSE_ONLY = False     # 「关机」指令仅 Close,不真断电\r
 \r
-# ---- 按键音/提示音策略 ----\r
-KEY_PRESS_TONE_FILE = None\r
-KEY_PRESS_TONE_DELAY_MS = 0\r
+KWS_PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "\r
 PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "\r
+\r
+# =============================================================================\r
+# 10. 按键音 / 开录音 / PTT 时序(k8:开录仅以灯提示,不播专用开录音)\r
+# =============================================================================\r
+\r
+KEY_PRESS_TONE_FILE = None         # AI 短按操作音;None=不播\r
+KEY_PRESS_TONE_DELAY_MS = 300\r
+\r
+RECORD_START_TONE_FILE = None      # 单对话长按开录提示音;None=仅蓝快闪\r
+RECORD_START_TONE_GAP_MS = 180\r
+\r
+PTT_RELEASE_END_SPEECH_DELAY_MS = 200   # 松手后发 audio.completed 前延迟\r
+PTT_CANCEL_WAIT_MS = 2500               # 打断后等 canceled 最长时间\r
+PTT_POST_MUTE_MS = 30                   # 本地硬静音后 codec 收尾\r
+\r
+# =============================================================================\r
+# 11. 调试开关(量产可全 False)\r
+# =============================================================================\r
+\r
+LED_DEBUG = True\r
+LED_STATE_DEBUG = True\r
+KEY_LED_DEBUG = True\r
+HW_STATUS_DEBUG = True
\ No newline at end of file
diff --git a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/configs/k8/default_config.py b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/configs/k8/default_config.py
new file mode 100644 (file)
index 0000000..d26ea6f
--- /dev/null
@@ -0,0 +1,193 @@
+# -*- coding: utf-8 -*-
+# k8 产品 — 设备侧默认配置(编译期 freeze 进固件)
+#
+# 加载优先级(vc_config.py):
+#   1) usr/default_config.py(现场覆盖)
+#   2) 本文件(由 new_clean.bat 按 product_k8.ini 的 DEV_TYPE 拷贝到 core/default_config.py)
+#
+# 约定:仅放「机型/产品差异」常量,勿放运行态变量。
+
+# =============================================================================
+# 1. 产品与区域
+# =============================================================================
+
+# 硬件平台类型(与 products/configs/<DEV_TYPE>/ 目录名一致)
+DEV_TYPE = "7PIN"
+
+# 文案/逻辑用语言(如 zh / en)
+LANG = "zh"
+
+# 设备鉴权(device_res_author:HMAC 签名与 AES 加解密)产品名“小量博士” 、“浪漫挂坠”
+PRODUCT_PID = "cqxJZi5CNgzjS3mX21Zw"
+PRODUCT_KEY = "uKLrChwrxA1LklgBc3VwawYjUkbT3BgX"  # 32 字节,运行时转为 bytes
+
+# =============================================================================
+# 2. GPIO 引脚
+# =============================================================================
+
+# --- 2.1 三色 LED(Pin 属性名字符串,vc_config 转为 Pin.GPIOxx)---
+LED_GPIO = ("GPIO36", "GPIO44", "GPIO43")  # (R, G, B)
+
+# 点亮电平:False=高电平亮;True=低电平亮;仅某路反相用 LED_ACTIVE_LOW_RGB
+LED_ACTIVE_LOW = False
+LED_ACTIVE_LOW_RGB = None  # 例 (True, False, False) 仅红灯反相
+
+# --- 2.2 按键 ExtInt ---
+EXTINT_AI_GPIO = "GPIO22"   # AI 键
+PWK_KEY_GPIO = "GPIO26"     # 电源键(短按/双击切模式等)
+
+# btn_device_ec800m:扩展按键(本机型未用)
+BTN_WECHAT_GPIO = None
+BTN_SOS_GPIO = None
+
+# --- 2.3 马达(motor.py / motorEventManager)---
+MOTOR_PIN_GPIO = "GPIO20"   # None 表示无马达
+MOTOR_TICK_MS = 100
+MOTOR_ACTIVE_LOW = False
+MOTOR_BOOT_VIBRATE_MS = 200  # 开机震动时长(ms)
+
+# --- 2.4 蓝牙电源 / 音频 IIS(本机型关闭)---
+BT_POWER_MOTOR_ENABLED = False
+PIN_BT_ON_GPIO = None
+PIN_MOT_GPIO = None
+AUDIO_QDAI_CFG = None  # 非 None 时调用 audio.set_qdai_cfg(...)
+
+# --- 2.5 蓝牙↔4G 唤醒(本机型关闭)---
+BT_WAKEUP_4G_ENABLED = False
+EXTINT_WAKEUP_CTA1_GPIO = None
+PIN_WAKEUP_BT_GPIO = None
+
+# =============================================================================
+# 3. LED 灯效策略(AI_led / ai_VC,对齐交互规范)
+# =============================================================================
+
+# 正常待机 idle:off=灭灯(REQ-BAT-01);green_blink=旧工程绿灯慢闪
+LED_IDLE_MODE = "off"
+
+# green_blink 模式下的亮/灭时长(ms);LED_IDLE_MODE=off 时不生效
+LED_IDLE_GREEN_BLINK_MS = 1000
+LED_IDLE_GREEN_BLINK_OFF_MS = 2000
+
+# LLM 思考阶段:flicker_blue_green | open_red_green
+THINKING_LED_MODE = "flicker_blue_green"
+
+# 收到 vad.active 是否强制回蓝灯(长对话)
+VAD_ACTIVE_BLUE_LED = True
+
+# 收音 listening:长对话蓝慢闪(1000ms)、单对话蓝快闪(250ms) 由 VC_mode 在 AI_led 内切换,无需本文件单独配置
+
+# =============================================================================
+# 4. 电池与硬件状态(AI_hw_status / AI_dev)
+# =============================================================================
+
+HW_STATUS_PERIOD_MS = 10000
+SIGNAL_WEAK_CSQ = 10  # 0≤CSQ<此值 → signal=weak(REQ-ERR-02)
+
+# SoC 百分比门限(OCV 模式,对齐 REQ-BAT-02/03)
+BATTERY_LOW_SOC_PERCENT = 30
+BATTERY_EMPTY_SOC_PERCENT = 10
+BATTERY_LOW_WARN_INTERVAL_MS = 300000  # 低电语音间隔 5min
+BATTERY_EMPTY_FLASH_COUNT = 3
+BATTERY_EMPTY_FLASH_PERIOD_MS = 500    # 空电红快闪约 2 次/秒
+
+# 电压门限(mV):充满 / 低电 / 空电(与 get_battery、simple 模式共用)
+USE_SIMPLE_VBATT_THRESH = False  # True 时忽略 BATTERY_OCV_MAP,仅用三档电压
+VBATT_CHARGE_FULL_MV = 4290
+VBATT_LOW_MV = 3699
+VBATT_EMPTY_MV = 3629
+
+# 开路电压(mV)→SoC(%) 插值表;None 则用 ai_VC 内置默认表
+BATTERY_OCV_MAP = {
+    4290: 100, 4237: 95, 4179: 90, 4123: 85, 4070: 80, 4020: 75, 3963: 70, 3919: 65, 3882: 60, 3847: 55,
+    3818: 50, 3794: 45, 3772: 40, 3752: 35, 3733: 30, 3716: 25, 3699: 20, 3677: 15, 3652: 10, 3629: 5, 3400: 0,
+}
+
+# =============================================================================
+# 5. 可选外设与协议
+# =============================================================================
+
+GNSS_ENABLED = False           # 无 GNSS 硬件时 False,避免 import quecgnss
+UART_SUPPORTED = False
+BT_OTA_ENABLED = False
+UPLOAD_START_TONE_ENABLED = False  # 开始上传语音时是否播成功提示音
+
+# =============================================================================
+# 6. 按键逻辑(AI_dev / btn_device)
+# =============================================================================
+
+KEY_RELEASE_DEBOUNCE_COUNT = 5
+AI_LONG_PRESS_POWER_OFF_ENABLED = False  # AI 键长按 8s 关机
+SOS_ENABLED = False                      # AI 长按 3s SOS、板载 SOS 入口
+
+# =============================================================================
+# 7. VC 会话 / WebSocket / 空闲与休眠
+# =============================================================================
+
+# 语音 WS 路径段(无首尾斜杠);完整 URL 见 default_srv.AMP_SERVER + 文首拼接说明
+VC_WS_PATH = "vc/agent"
+VC_WS_PING_INTERVAL_SEC = 55  # 文本 ping 间隔,过大影响保活,过小费电
+
+# 长对话:无有效业务下行累计达此秒数结束会话(代码最小 5)
+VC_IDLE_AUTO_CLOSE_SEC = 20
+VC_IDLE_AUTO_CLOSE_SEC_PTT = 0  # 单对话预留,当前与 idle 统一处理
+
+# 业务 idle 达此秒数触发深睡断开(最小 10;ENABLE=0 关闭)
+VC_IDLE_DEEP_SLEEP_SEC = 60
+VC_IDLE_DEEP_SLEEP_ENABLE = 1
+
+VAD_SILENCE_DEFAULT = 300  # VAD 静音阈值(ms)
+
+# =============================================================================
+# 8. 语音对话 / TTS / 智能体
+# =============================================================================
+
+POWER_ON_TONE_ENABLED = True
+POWER_ON_TONE_DEFAULT = "你好,我来了"
+
+VC_TTS_VOICE_TYPE_DEFAULT = None   # dict 或 None
+VC_TTS_VOICE_KEY_DEFAULT = None    # 如 "男" / "童"
+
+DIALOG_BOT_OPTION = None           # {"bot_id", "workflow_id", "use_workflow"}
+VC_BOT_ID = "7462617881076351002"
+VC_WORKFLOW_ID = "7462621757327196187"
+DIALOG_PROVIDER_DEFAULT = None
+KEYWORDS_FROM_VOICE_TYPE = False
+
+BT_VOICE_FEATURES_ENABLED = False
+
+# =============================================================================
+# 9. 本地指令与固定话术
+# =============================================================================
+
+# protocol.ai_cmd_handle:云端 NLP 扩展(会议纪要 / 实时翻译 / 历史留言)
+NLP_EXTENDED_CMDS = False
+
+CMD_TOGGLE_MODE_TO_PTT_TTS = "已切换到单对话模式"
+CMD_TOGGLE_MODE_TO_LONG_TTS = "已切换到长对话模式"
+POWEROFF_CMD_CLOSE_ONLY = False     # 「关机」指令仅 Close,不真断电
+
+KWS_PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "
+PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "
+
+# =============================================================================
+# 10. 按键音 / 开录音 / PTT 时序(k8:开录仅以灯提示,不播专用开录音)
+# =============================================================================
+
+KEY_PRESS_TONE_FILE = None         # AI 短按操作音;None=不播
+KEY_PRESS_TONE_DELAY_MS = 300
+
+RECORD_START_TONE_FILE = None      # 单对话长按开录提示音;None=仅蓝快闪
+RECORD_START_TONE_GAP_MS = 180
+
+PTT_RELEASE_END_SPEECH_DELAY_MS = 200   # 松手后发 audio.completed 前延迟
+PTT_CANCEL_WAIT_MS = 2500               # 打断后等 canceled 最长时间
+PTT_POST_MUTE_MS = 30                   # 本地硬静音后 codec 收尾
+
+# =============================================================================
+# 11. 调试开关(量产可全 False)
+# =============================================================================
+
+LED_DEBUG = True
+LED_STATE_DEBUG = True
+KEY_LED_DEBUG = True
+HW_STATUS_DEBUG = True
\ No newline at end of file
diff --git a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/configs/k8s/default_config.py b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/configs/k8s/default_config.py
new file mode 100644 (file)
index 0000000..682df73
--- /dev/null
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+# k8s 产品 — 仅电源键(无独立 AI 键),AI 短按/长按/PTT 由电源键承担(见 SINGLE_POWER_KEY_AI_MERGED)
+#
+# 加载优先级(vc_config.py):
+#   1) usr/default_config.py(现场覆盖)
+#   2) 本文件(由 new_clean.bat 按 product_k8s.ini 的 DEV_TYPE 拷贝到 core/default_config.py)
+#
+# 约定:仅放「机型/产品差异」常量,勿放运行态变量。
+
+# =============================================================================
+# 1. 产品与区域
+# =============================================================================
+
+# 硬件平台类型(与 products/configs/<DEV_TYPE>/ 目录名一致)
+DEV_TYPE = "7PIN"
+
+# 文案/逻辑用语言(如 zh / en)
+LANG = "zh"
+
+# 设备鉴权(device_res_author:HMAC 签名与 AES 加解密)  产品名“小量博士”、产品名“浪漫挂坠”
+PRODUCT_PID = "cqxJZi5CNgzjS3mX21Zw"
+PRODUCT_KEY = "uKLrChwrxA1LklgBc3VwawYjUkbT3BgX"  # 32 字节,运行时转为 bytes
+
+# =============================================================================
+# 2. GPIO 引脚
+# =============================================================================
+
+# --- 2.1 三色 LED(Pin 属性名字符串,vc_config 转为 Pin.GPIOxx)---
+LED_GPIO = ("GPIO36", "GPIO44", "GPIO43")  # (R, G, B)
+
+# 点亮电平:False=高电平亮;True=低电平亮;仅某路反相用 LED_ACTIVE_LOW_RGB
+LED_ACTIVE_LOW = False
+LED_ACTIVE_LOW_RGB = None  # 例 (True, False, False) 仅红灯反相
+
+# --- 2.2 按键 ExtInt(k8s:无 AI 物理键,EXTINT_AI_GPIO 置 None)---
+EXTINT_AI_GPIO = None       # 无独立 AI 键;对话由 PWK 承担
+PWK_KEY_GPIO = "GPIO26"     # 电源键:单击/长按=原 AI 键;双击仍切长/单对话;长按关机见 PWK_LONG_PRESS_SHUTDOWN_MS
+
+# btn_device_ec800m:扩展按键(本机型未用)
+BTN_WECHAT_GPIO = None
+BTN_SOS_GPIO = None
+
+# --- 2.3 马达(motor.py / motorEventManager)---
+MOTOR_PIN_GPIO = "GPIO20"   # None 表示无马达
+MOTOR_TICK_MS = 100
+MOTOR_ACTIVE_LOW = False
+MOTOR_BOOT_VIBRATE_MS = 200  # 开机震动时长(ms)
+
+# --- 2.4 蓝牙电源 / 音频 IIS(本机型关闭)---
+BT_POWER_MOTOR_ENABLED = False
+PIN_BT_ON_GPIO = None
+PIN_MOT_GPIO = None
+AUDIO_QDAI_CFG = None  # 非 None 时调用 audio.set_qdai_cfg(...)
+
+# --- 2.5 蓝牙↔4G 唤醒(本机型关闭)---
+BT_WAKEUP_4G_ENABLED = False
+EXTINT_WAKEUP_CTA1_GPIO = None
+PIN_WAKEUP_BT_GPIO = None
+
+# =============================================================================
+# 3. LED 灯效策略(AI_led / ai_VC,对齐交互规范)
+# =============================================================================
+
+# 正常待机 idle:off=灭灯(REQ-BAT-01);green_blink=旧工程绿灯慢闪
+LED_IDLE_MODE = "off"
+
+# green_blink 模式下的亮/灭时长(ms);LED_IDLE_MODE=off 时不生效
+LED_IDLE_GREEN_BLINK_MS = 1000
+LED_IDLE_GREEN_BLINK_OFF_MS = 2000
+
+# LLM 思考阶段:flicker_blue_green | open_red_green
+THINKING_LED_MODE = "flicker_blue_green"
+
+# 收到 vad.active 是否强制回蓝灯(长对话)
+VAD_ACTIVE_BLUE_LED = True
+
+# 收音 listening:长对话蓝慢闪(1000ms)、单对话蓝快闪(250ms) 由 VC_mode 在 AI_led 内切换,无需本文件单独配置
+
+# =============================================================================
+# 4. 电池与硬件状态(AI_hw_status / AI_dev)
+# =============================================================================
+
+HW_STATUS_PERIOD_MS = 10000
+SIGNAL_WEAK_CSQ = 10  # 0≤CSQ<此值 → signal=weak(REQ-ERR-02)
+
+# SoC 百分比门限(OCV 模式,对齐 REQ-BAT-02/03)
+BATTERY_LOW_SOC_PERCENT = 30
+BATTERY_EMPTY_SOC_PERCENT = 10
+BATTERY_LOW_WARN_INTERVAL_MS = 300000  # 低电语音间隔 5min
+BATTERY_EMPTY_FLASH_COUNT = 3
+BATTERY_EMPTY_FLASH_PERIOD_MS = 500    # 空电红快闪约 2 次/秒
+
+# 电压门限(mV):充满 / 低电 / 空电(与 get_battery、simple 模式共用)
+USE_SIMPLE_VBATT_THRESH = False  # True 时忽略 BATTERY_OCV_MAP,仅用三档电压
+VBATT_CHARGE_FULL_MV = 4290
+VBATT_LOW_MV = 3699
+VBATT_EMPTY_MV = 3629
+
+# 开路电压(mV)→SoC(%) 插值表;None 则用 ai_VC 内置默认表
+BATTERY_OCV_MAP = {
+    4290: 100, 4237: 95, 4179: 90, 4123: 85, 4070: 80, 4020: 75, 3963: 70, 3919: 65, 3882: 60, 3847: 55,
+    3818: 50, 3794: 45, 3772: 40, 3752: 35, 3733: 30, 3716: 25, 3699: 20, 3677: 15, 3652: 10, 3629: 5, 3400: 0,
+}
+
+# =============================================================================
+# 5. 可选外设与协议
+# =============================================================================
+
+GNSS_ENABLED = False           # 无 GNSS 硬件时 False,避免 import quecgnss
+UART_SUPPORTED = False
+BT_OTA_ENABLED = False
+UPLOAD_START_TONE_ENABLED = False  # 开始上传语音时是否播成功提示音
+
+# =============================================================================
+# 6. 按键逻辑(AI_dev / btn_device)
+# =============================================================================
+
+KEY_RELEASE_DEBOUNCE_COUNT = 5
+
+# 单电源键:电源短按不再 Close 对话,按下/松开走 AI_key_press / AI_key_release(与 ai_VC.AI_dev 一致)
+SINGLE_POWER_KEY_AI_MERGED = True
+
+# 仅按住电源键达此毫秒后关机(pwk_key_timer 100ms 节拍);双键机型默认 3000,k8s 为 18s
+PWK_LONG_PRESS_SHUTDOWN_MS = 18000
+
+AI_LONG_PRESS_POWER_OFF_ENABLED = False  # 无独立 AI 键时无效;关机仅走电源长按
+SOS_ENABLED = False                      # 无 AI 键 SOS 入口
+
+# =============================================================================
+# 7. VC 会话 / WebSocket / 空闲与休眠
+# =============================================================================
+
+# 语音 WS 路径段(无首尾斜杠);完整 URL 见 default_srv.AMP_SERVER + 文首拼接说明
+VC_WS_PATH = "vc/agent"
+VC_WS_PING_INTERVAL_SEC = 55  # 文本 ping 间隔,过大影响保活,过小费电
+
+# 长对话:无有效业务下行累计达此秒数结束会话(代码最小 5)
+VC_IDLE_AUTO_CLOSE_SEC = 20
+VC_IDLE_AUTO_CLOSE_SEC_PTT = 0  # 单对话预留,当前与 idle 统一处理
+
+# 业务 idle 达此秒数触发深睡断开(最小 10;ENABLE=0 关闭)
+VC_IDLE_DEEP_SLEEP_SEC = 60
+VC_IDLE_DEEP_SLEEP_ENABLE = 1
+
+VAD_SILENCE_DEFAULT = 300  # VAD 静音阈值(ms)
+
+# =============================================================================
+# 8. 语音对话 / TTS / 智能体
+# =============================================================================
+
+POWER_ON_TONE_ENABLED = True
+POWER_ON_TONE_DEFAULT = "你好,我来了"
+
+VC_TTS_VOICE_TYPE_DEFAULT = None   # dict 或 None
+VC_TTS_VOICE_KEY_DEFAULT = None    # 如 "男" / "童"
+
+DIALOG_BOT_OPTION = None           # {"bot_id", "workflow_id", "use_workflow"}
+VC_BOT_ID = "7462617881076351002"
+VC_WORKFLOW_ID = "7462621757327196187"
+DIALOG_PROVIDER_DEFAULT = None
+KEYWORDS_FROM_VOICE_TYPE = False
+
+BT_VOICE_FEATURES_ENABLED = False
+
+# =============================================================================
+# 9. 本地指令与固定话术
+# =============================================================================
+
+# protocol.ai_cmd_handle:云端 NLP 扩展(会议纪要 / 实时翻译 / 历史留言)
+NLP_EXTENDED_CMDS = False
+
+CMD_TOGGLE_MODE_TO_PTT_TTS = "已切换到单对话模式"
+CMD_TOGGLE_MODE_TO_LONG_TTS = "已切换到长对话模式"
+POWEROFF_CMD_CLOSE_ONLY = False     # 「关机」指令仅 Close,不真断电
+
+KWS_PTT_HINT = "请您按住电源键开始讲话,讲完松开即可 "
+PTT_HINT = "请您按住电源键开始讲话,讲完松开即可 "
+
+# =============================================================================
+# 10. 按键音 / 开录音 / PTT 时序(k8s:开录仅以灯提示,不播专用开录音)
+# =============================================================================
+
+KEY_PRESS_TONE_FILE = None         # AI 短按操作音;None=不播
+KEY_PRESS_TONE_DELAY_MS = 300
+
+RECORD_START_TONE_FILE = None      # 单对话长按开录提示音;None=仅蓝快闪
+RECORD_START_TONE_GAP_MS = 180
+
+PTT_RELEASE_END_SPEECH_DELAY_MS = 200   # 松手后发 audio.completed 前延迟
+PTT_CANCEL_WAIT_MS = 2500               # 打断后等 canceled 最长时间
+PTT_POST_MUTE_MS = 30                   # 本地硬静音后 codec 收尾
+
+# =============================================================================
+# 11. 调试开关(量产可全 False)
+# =============================================================================
+
+LED_DEBUG = True
+LED_STATE_DEBUG = True
+KEY_LED_DEBUG = True
+HW_STATUS_DEBUG = True
diff --git a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/product_k8_jswm.ini b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/product_k8_jswm.ini
new file mode 100644 (file)
index 0000000..75436bf
--- /dev/null
@@ -0,0 +1,3 @@
+DEV_TYPE=jswm
+SRV_TYPE=beike_dev
+VERSION=V1.2
diff --git a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/product_k8s.ini b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/product_k8s.ini
new file mode 100644 (file)
index 0000000..fee76c5
--- /dev/null
@@ -0,0 +1,3 @@
+DEV_TYPE=k8s
+SRV_TYPE=beike_dev
+VERSION=V1.2
index f784ef6..7915225 100644 (file)
@@ -1,38 +1,23 @@
-\r
-\r
-# 设备侧 Server 配置(freeze 内置)。\r
-#\r
-# 注意:\r
-# - `constants.py` 中固定不变的常量(事件名/枚举)继续保留在 constants.py。\r
-# - 本文件统一承载“协议/服务器相关的可配置项”,并支持 usr 覆盖。\r
-\r
-# ---- 环境选择(服务器地址由 srv_configs/<SERVER_ENV>.py 承载)----\r
-# 服务器环境配置,切换只需修改 SERVER_ENV\r
-# "beike" = 北科测试, "kxc" = 科学城demo\r
-SERVER_ENV = "beike"\r
-\r
-# ---- 默认环境地址(即使没有 srv_configs/<env>.py 也能工作)----\r
-# 这里默认给出 beike_dev 的地址;若要切换环境,建议:\r
-# - 修改 SERVER_ENV\r
-# - 同时提供对应的 srv_configs/<SERVER_ENV>.py(或 usr 覆盖)\r
-# SERVER_ENV = beike(北科测试)\r
-\r
-DMP_BASE = "http://dmp.raisound.com/bz-api"\r
-AMP_SERVER = "115.190.94.208:22080"\r
-IOT_WS_URL = "ws://wsiot-nocdn.raisound.com/ws-hub/dev/v1"\r
-\r
-# ---- DMP/认证/时间同步等(默认使用 constants.DMP_BASE 拼接)----\r
-DMP_TIME_PATH = "/dev/time"\r
-DMP_ACTIVE_PATH = "/dev/auth/active"\r
-DMP_SIGN_IN_PATH = "/dev/auth/sign_in"\r
-DMP_TOKEN_PATH = "/dev/auth/token"\r
-DMP_ICCID_PATH = "/dev/iccid"\r
-DMP_DEV_CONFIG_PATH_FMT = "/dev/config/{}"  # deviceId\r
-\r
-# ---- OTA(现网有的走 DMP_BASE,有的走 qipai 域名;先集中成可配项)----\r
-OTA_TASK_URL_FMT = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}"\r
-OTA_RESULT_PATH_FMT = "/ota/result?deviceId={}&currentVersion={}"\r
-\r
-# ---- AMP/健康检查等 ----\r
-AMP_HEALTH_URL_FMT = "http://{}/health?task_id={}"\r
-\r
+# 北科测试环境 beike — Server 配置模板(编译时 SRV_TYPE=beike 拷贝为 core/default_srv.py)
+# URL 拼接说明见 beike_dev/default_srv.py 文首;本环境示例:
+#   VC: ws://115.190.94.208:22080/{VC_WS_PATH}?...
+#   IoT: ws://wsiot-nocdn.raisound.com/ws-hub/dev/v1?bz-dt=...
+#   DMP: http://dmp.raisound.com/bz-api/dev/...
+
+SERVER_ENV = "beike"
+
+DMP_BASE = "http://dmp.raisound.com/bz-api"
+AMP_SERVER = "115.190.94.208:22080"
+IOT_WS_URL = "ws://wsiot-nocdn.raisound.com/ws-hub/dev/v1"
+
+DMP_TIME_PATH = "/dev/time"
+DMP_ACTIVE_PATH = "/dev/auth/active"
+DMP_SIGN_IN_PATH = "/dev/auth/sign_in"
+DMP_TOKEN_PATH = "/dev/auth/token"
+DMP_ICCID_PATH = "/dev/iccid"
+DMP_DEV_CONFIG_PATH_FMT = "/dev/config/{}"
+
+OTA_TASK_URL_FMT = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}"
+OTA_RESULT_PATH_FMT = "/ota/result?deviceId={}&currentVersion={}"
+
+AMP_HEALTH_URL_FMT = "http://{}/health?task_id={}"
index 5ae3122..c333e1a 100644 (file)
@@ -1,34 +1,99 @@
-# 设备侧 Server 配置(freeze 内置)。\r
+# 设备侧 Server 配置(编译期由 product_*.ini 的 SRV_TYPE 拷贝为 core/default_srv.py 并 freeze)。\r
 #\r
-# 注意:\r
-# - `constants.py` 中固定不变的常量(事件名/枚举)继续保留在 constants.py。\r
-# - 本文件统一承载“协议/服务器相关的可配置项”,并支持 usr 覆盖。\r
+# =============================================================================\r
+# 加载与修改方式(改环境时按此检查,避免漏改)\r
+# =============================================================================\r
+#\r
+# 编译:\r
+#   product_k8.ini 中 SRV_TYPE=beike_dev\r
+#     → new_clean.bat 拷贝本文件 → core/default_srv.py → 打进固件\r
+#\r
+# 运行时优先级(srv_config.py):\r
+#   1) usr/default_srv.py(现场覆盖,无需重编)\r
+#   2) freeze 内置 core/default_srv.py\r
+#\r
+# 启动时注入 constants(constants.py 仅读取下列 3 项到模块常量):\r
+#   DMP_BASE, AMP_SERVER, IOT_WS_URL  →  constants.DMP_BASE / AMP_SERVER / IOT_WS_URL\r
+#\r
+# 与 default_config.py 的分工(勿混在同一文件改):\r
+#   - 本文件:环境 / 域名 / 主机(随 SRV_TYPE 变,如 beike_dev ↔ beike)\r
+#   - default_config:产品能力(随 DEV_TYPE/k8 变),含 VC_WS_PATH、VC_BOT_ID 等\r
+#\r
+# =============================================================================\r
+# URL 拼接总览(改 VC 联调时两项都要对)\r
+# =============================================================================\r
+#\r
+# 【语音对话 WebSocket — AI_VC.websocket_connect_VC】\r
+#   主机:constants.AMP_SERVER(本文件 AMP_SERVER)\r
+#         → ai_VC 赋给 self.server_ip(可被存储 server_ip / 云端下发覆盖)\r
+#   路径:default_config.VC_WS_PATH(vc_config,如 k8 为 "vc/agent")\r
+#   查询:is_auth、设备 token(get_dev_token)\r
+#\r
+#   拼接结果:\r
+#     req_str = "{VC_WS_PATH}?is_auth={is_auth}"\r
+#     有 token:  ws://{AMP_SERVER}/{req_str}&dt={token}\r
+#     无 token:  ws://{AMP_SERVER}/{req_str}\r
+#\r
+#   示例(beike_dev + k8):\r
+#     ws://118.145.158.207:22080/vc/agent?is_auth=...&dt=...\r
+#\r
+# 【IoT 设备云 WebSocket — protocol.py】\r
+#   完整 URL 已在本文件写死为 IOT_WS_URL,仅追加 token:\r
+#     {IOT_WS_URL}?bz-dt={token}\r
+#   示例:ws://dmp-dev.raisound.com/iot-api/ws-hub/dev/v1?bz-dt=...\r
+#\r
+# 【DMP HTTP — ai_VC 内认证/校时/ICCID/OTA 结果等】\r
+#   前缀:constants.DMP_BASE(本文件 DMP_BASE,无尾部斜杠)\r
+#   路径:ai_VC 内目前为字面量 "/dev/..."(与下方 DMP_*_PATH 取值应对齐)\r
+#   示例:{DMP_BASE}/dev/time 、{DMP_BASE}/dev/auth/active\r
+#\r
+# 【OTA 检查任务 — ai_VC OTA 查询】\r
+#   当前 ai_VC 使用完整 URL 字面量(应与 OTA_TASK_URL_FMT 一致):\r
+#     OTA_TASK_URL_FMT.format(imei, sourceVersion)\r
+#   OTA 结果上报:{DMP_BASE}/ota/result?deviceId=...(路径与 OTA_RESULT_PATH_FMT 应对齐)\r
+#\r
+# 【AMP 健康检查 — ai_VC 错误上报】\r
+#   使用 self.server_ip(同 AMP_SERVER)+ 硬编码路径:\r
+#     http://{server_ip}/health?task_id={...}\r
+#   与 AMP_HEALTH_URL_FMT 形式一致(第一参数为 host:port,无 http 前缀)\r
+#\r
+# 注意:下方 DMP_*_PATH / OTA_*_FMT / AMP_HEALTH_URL_FMT 为**集中文档与目标值**;\r
+# constants 启动时尚未自动读取这些路径常量;修改后若 ai_VC 仍写死字符串,需同步改代码或接 srv_config.get_attr。\r
+#\r
+# =============================================================================\r
 \r
-# ---- 环境选择(服务器地址由 srv_configs/<SERVER_ENV>.py 承载)----\r
-# 服务器环境配置,切换只需修改 SERVER_ENV\r
-# "beike" = 北科测试, "kxc" = 科学城demo\r
+# ---- 环境标识(仅注释/运维用;constants.SERVER_ENV 会读取)----\r
 SERVER_ENV = "beike_dev"\r
 \r
-# ---- 默认环境地址(即使没有 srv_configs/<env>.py 也能工作)----\r
-# 这里默认给出 beike_dev 的地址;若要切换环境,建议:\r
-# - 修改 SERVER_ENV\r
-# - 同时提供对应的 srv_configs/<SERVER_ENV>.py(或 usr 覆盖)\r
+# ---- 三套「根地址」(constants.py 启动时加载,改环境必改)----\r
+\r
+# DMP 业务 HTTP 根路径(无尾斜杠)。用于 DMP_BASE + "/dev/..." 、"/ota/result?..."\r
 DMP_BASE = "http://dmp-dev.raisound.com/dmp-api"\r
+\r
+# 语音对话 AMP 主机 host:port(无 ws://)。用于 ws://{AMP_SERVER}/{VC_WS_PATH}...\r
+# 须与 default_config.VC_WS_PATH 联调;勿漏改 VC_WS_PATH(在 configs/k8/default_config.py)\r
 AMP_SERVER = "118.145.158.207:22080"\r
+\r
+# IoT 长连接完整 WebSocket URL(含 ws://)。protocol 仅追加 ?bz-dt=token\r
 IOT_WS_URL = "ws://dmp-dev.raisound.com/iot-api/ws-hub/dev/v1"\r
 \r
-# ---- DMP/认证/时间同步等(默认使用 constants.DMP_BASE 拼接)----\r
-DMP_TIME_PATH = "/dev/time"\r
-DMP_ACTIVE_PATH = "/dev/auth/active"\r
-DMP_SIGN_IN_PATH = "/dev/auth/sign_in"\r
-DMP_TOKEN_PATH = "/dev/auth/token"\r
-DMP_ICCID_PATH = "/dev/iccid"\r
-DMP_DEV_CONFIG_PATH_FMT = "/dev/config/{}"  # deviceId\r
+# ---- DMP 子路径(相对 DMP_BASE;与 ai_VC 内硬编码路径保持一致)----\r
+\r
+DMP_TIME_PATH = "/dev/time"                    # 校时 GET  → dev_NTP\r
+DMP_ACTIVE_PATH = "/dev/auth/active"           # 激活 POST\r
+DMP_SIGN_IN_PATH = "/dev/auth/sign_in"        # 登录\r
+DMP_TOKEN_PATH = "/dev/auth/token"             # 取 token → get_dev_token 链路\r
+DMP_ICCID_PATH = "/dev/iccid"                 # ICCID 上报(带 query)\r
+DMP_DEV_CONFIG_PATH_FMT = "/dev/config/{}"     # 设备配置,format(deviceId)\r
 \r
-# ---- OTA(现网有的走 DMP_BASE,有的走 qipai 域名;先集中成可配项)----\r
+# ---- OTA ----\r
+\r
+# 完整 URL 模板;占位符 {} 依次为 deviceId(IMEI)、sourceVersion\r
 OTA_TASK_URL_FMT = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}"\r
+\r
+# 相对 DMP_BASE 的路径模板(上报 OTA 结果)\r
 OTA_RESULT_PATH_FMT = "/ota/result?deviceId={}&currentVersion={}"\r
 \r
-# ---- AMP/健康检查等 ----\r
-AMP_HEALTH_URL_FMT = "http://{}/health?task_id={}"\r
+# ---- AMP 健康检查(host 用 AMP_SERVER / server_ip,非 DMP_BASE)----\r
 \r
+AMP_HEALTH_URL_FMT = "http://{}/health?task_id={}"  # .format(server_ip, task_id)\r
index 6d952ab..ae44bab 100644 (file)
@@ -1,37 +1,23 @@
-\r
-\r
-# 设备侧 Server 配置(freeze 内置)。\r
-#\r
-# 注意:\r
-# - `constants.py` 中固定不变的常量(事件名/枚举)继续保留在 constants.py。\r
-# - 本文件统一承载“协议/服务器相关的可配置项”,并支持 usr 覆盖。\r
-\r
-# ---- 环境选择(服务器地址由 srv_configs/<SERVER_ENV>.py 承载)----\r
-# 服务器环境配置,切换只需修改 SERVER_ENV\r
-# "beike" = 北科测试, "kxc" = 科学城demo\r
-SERVER_ENV = "kxc"\r
-\r
-# ---- 默认环境地址(即使没有 srv_configs/<env>.py 也能工作)----\r
-# 这里默认给出 beike_dev 的地址;若要切换环境,建议:\r
-# - 修改 SERVER_ENV\r
-# - 同时提供对应的 srv_configs/<SERVER_ENV>.py(或 usr 覆盖)\r
-# SERVER_ENV = kxc(科学城 demo)\r
-DMP_BASE = "http://kxc.raisound.com/bz-api"\r
-AMP_SERVER = "kxc.raisound.com/amp-api"\r
-IOT_WS_URL = "ws://kxc.raisound.com/iot-api/ws-hub/dev/v1"\r
-\r
-# ---- DMP/认证/时间同步等(默认使用 constants.DMP_BASE 拼接)----\r
-DMP_TIME_PATH = "/dev/time"\r
-DMP_ACTIVE_PATH = "/dev/auth/active"\r
-DMP_SIGN_IN_PATH = "/dev/auth/sign_in"\r
-DMP_TOKEN_PATH = "/dev/auth/token"\r
-DMP_ICCID_PATH = "/dev/iccid"\r
-DMP_DEV_CONFIG_PATH_FMT = "/dev/config/{}"  # deviceId\r
-\r
-# ---- OTA(现网有的走 DMP_BASE,有的走 qipai 域名;先集中成可配项)----\r
-OTA_TASK_URL_FMT = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}"\r
-OTA_RESULT_PATH_FMT = "/ota/result?deviceId={}&currentVersion={}"\r
-\r
-# ---- AMP/健康检查等 ----\r
-AMP_HEALTH_URL_FMT = "http://{}/health?task_id={}"\r
-\r
+# 科学城 demo 环境 kxc — Server 配置模板(编译时 SRV_TYPE=kxc 拷贝为 core/default_srv.py)
+# URL 拼接说明见 beike_dev/default_srv.py 文首;本环境示例:
+#   VC: ws://kxc.raisound.com/amp-api/{VC_WS_PATH}?...
+#   IoT: ws://kxc.raisound.com/iot-api/ws-hub/dev/v1?bz-dt=...
+#   DMP: http://kxc.raisound.com/bz-api/dev/...
+
+SERVER_ENV = "kxc"
+
+DMP_BASE = "http://kxc.raisound.com/bz-api"
+AMP_SERVER = "kxc.raisound.com/amp-api"
+IOT_WS_URL = "ws://kxc.raisound.com/iot-api/ws-hub/dev/v1"
+
+DMP_TIME_PATH = "/dev/time"
+DMP_ACTIVE_PATH = "/dev/auth/active"
+DMP_SIGN_IN_PATH = "/dev/auth/sign_in"
+DMP_TOKEN_PATH = "/dev/auth/token"
+DMP_ICCID_PATH = "/dev/iccid"
+DMP_DEV_CONFIG_PATH_FMT = "/dev/config/{}"
+
+OTA_TASK_URL_FMT = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}"
+OTA_RESULT_PATH_FMT = "/ota/result?deviceId={}&currentVersion={}"
+
+AMP_HEALTH_URL_FMT = "http://{}/health?task_id={}"
index 3d85234..f6b2ea7 100644 (file)
@@ -31,21 +31,25 @@ SHA256 = uhashlib.sha256
 import SecureData
 import versions
 import version_util
-dev_type = "7PIN"#柒牌1代  
-# dev_type = "7PIN_1_5"#柒牌1.5代  
-def server_init(server_type = None):
-    #服务器选择1,防走丢,2大锤,3三基同创,4童信,5云米,6荣为 8:ds未知 dey.hinwatch. com:9968  9:183.62.239.121:31869 10:183.62.239.121:33461
-    if server_type == None:
+try:
+    import vc_config as _vc_cfg
+    _NLP_EXTENDED_CMDS = _vc_cfg.get_bool("NLP_EXTENDED_CMDS", False)
+except Exception:
+    _NLP_EXTENDED_CMDS = False
+
+def server_init(server_type=None):
+    # 服务器选择1,防走丢,2大锤,3三基同创,4童信,5云米,6荣为 8:ds未知 dey.hinwatch. com:9968  9:183.62.239.121:31869 10:183.62.239.121:33461
+    if server_type is None:
         server_type = 2
-    #EventMesh.debug_log("server_init:server_type:{}".format(server_type))
-   
+    # EventMesh.debug_log("server_init:server_type:{}".format(server_type))
+
     d_server_addr = "api.xcloudtech.com"
     d_server_port = 9000
     d_md5_key = "bZld0ALsZTt15tKEZhOlLPgbsO75BLTP"
     d_PID = "SZKW"
     if server_type == 1:
         pass
-        
+
     elif server_type == 2:
         # test.iot.socket.thisiot.com:9999
         # iot.socket .thisiot.com:9999
@@ -64,9 +68,9 @@ def server_init(server_type = None):
         d_server_port = 2346
         d_md5_key = ""
         d_PID = "3G"
-    elif  server_type == 4:
-         d_server_addr = "tcp.tx.kidxin.com"
-         d_server_port = 20500
+    elif server_type == 4:
+        d_server_addr = "tcp.tx.kidxin.com"
+        d_server_port = 20500
     elif server_type == 5:
         d_server_addr = "device.miwitracker.com"
         d_server_port = 8505
@@ -80,7 +84,7 @@ def server_init(server_type = None):
     elif server_type == 7:
         d_server_addr = "8.134.68.15"
         d_server_port = 30346
-        d_PID="KnHnj1Cd5VxcO3Ft"
+        d_PID = "KnHnj1Cd5VxcO3Ft"
         d_md5_key = "evxThVhMBmRNiQ1Twigw"
     elif server_type == 99:
         d_server_addr = "lab.xcloudtech.com"
@@ -95,7 +99,7 @@ def server_init(server_type = None):
         d_server_addr = "183.62.239.121"
         d_server_port = 33461
 
-    return [d_server_addr,d_server_port,d_md5_key,d_PID]
+    return [d_server_addr, d_server_port, d_md5_key, d_PID]
 from queue import Queue
 def unicode_to_utf8(src):
     ref = 0xe08080
@@ -195,88 +199,6 @@ def get_hmac_sha256(k, m):
     hex_data = hex_data.decode('ascii')
     return hex_data
 
-def mqtt_client_pro_init():#一型一密
-    client = 0
-    
-    # 创建一个mqtt实例
-#     
-    # {"productKey":"cu1r1rjjrjdhz0lx","deviceKey":"DD86000000001","deviceSecret":"93FEEC6D16D30E2B90FE0DBC2FFCDB14"} 设备基本信息
-    # hmac_sha256(deviceIddeviceKeyproductKey,deviceSecret) mqttclientId
-    deviceId = ""#"bOOSGN5FlXEjt15"
-    deviceKey = ''#"bOOSGN5FlXEjt15"
-    imei = EventMesh.publish(constants.EVENT.GET_IMEI)
-    deviceId = str(imei)
-    deviceKey = str(imei)
-    productKey = gewu_init()
-    
-    EventMesh.publish("set_product_key",productKey)
-    data = "{}{}{}".format(deviceId,deviceKey,productKey)#需要用户定义产品是提供数据
-    # data = b"The data to encrypt"
-    # 密钥
-    productSecret = b"765fc66242616852209d1c57e51b0744" #765fc66242616852209d1c57e51b0744 ,productSecret
-
-    # {"productKey":"cu1shld45y4hjr1L","deviceKey":"bOOSGN5FlXEjt15","deviceSecret":"9234577490038E0949B800C243BF4321"}
-    checkpassword = get_hmac_sha256(productSecret,data)
-    # print(checkpassword)
-    # {deviceId}|{productKey}|{signMethod}|{authType}|{operator};连接id格式
-    
-    Client_id = "{}|{}|0|1|0".format(deviceId,productKey) #
-    # print(Client_id)
-    server = "dmp-mqtts.cuiot.cn"#"dmp-mqtt.cuiot.cn"
-    port = 8883
-    # {deviceKey}|{productKey} mqttUsername
-    userName = "{}|{}".format(deviceKey,productKey)
-    userpassword = checkpassword
-    # print(userName)
-    # print(userpassword)
-    # client = umqtt.MQTTClient("866610001011658|cu1shld45y4hjr1L|0|1|0","dmp-mqtt.cuiot.cn", 1883, "866610001011658|cu1shld45y4hjr1L","675f6fbfb15a729bbc280273632ce3a96f8b38d4736fbaf888fe8a48fd7aee09")
-    # client = umqtt.MQTTClient("lxRXQayZ4poQz8G|cujg06l29iav591b|0|0|0","dmp-mqtt.cuiot.cn", 1883, "lxRXQayZ4poQz8G|cujg06l29iav591b","1f8c69d29afca0505d967066b39d84a5eb1e54302b87e337b405f99d029a9242")
-    client = umqtt.MQTTClient(Client_id,server, port, userName,"{}".format(userpassword),reconn=False,ssl=True)
-    # client = MqttClient(Client_id, server, port, userName,"{}".format(userpassword))
-    # 设置消息回调
-    client.set_callback(mqttsub_cb)
-    # 设置异常回调
-    client.error_register_cb(mqtterr_cb)
-    return client
-
-def mqtt_client_dev_init():#一机一密
-    client = 0
-    
-    # 创建一个mqtt实例
-#     
-    # {"productKey":"cu1r1rjjrjdhz0lx","deviceKey":"DD86000000001","deviceSecret":"93FEEC6D16D30E2B90FE0DBC2FFCDB14"} 设备基本信息
-    # hmac_sha256(deviceIddeviceKeyproductKey,deviceSecret) mqttclientId
-    deviceId = '' #"bOOSGN5FlXEjt15"
-    deviceKey = ''#"bOOSGN5FlXEjt15"
-    imei = EventMesh.publish(constants.EVENT.GET_IMEI)
-    deviceId = str(imei)
-    deviceKey = str(imei)
-    productKey = gewu_init()
-    data = "{}{}{}".format(deviceId,deviceKey,productKey)#需要用户定义产品是提供数据
-    # data = b"The data to encrypt"
-    # 密钥
-    deviceSecret_tmp = EventMesh.publish("get_deviceSecret")
-    subscribe_msg = deviceSecret_tmp.encode('ascii')
-    deviceSecret = subscribe_msg #765fc66242616852209d1c57e51b0744 ,productSecret
-    print(deviceSecret)
-
-    checkpassword = get_hmac_sha256(deviceSecret,data)
-    print(checkpassword)
-    # {deviceId}|{productKey}|{signMethod}|{authType}|{operator};连接id格式
-    Client_id = "{}|{}|0|0|0".format(deviceId,productKey) # 860132060346475|cu1shld45y4hjr1L|0|0|0
-    server = "dmp-mqtts.cuiot.cn"#"dmp-mqtt.cuiot.cn"
-    port = 8883
-    # {deviceKey}|{productKey} mqttUsername
-    userName = "{}|{}".format(deviceKey,productKey)
-    userpassword = checkpassword
-
-    client = umqtt.MQTTClient(Client_id,server, port, userName,"{}".format(userpassword),reconn=False,ssl=True)
-    # client = MqttClient(Client_id, server, port, userName,"{}".format(userpassword))
-    # 设置消息回调
-    client.set_callback(mqttsub_cb)
-    # 设置异常回调
-    client.error_register_cb(mqtterr_cb)
-    return client
 ######################IOT######################
 import sms
 import voiceCall
@@ -2758,24 +2680,19 @@ def ai_cmd_handle(cmd):
         tts_play_str = "抱歉呀,暂不支持该命令"
         EventMesh.publish("play_tts",[tts_play_str,0])        
 
-    if dev_type ==  "7PIN":#一代没有下面的功能
-        # print("一代不支持")
+    if not _NLP_EXTENDED_CMDS:
         return
-    if "会议纪要" in cmd:#当指令中有会议纪要时,进入会议纪要模式
-        if dev_type == "7PIN_1_5":
-            print("cmd:已进入会议")
-            EventMesh.publish("come_meet_mode")#进入会议纪要
-    elif "翻译" in cmd:#当指令中有翻译时,进入AI翻译模式
-        if dev_type == "7PIN_1_5":
-            print("cmd:已进入翻译")
-            EventMesh.publish("come_trans_mode")#进入AI翻译纪要
-    elif "查看历史留言" in cmd :#当指令中有查看历史留言时,调用历史留言接口
-        if dev_type == "7PIN_1_5":
-            EventMesh.publish(WEB_CMD_ENUM.GET_NEW_VOICE_MSG_S)
-            EventMesh.publish("set_history_voice_flag",1)#修改标志位
-            EventMesh.publish("clean_unread_vocie")#清空未读多条一起下发记录   
-            EventMesh.publish("set_storage_config",["is_noread_voice",0])
-            pass
+    if "会议纪要" in cmd:
+        print("cmd:已进入会议")
+        EventMesh.publish("come_meet_mode")
+    elif "翻译" in cmd:
+        print("cmd:已进入翻译")
+        EventMesh.publish("come_trans_mode")
+    elif "查看历史留言" in cmd:
+        EventMesh.publish(WEB_CMD_ENUM.GET_NEW_VOICE_MSG_S)
+        EventMesh.publish("set_history_voice_flag", 1)
+        EventMesh.publish("clean_unread_vocie")
+        EventMesh.publish("set_storage_config", ["is_noread_voice", 0])
 
 #NLP指令
 class IOTWeb_Nlp_ConfigProtoCol(ProtoCol):
diff --git a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_text_intent.py b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_text_intent.py
new file mode 100644 (file)
index 0000000..2e5f081
--- /dev/null
@@ -0,0 +1,469 @@
+# -*- coding: utf-8 -*-
+"""
+本地/统一文本指令识别(表驱动)。
+
+设计目标:
+- 与 ai_VC 解耦:只做「文本 -> 结构化意图」,不直接操作硬件/网络。
+- 不区分 LLM 下发的 chinese_description 与 ASR 原文:调用方先得到字符串,再 match。
+- MicroPython 友好:无第三方依赖,仅 str.find / 列表遍历。
+
+扩展命中率(同义说法):
+- 在 _INTENT_RULES / _BT_INTENT_RULES 里找到对应 intent 的字典。
+- any_groups 为「与」关系的多组:每组是 (a, b, c, ...) 时,组内为「或」——命中任意一个子串即满足该组。
+  例:("音量减小", "音量太大", "声音太大") 表示这三种说法之一即可视为「音量减小」意图。
+- 需要「同时包含」多个子串时,用 all: ("音量", "减") 这类(组内全是 AND)。
+- 排除误触:用 forbid,子串出现则整条规则不匹配。
+- 归一化/别字:改 normalize_vc_command_text 或 _ASR_ALIAS。
+
+用法示例::
+
+    from vc_text_intent import match_vc_command, merge_intent_result
+
+    r = match_vc_command(user_text, ctx={"bt_voice": True})
+    if r:
+        # r["intent"], r.get("tts"), r.get("close_flag"), r.get("power_off_hint") ...
+        pass
+"""
+
+# ---------------------------------------------------------------------------
+# 文本归一化
+# ---------------------------------------------------------------------------
+
+# 用 str.replace 删标点;勿 for ch in text(EG810 MPY 逐字符迭代会破坏 UTF-8 中文)
+_PUNCT_REMOVE = (
+    " ", "\t", "\r", "\n",
+    "\u200b", "\u200c", "\u200d", "\ufeff",
+    "。", ".", ".", "!", "!", "?", "?", ",", ",", "、",
+    ";", ";", ":", ":", '"', "'",
+    "(", ")", "(", ")",
+    "[", "]", "【", "】", "《", "》", "<", ">",
+)
+
+# 常见 ASR 别字:可按产品迭代追加(全词替换后再匹配)
+_ASR_ALIAS = (
+    ("在见", "再见"),
+    ("拜白", "拜拜"),
+    ("关毕对话", "关闭对话"),
+    ("关比对话", "关闭对话"),
+)
+
+
+def normalize_vc_command_text(text):
+    """去空白 + 去常见标点 + 轻量别字替换。"""
+    if text is None:
+        return ""
+    try:
+        t = str(text).strip()
+    except Exception:
+        return ""
+    if not t:
+        return ""
+    for p in _PUNCT_REMOVE:
+        if p:
+            t = t.replace(p, "")
+    for wrong, right in _ASR_ALIAS:
+        if wrong in t:
+            t = t.replace(wrong, right)
+    return t
+
+
+def _has_all(t, subs):
+    for s in subs:
+        if not s or t.find(s) == -1:
+            return False
+    return True
+
+
+def _has_any(t, subs):
+    for s in subs:
+        if s and t.find(s) != -1:
+            return True
+    return False
+
+
+def _has_none(t, subs):
+    """若 subs 中任一出现在 t 中,返回 False(用于排除「太快」等)。"""
+    for s in subs:
+        if s and t.find(s) != -1:
+            return False
+    return True
+
+
+def _ctx_ok(rule, ctx):
+    req = rule.get("require_ctx")
+    if not req:
+        return True
+    if not ctx:
+        return False
+    return bool(ctx.get(req))
+
+
+def _match_rule(t, n, rule):
+    if not _ctx_ok(rule, rule.get("_ctx") or {}):
+        return False
+    lt = rule.get("len_lt")
+    if lt is not None and n >= lt:
+        return False
+    le = rule.get("len_le")
+    if le is not None and n > le:
+        return False
+    gt = rule.get("len_gt")
+    if gt is not None and n <= gt:
+        return False
+    # 必须全部出现
+    if not _has_all(t, rule.get("all", ())):
+        return False
+    # 任一「或组」:每组至少命中一个子串;多组则组间为 AND
+    for grp in rule.get("any_groups", ()):
+        if not _has_any(t, grp):
+            return False
+    # 禁止出现
+    if not _has_none(t, rule.get("forbid", ())):
+        return False
+    return True
+
+
+def _build_result(rule):
+    out = {"intent": rule["id"]}
+    for k in ("tts", "close_flag", "power_off_hint", "factory_hint", "needs_payload"):
+        if k in rule:
+            out[k] = rule[k]
+    return out
+
+
+# ---------------------------------------------------------------------------
+# 规则表:顺序即优先级(先命中先返回)
+# ---------------------------------------------------------------------------
+
+_INTENT_RULES = (
+    # ---------- 会话控制 ----------
+    {
+        "id": "goodbye",
+        "len_lt": 20,
+        "any_groups": (("再见", "拜拜", "关闭对话"),),
+        "all": (),
+        "forbid": (),
+        "tts": "好嘞,再见,我们下次再聊",
+        "close_flag": 1,
+    },
+    {
+        "id": "shutdown",
+        "len_lt": 12,
+        "any_groups": (("关机",),),
+        "all": (),
+        "forbid": (),
+        "power_off_hint": 1,
+    },
+    {
+        "id": "toggle_dialog_mode",
+        "any_groups": (("切换对话模式",),),
+        "all": (),
+        "forbid": (),
+        # 具体播报文案建议仍由 ai_VC / vc_config 决定,此处仅占位
+        "tts": None,
+    },
+    # ---------- 音量(与 LLM 侧「音量增大」类说法对齐) ----------
+    {
+        "id": "volume_set",
+        "any_groups": (("音量调整",),),
+        "all": (),
+        "forbid": (),
+        "needs_payload": 1,
+        "tts": None,
+    },
+    {
+        "id": "volume_up",
+        "any_groups": (
+            (
+                "音量增大",
+                "音量太小",
+                "声音太小",
+                "大声一点",
+                "音量大一点",
+                "再大一点声",
+            ),
+        ),
+        "all": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "volume_up",
+        "all": ("音量", "加"),
+        "any_groups": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "volume_down",
+        "any_groups": (
+            (
+                "音量减小",
+                "音量太大",
+                "声音太大",
+                "太响了",
+                "太吵了",
+                "小声一点",
+                "音量小一点",
+                "关小音量",
+                "降低音量",
+                "把音量调低",
+            ),
+        ),
+        "all": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "volume_down",
+        "all": ("音量", "减"),
+        "any_groups": (),
+        "forbid": (),
+        "tts": None,
+    },
+    # ---------- 语速(讲话/说话 + 快/慢,排除「太快」「太慢」) ----------
+    {
+        "id": "speech_faster",
+        "any_groups": (("讲话", "说话"), ("快",)),
+        "all": (),
+        "forbid": ("太",),
+        "tts": None,
+    },
+    {
+        "id": "speech_slower",
+        "any_groups": (("讲话", "说话"), ("慢",)),
+        "all": (),
+        "forbid": ("太",),
+        "tts": None,
+    },
+    # LLM 常用「语速增大/减小」(与「讲话快」并列)
+    {
+        "id": "speech_rate_up",
+        "any_groups": (("语速增大",),),
+        "all": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "speech_rate_down",
+        "any_groups": (("语速减小",),),
+        "all": (),
+        "forbid": (),
+        "tts": None,
+    },
+    # ---------- VAD 断句(聆听/明天 + 快/慢) ----------
+    {
+        "id": "vad_faster",
+        "len_lt": 22,
+        "any_groups": (("聆听", "明天"), ("快",)),
+        "all": (),
+        "forbid": ("太",),
+        "tts": None,
+    },
+    {
+        "id": "vad_slower",
+        "len_lt": 22,
+        "any_groups": (("聆听", "明天"), ("慢",)),
+        "all": (),
+        "forbid": ("太",),
+        "tts": None,
+    },
+    # ---------- 查询 ----------
+    {
+        "id": "query_signal",
+        "all": ("查询", "信号"),
+        "any_groups": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "query_battery",
+        "all": ("查询", "电量"),
+        "any_groups": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "query_voltage",
+        "all": ("查询", "电压"),
+        "any_groups": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "query_version",
+        "all": ("查询", "版本"),
+        "any_groups": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "query_server",
+        "all": ("查询", "服务器地址"),
+        "any_groups": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "query_iccid",
+        "all": ("查询", "卡号"),
+        "any_groups": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "query_imei",
+        "all": ("查询", "设备号"),
+        "any_groups": (),
+        "forbid": (),
+        "tts": None,
+    },
+    # ---------- OTA / 工厂 ----------
+    {
+        "id": "ota_check",
+        "all": ("升级",),
+        "any_groups": (("OTA", "ota"),),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "factory_mode",
+        "any_groups": (("进入工厂模式",),),
+        "all": (),
+        "forbid": (),
+        "factory_hint": 1,
+    },
+    # ---------- 闹钟(仅关键词;结构化参数仍由服务端/JSON 提供) ----------
+    {
+        "id": "alarm_set",
+        "any_groups": (("闹钟设定",),),
+        "all": (),
+        "forbid": (),
+        "needs_payload": 1,
+        "tts": None,
+    },
+    {
+        "id": "alarm_clear",
+        "any_groups": (("取消闹钟",),),
+        "all": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "alarm_list",
+        "any_groups": (("闹钟列表",),),
+        "all": (),
+        "forbid": (),
+        "tts": None,
+    },
+)
+
+# 蓝牙相关:默认不匹配,ctx["bt_voice"]=True 时注入
+_BT_INTENT_RULES = (
+    {
+        "id": "voice_message_mode",
+        "any_groups": (("语音留言",),),
+        "all": (),
+        "forbid": (),
+        "close_flag": 1,
+        "tts": None,
+    },
+    {
+        "id": "bt_ble_adv_on",
+        "any_groups": (("开广播", "开蓝牙广播"),),
+        "all": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "bt_ble_adv_off",
+        "any_groups": (("关广播", "关蓝牙广播"),),
+        "all": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "bt_classic_on",
+        "any_groups": (("开经典蓝牙",),),
+        "all": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "bt_classic_off",
+        "any_groups": (("关闭经典蓝牙", "关经典蓝牙"),),
+        "all": (),
+        "forbid": (),
+        "tts": None,
+    },
+    {
+        "id": "unbind_confirm",
+        "any_groups": (("确认解绑设备", "确认设备解绑"),),
+        "all": (),
+        "forbid": (),
+        "close_flag": 1,
+        "tts": None,
+    },
+    {
+        "id": "clear_earphone",
+        "any_groups": (("删除耳机记录", "清楚耳机记录"),),
+        "all": (),
+        "forbid": (),
+        "tts": None,
+    },
+)
+
+
+def _iter_rules(ctx):
+    bt = bool(ctx and ctx.get("bt_voice"))
+    for r in _INTENT_RULES:
+        rr = dict(r)
+        rr["_ctx"] = ctx
+        yield rr
+    if bt:
+        for r in _BT_INTENT_RULES:
+            rr = dict(r)
+            rr["_ctx"] = ctx
+            rr["require_ctx"] = "bt_voice"
+            yield rr
+
+
+def match_vc_command(text, ctx=None):
+    """
+    对单条用户文本做意图匹配。
+
+    :param text: 任意来源的 utf-8 字符串(ASR 合并句 / LLM chinese_description)
+    :param ctx: 可选 dict,目前支持:
+        - bt_voice: True 时启用蓝牙相关指令规则
+    :return: 命中则返回 dict;未命中返回 None
+
+    返回 dict 常见字段:
+        - intent: str
+        - tts: str | None  (None 表示由调用方按 intent 自行组句)
+        - close_flag: 0/1
+        - power_off_hint: 0/1
+        - factory_hint: 0/1
+        - needs_payload: 0/1  (如闹钟设定需 JSON)
+    """
+    ctx = ctx or {}
+    t = normalize_vc_command_text(text)
+    n = len(t)
+    if n == 0:
+        return None
+    if n > 60:
+        return None
+
+    for rule in _iter_rules(ctx):
+        if _match_rule(t, n, rule):
+            return _build_result(rule)
+    return None
+
+
+def merge_intent_result(base, overrides):
+    """浅合并,便于调用方用 vc_config 覆盖 tts 等。"""
+    out = {}
+    if base:
+        out.update(base)
+    if overrides:
+        out.update(overrides)
+    return out if out else None
index cc37a03..abeff68 100644 (file)
@@ -1,36 +1,68 @@
 import versions
 
 
-def _build_time_token():
-    """从 versions.Build_Time 解析,输出 yyyy_MM_dd_HH_mm(不含秒)。"""
+def _char_is_digit(ch):
     try:
-        s = str(versions.Build_Time).strip().strip('"').strip("'")
+        return ch.isdigit()
     except Exception:
-        s = ""
+        return len(ch) == 1 and ch in "0123456789"
+
+
+def _build_time_token_sanitize(s):
+    """ASCII 兜底:不依赖 isdigit/逐字符迭代,仅 replace + split。"""
     if not s:
         return ""
-
-    # 已是下划线格式:取前 5 段(去掉秒)
-    if all((c.isdigit() or c == "_") for c in s):
-        parts = s.split("_")
-        if len(parts) >= 5:
-            y, mo, d, hh, mi = parts[0], parts[1], parts[2], parts[3], parts[4]
+    out = str(s).strip()
+    for a, b in ((" ", "_"), ("-", "_"), (":", "_"), (".", "_"), ("/", "_")):
+        out = out.replace(a, b)
+    while "__" in out:
+        out = out.replace("__", "_")
+    out = out.strip("_")
+    parts = [p for p in out.split("_") if p]
+    if len(parts) >= 5:
+        y, mo, d, hh, mi = parts[0], parts[1], parts[2], parts[3], parts[4]
+        try:
             return "{}_{}_{}_{}_{}".format(
                 y, mo.zfill(2), d.zfill(2), hh.zfill(2), mi.zfill(2)
             )
+        except Exception:
+            pass
+    return out
+
+
+def _build_time_token():
+    """从 versions.Build_Time 解析,输出 yyyy_MM_dd_HH_mm(不含秒)。"""
+    try:
+        s = str(getattr(versions, "Build_Time", "") or "").strip().strip('"').strip("'")
+    except Exception:
+        s = ""
+    if not s:
+        return ""
 
-    # 原始 Build_Time:2026-05-18  1:11:19
     try:
+        # 已是下划线格式:2026_05_19_13_48_10
+        if all((_char_is_digit(c) or c == "_") for c in s):
+            parts = [p for p in s.split("_") if p]
+            if len(parts) >= 5:
+                y, mo, d, hh, mi = parts[0], parts[1], parts[2], parts[3], parts[4]
+                return "{}_{}_{}_{}_{}".format(
+                    y, mo.zfill(2), d.zfill(2), hh.zfill(2), mi.zfill(2)
+                )
+
+        # 常见:2026-05-19 13:48:10
         while "  " in s:
             s = s.replace("  ", " ")
-        date_part, time_part = s.split(" ", 1)
-        y, mo, d = date_part.replace("/", "-").split("-")[:3]
-        t = time_part.strip().split(":")
-        hh = t[0].zfill(2) if len(t) > 0 else "00"
-        mi = t[1].zfill(2) if len(t) > 1 else "00"
-        return "{}_{}_{}_{}_{}".format(y, mo.zfill(2), d.zfill(2), hh, mi)
+        if " " in s:
+            date_part, time_part = s.split(" ", 1)
+            y, mo, d = date_part.replace("/", "-").split("-")[:3]
+            t = time_part.strip().split(":")
+            hh = t[0].zfill(2) if len(t) > 0 else "00"
+            mi = t[1].zfill(2) if len(t) > 1 else "00"
+            return "{}_{}_{}_{}_{}".format(y, mo.zfill(2), d.zfill(2), hh, mi)
     except Exception:
-        return ""
+        pass
+
+    return _build_time_token_sanitize(s)
 
 
 def get_unified_version():
@@ -42,15 +74,21 @@ def get_unified_version():
     if not pn:
         pn = "unknown"
     try:
-        ver = str(versions.Version).strip()
+        ver = str(getattr(versions, "Version", "") or "").strip()
     except Exception:
         ver = ""
-    return "{}_{}_{}".format(pn, ver, _build_time_token())
+    bt = _build_time_token()
+    if bt:
+        return "{}_{}_{}".format(pn, ver, bt)
+    return "{}_{}".format(pn, ver)
 
 
 def get_tts_version(bt_suffix=""):
     """本地 TTS 播报用统一版本号,可选拼接蓝牙模块版本。"""
     base = get_unified_version()
-    if bt_suffix:
-        return base + str(bt_suffix)
-    return base
+    if not bt_suffix:
+        return base
+    sfx = str(bt_suffix)
+    if sfx.startswith("_"):
+        return base + sfx
+    return base + "_" + sfx