暂时单对话、长对话都工作,但是单对话还是会出现按下去后,灯不太正常的情况,要等两秒左右进入快闪模式才正常
authorSnickerSun <snickersun@163.com>
Sun, 17 May 2026 16:23:02 +0000 (00:23 +0800)
committerSnickerSun <snickersun@163.com>
Sun, 17 May 2026 16:23:02 +0000 (00:23 +0800)
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/products/configs/7PIN/default_config.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/versions.py

index 6b5bfc8..7d4ad9f 100644 (file)
@@ -3451,7 +3451,9 @@ class VCStateMachine:
                     if self.mode() == VCMode.Realtime and getattr(self.vc, "Close_flag", 0) == 0:
                         k = getattr(self.vc, "_last_tts_kind", "") or ""
                         if k == "dialog":
-                            actions.append(("uplink_start", "local.play_end"))
+                            # 用户短按打断(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()
@@ -3524,10 +3526,116 @@ class AI_VC(Abstract):
                 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:
@@ -3682,6 +3790,10 @@ class AI_VC(Abstract):
                 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:
@@ -3690,6 +3802,12 @@ class AI_VC(Abstract):
                     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:
@@ -3721,6 +3839,10 @@ class AI_VC(Abstract):
             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
 
@@ -3831,8 +3953,93 @@ class AI_VC(Abstract):
     # - 长按:无论当前处于 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
 
@@ -3932,6 +4139,11 @@ class AI_VC(Abstract):
             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:
@@ -3942,6 +4154,10 @@ class AI_VC(Abstract):
 
     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:
@@ -3982,11 +4198,6 @@ class AI_VC(Abstract):
             self._ptt_await_server = 0
         except Exception:
             pass
-        # 打断上一段 TTS 后可能仍有残留音频帧下发:短时间丢弃,避免“断断续续继续播”
-        try:
-            self._drop_tts_audio = 1
-        except Exception:
-            pass
 
         t0 = 0
         try:
@@ -3994,30 +4205,28 @@ class AI_VC(Abstract):
         except Exception:
             t0 = 0
 
-        # 先亮聆听灯,再停播;VC_init/VC_Play_End 全程抑制 idle,避免开录前灭灯
+        self._ptt_enter_interrupt_arm()
         try:
-            self._vc_play_end_suppress_idle_led = 1
+            self._vc_publish_led("listening", "ptt_begin/pre")
         except Exception:
             pass
+
+        # 本地先硬静音再 VC_init,避免 DMA/队列尾包漏播
         try:
-            self._vc_publish_led("listening", "ptt_begin/pre")
+            self._ptt_hard_mute_local_playback()
         except Exception:
             pass
-
-        # 本地先停播并清空队列,避免“边播边录/回声触发/状态机乱跳”
         try:
             self.VC_init()
         except Exception:
             pass
-
-        # 再强制停播一次(兼容:VC_init 未完全覆盖播放对象的场景)
         try:
-            if getattr(self, "VC_Play_flag", 0) == 1 or getattr(self, "play_pcm", None) != None:
+            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_publish_led("listening", "ptt_begin/armed")
+            self._ptt_hard_mute_local_playback()
         except Exception:
             pass
 
@@ -4043,7 +4252,6 @@ class AI_VC(Abstract):
             if getattr(self, "AI_client_VC_isconnect", 0) != 1:
                 self._ptt_force_record_on_started = 1
                 self.AI_VC_start()
-                self._ptt_end_led_suppress()
                 return
         except Exception:
             self._ptt_force_record_on_started = 1
@@ -4051,7 +4259,6 @@ class AI_VC(Abstract):
                 self.AI_VC_start()
             except Exception:
                 pass
-            self._ptt_end_led_suppress()
             return
 
         # 会话还没 initialized:先发 Start,请求服务端进入 started
@@ -4066,17 +4273,60 @@ class AI_VC(Abstract):
                 self.AI_client_VC_start_requset()
             except Exception:
                 pass
-            self._ptt_end_led_suppress()
             return
 
         # 服务端仍在思考/TTS:先 cancel,在 btn 队列收到 canceled 后再开录(方案甲串行)
         if self._vc_ptt_need_interrupt_wait():
             self._vc_begin_ptt_cancel_wait("ptt_begin")
-            self._ptt_end_led_suppress()
             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:
@@ -4114,6 +4364,15 @@ class AI_VC(Abstract):
         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(
@@ -4129,6 +4388,12 @@ class AI_VC(Abstract):
 
     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")
@@ -4189,6 +4454,22 @@ class AI_VC(Abstract):
         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:
@@ -4232,6 +4513,12 @@ class AI_VC(Abstract):
             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):
@@ -4324,9 +4611,9 @@ class AI_VC(Abstract):
                     self.request_uplink_start(a[1] if len(a) > 1 else "")
                 elif a[0] == "play_start":
                     if getattr(self, "VC_mode", -1) == 1:
-                        if getattr(self, "_vc_serial_canceling", 0) or getattr(self, "_ptt_pending_arm", 0):
+                        if self._ptt_should_skip_remote_playback():
                             if self._vc_led_state_debug_enabled():
-                                print("[VC][serial] skip play_start (cancel_wait)")
+                                print("[VC][serial] skip play_start (ptt_interrupt)")
                             continue
                     try:
                         self.fsm.on_local_play_start()
@@ -4442,6 +4729,10 @@ class AI_VC(Abstract):
         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
@@ -4728,6 +5019,11 @@ class AI_VC(Abstract):
         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 #音乐播放标志
@@ -5073,6 +5369,11 @@ class AI_VC(Abstract):
                     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])#音量配置
@@ -5400,6 +5701,13 @@ class AI_VC(Abstract):
             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)
@@ -5447,6 +5755,11 @@ class AI_VC(Abstract):
                     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()
@@ -5459,7 +5772,7 @@ class AI_VC(Abstract):
             self._uplink_mark_end("VC_Speech_End")
             self.AI_ASR_flag = 0
             self.AI_ASR_str_tmp = ""
-            self.AI_ASR_str = ""
+            # 勿在此清空 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:
@@ -6339,8 +6652,10 @@ class AI_VC(Abstract):
                                 self._asr_completed_tone_done = 0
                             except Exception:
                                 pass
+                            # 单对话:_drop_tts_audio 在 VC_Speech_Start 真开录后再清 0,避免 canceled 后仍播旧 TTS
                             try:
-                                self._drop_tts_audio = 0
+                                if getattr(self, "VC_mode", 0) != 1:
+                                    self._drop_tts_audio = 0
                             except Exception:
                                 pass
                             # 单对话:勿在 asr.started 置 flag=2(未真开麦会僵死);由 VC_Speech_Start 维护
@@ -6354,15 +6669,30 @@ class AI_VC(Abstract):
                                 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
-                            # 本地 think 提示:每轮 ASR 最多一次,且不得与流式 TTS/PCM 播放竞态(避免长对话偶发连播「滴」)
+                            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:
@@ -6376,13 +6706,13 @@ class AI_VC(Abstract):
                                     except Exception:
                                         thinking = False
                                     if (not stream_on) and (not pcm_busy) and (not playing) and (not thinking):
-                                        self.aud.play(1, 1, 'U:/static/think.mp3')
+                                        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.AI_ASR_str)
+                                ret= self.check_text(self._vc_effective_asr_text())
                                 if ret == 0:#配置指令
                                             
                                     if self.AI_VC_send_data_queue.empty()!=True:
@@ -6529,6 +6859,7 @@ class AI_VC(Abstract):
                             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
@@ -6664,7 +6995,7 @@ class AI_VC(Abstract):
                         
                         # PTT 打断后丢弃残留 TTS 音频帧,避免“断断续续继续播”
                         try:
-                            if getattr(self, "_drop_tts_audio", 0) == 1:
+                            if self._ptt_should_drop_inbound_tts():
                                 continue
                         except Exception:
                             pass
@@ -6837,7 +7168,11 @@ class AI_VC(Abstract):
     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:
@@ -7505,10 +7840,23 @@ class AI_VC(Abstract):
         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:
@@ -7527,9 +7875,11 @@ class AI_VC(Abstract):
                     if self.play_pcm == None:
                         # 刚停采集时延后打开播音 PCM,且失败时重试,降低「pcm open write only fail」
                         last_err = None
-                        for i in range(3):
+                        _open_retries = 1 if _ptt_fast_open else 3
+                        for i in range(_open_retries):
                             try:
-                                utime.sleep_ms(80 + i * 60)
+                                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))
@@ -7549,7 +7899,8 @@ class AI_VC(Abstract):
                     pass
                 self.VC_Play_flag = 1
                 try:
-                    self._vc_publish_led("playing", "VC_Play_Start/tts")
+                    if not self._ptt_should_skip_remote_playback():
+                        self._vc_publish_led("playing", "VC_Play_Start/tts")
                 except Exception:
                     pass
             except Exception as e:
@@ -8190,6 +8541,7 @@ class AI_VC(Abstract):
         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
@@ -8733,6 +9085,11 @@ class AI_VC(Abstract):
                     #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()
@@ -8850,10 +9207,15 @@ class AI_VC(Abstract):
         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 = ""
index 243f326..bc5c56a 100644 (file)
-# 设备侧默认配置(freeze 内置)。\r
-#\r
-# 加载优先级(由 vc_config.py 执行):\r
-# 1) usr/default_config.py(或 usr/defualt_config.py)\r
-# 2) 本文件 default_config.py\r
-\r
-# 当前设备类型(编译期间确定;现场仅可用 usr/default_config.py 覆盖)\r
-DEV_TYPE = "7PIN"\r
-\r
-# 是否加载 quecgnss 并在定位管理器初始化时调用 gnssEnable(0)。\r
-# 无 GNSS 硬件的机型(如部分吊坠)可置 False,避免 import quecgnss 或底层驱动异常/阻塞。\r
-GNSS_ENABLED = False\r
-\r
-# 为 True 时,protocol 模块 import 过程会打印 [proto.import] 阶段标记(配合 qp 日志口排查卡点)。\r
-PROTOCOL_IMPORT_TRACE = True\r
-\r
-# 当前设备配置(仅保留“设备差异项”,不要放运行态变量)\r
-# led_gpio 使用 Pin 属性名(字符串),由 vc_config.py 转成 Pin.GPIOxx 常量。\r
-LED_GPIO = ("GPIO36", "GPIO44", "GPIO43")  # (R, G, B)\r
-\r
-# LED 电平极性(是否“反向”点亮)。\r
-# - 默认 LED_ACTIVE_LOW=False:高电平点亮(write(1)=亮)\r
-# - 若你的硬件是低电平点亮(write(0)=亮),将 LED_ACTIVE_LOW=True\r
-# - 若仅某一路反相,用 LED_ACTIVE_LOW_RGB 单独配置(按 LED_GPIO 顺序 R/G/B)\r
-LED_ACTIVE_LOW = False\r
-LED_ACTIVE_LOW_RGB = None  # 例如 (True, False, False) 表示仅红灯反相\r
-# LED_ACTIVE_LOW_RGB = (True, False, False)\r
-\r
-# idle 绿灯长慢闪:常亮 / 熄灭(ms);仅配 LED_IDLE_GREEN_BLINK_MS 时灭灯时长与常亮相同(对称慢闪)\r
-LED_IDLE_GREEN_BLINK_MS = 1000\r
-LED_IDLE_GREEN_BLINK_OFF_MS = 2000\r
-\r
-# 是否支持 UART 相关能力(例如 7PIN_1_5 才允许走串口)\r
-UART_SUPPORTED = False\r
-\r
-# 语言/地区(如 zh/en),用于少量文案/逻辑差异\r
-LANG = "zh"\r
-\r
-# --------------------\r
-# 硬件差异项(由每个设备的 default_config 固定)\r
-# --------------------\r
-LED_DEBUG = True\r
-LED_STATE_DEBUG = True\r
-KEY_LED_DEBUG = True\r
-HW_STATUS_DEBUG = True\r
-\r
-# --------------------\r
-# 电池 OCV / 硬件状态轮询(AI_hw_status,可在 usr/default_config.py 覆盖)\r
-# --------------------\r
-# True 时用三档电压门限估算 SoC,忽略 BATTERY_OCV_MAP\r
-USE_SIMPLE_VBATT_THRESH = False\r
-VBATT_CHARGE_FULL_MV = 4290   # USB 在位且电压≥此值视为充满(get_battery / AI_hw_status / AI_dev 共用)\r
-VBATT_LOW_MV = 3699           # 低电电压门限;simple 模式与 AI_dev 低电提示用\r
-VBATT_EMPTY_MV = 3629         # 空电电压门限;AI_dev 低电关机等\r
-BATTERY_LOW_SOC_PERCENT = 30  # OCV 模式:soc < 此值 → battery=low(REQ-BAT-02:10%~30%)\r
-BATTERY_EMPTY_SOC_PERCENT = 10  # OCV 模式:soc < 此值 → battery=empty(REQ-BAT-03:<10%)\r
-HW_STATUS_PERIOD_MS = 10000\r
-SIGNAL_WEAK_CSQ = 10\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
-# 是否需要初始化蓝牙电源/马达/音频IIS配置等(原先仅 7PIN_1_5)\r
-BT_POWER_MOTOR_ENABLED = False\r
-PIN_BT_ON_GPIO = None      # 例如 "GPIO31"\r
-PIN_MOT_GPIO = None\r
-# audio.set_qdai_cfg(...) 参数;若为 None 则不调用\r
-AUDIO_QDAI_CFG = None      # 例如 (1,0,0,2,0,1,1,1)\r
-\r
-# motor.py(motorEventManager):马达输出脚与节拍。MOTOR_GPIO 为 None 时依次尝试 PIN_MOT_GPIO、默认 GPIO36。\r
-MOTOR_PIN_GPIO = "GPIO20"          # 例如 "GPIO36";无马达硬件可保持 None\r
-MOTOR_TICK_MS = 100\r
-MOTOR_ACTIVE_LOW = False\r
-MOTOR_BOOT_VIBRATE_MS = 200 # 马达开机震动时间 200ms\r
-\r
-\r
-# AI 按键中断与电源键中断引脚(ExtInt.GPIOxx)\r
-EXTINT_AI_GPIO = "GPIO22"   # ai按键\r
-PWK_KEY_GPIO = "GPIO26"     # 侧边power按键 , 模式切换\r
-\r
-#btn_device_ec800m.py\r
-BTN_WECHAT_GPIO = None\r
-BTN_SOS_GPIO = None\r
-\r
-\r
-# 蓝牙唤醒 4G/4G 唤醒蓝牙相关(原先仅 7PIN_1_5)\r
-BT_WAKEUP_4G_ENABLED = False\r
-EXTINT_WAKEUP_CTA1_GPIO = None  # 例如 "GPIO22"\r
-PIN_WAKEUP_BT_GPIO = None       # 例如 "GPIO21"\r
-\r
-# --------------------\r
-# 按键/防抖差异\r
-# --------------------\r
-\r
-# 释放防抖阈值(KeyAI/pwk):原代码部分设备用 3,其他用 5\r
-KEY_RELEASE_DEBOUNCE_COUNT = 5\r
-\r
-# 是否允许 AI 键长按 8 秒触发关机逻辑(原 jswm/jswm_BOX)\r
-AI_LONG_PRESS_POWER_OFF_ENABLED = False\r
-\r
-# 是否启用 SOS:False 时禁用 AI 键长按 3s 拨紧急号、以及按键板上的 SOS 长按拨号入口\r
-SOS_ENABLED = False\r
-\r
-# VC WebSocket 文本保活 ping 间隔(秒)。原逻辑约每 4 秒一次,空闲功耗偏高;拉大间隔可降低唤醒与发包频率\r
-VC_WS_PING_INTERVAL_SEC = 55\r
-\r
-# --------------------\r
-# VC 空闲/超时(ai_VC._1S_timer_cb_ 每秒累计)\r
-# --------------------\r
-# 长对话:无有效业务下行累计达此秒数后结束会话;代码侧最小 5\r
-VC_IDLE_AUTO_CLOSE_SEC = 20\r
-# 单对话侧预留(当前与 idle+深睡统一处理,与 7PIN_1_5 对齐)\r
-VC_IDLE_AUTO_CLOSE_SEC_PTT = 0\r
-# 业务 idle 累计达此秒数触发深睡断开;代码侧最小 10;0 关闭请改 ENABLE\r
-VC_IDLE_DEEP_SLEEP_SEC = 60\r
-VC_IDLE_DEEP_SLEEP_ENABLE = 1\r
-\r
-# --------------------\r
-# 开机提示/语音参数默认值(与具体产品绑定,编译期决定)\r
-# --------------------\r
-\r
-# 若为 None 或 POWER_ON_TONE_ENABLED=False,则不开机播报提示词\r
-POWER_ON_TONE_ENABLED = True\r
-POWER_ON_TONE_DEFAULT = "你好,我来了"\r
-\r
-# VAD 静音阈值默认值(ms)\r
-VAD_SILENCE_DEFAULT = 300\r
-\r
-# 默认音色配置(与 ai_VC.py 中格式保持一致)\r
-VC_TTS_VOICE_TYPE_DEFAULT = None  # dict 或 None(None 表示沿用 ai_VC.py 内置兜底/存储值)\r
-VC_TTS_VOICE_KEY_DEFAULT = None   # 例如 "男"/"童"\r
-\r
-# VC WebSocket 路径(ws://{AMP_SERVER}/{VC_WS_PATH}...)\r
-VC_WS_PATH = "vc/agent"\r
-\r
-# 是否支持蓝牙 OTA(原先仅 7PIN_1_5 会检查 bt_ota)\r
-BT_OTA_ENABLED = False\r
-\r
-# 是否在“开始上传语音”时播放成功提示音(原逻辑:非 7PIN/7PIN_1_5/7PIN_coze 才播)\r
-UPLOAD_START_TONE_ENABLED = False\r
-\r
-# 正常待机 idle 灯效:off=灭灯(REQ-BAT-01);green_blink=旧工程绿灯慢闪\r
-LED_IDLE_MODE = "off"\r
-\r
-# LLM thinking 阶段 LED 策略: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
-# 本地指令:切换对话模式时的提示语(是什么语言就写什么)\r
-CMD_TOGGLE_MODE_TO_PTT_TTS = "已切换到单对话模式"\r
-CMD_TOGGLE_MODE_TO_LONG_TTS = "已切换到长对话模式"\r
-\r
-# 蓝牙/语音留言类本地指令是否启用(原先仅 7PIN_1_5)\r
-BT_VOICE_FEATURES_ENABLED = False\r
-\r
-# “关机”语音指令是否仅关闭对话(不触发 Power_off)。原逻辑:7PIN/7PIN_coze/7PIN_1_5 只 Close\r
-POWEROFF_CMD_CLOSE_ONLY = True\r
-\r
-# 关键词唤醒时在 PTT 模式下播报的提示语\r
-KWS_PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "\r
-\r
-# 对话智能体/工作流默认配置(编译期确定;可在 usr/default_config.py 覆盖)\r
-# 结构示例:\r
-# DIALOG_BOT_OPTION = {"bot_id":"...", "workflow_id":"...", "use_workflow":True}\r
-DIALOG_BOT_OPTION = None\r
-\r
-# VC Start 请求里 dialog_attributes.cfg.bot_option(与 DIALOG_BOT_OPTION 二选一或叠加:后者可再覆盖)\r
-# 允许空字符串 "";未定义键时沿用下方默认值;若两者都为空则自动 use_workflow=False\r
-VC_BOT_ID = "7462617881076351002"\r
-VC_WORKFLOW_ID = "7462621757327196187"\r
-\r
-# dialog_attributes.provider 覆盖(None 表示沿用存储配置 dialog_attributes_provider)\r
-DIALOG_PROVIDER_DEFAULT = None\r
-\r
-# 是否从 VC_TTS_voice_type 的 key 列表生成 ASR 热词\r
-KEYWORDS_FROM_VOICE_TYPE = False\r
-\r
-# --------------------\r
-# 按键音/提示音策略(设备差异项)\r
-# --------------------\r
-\r
-# AI 键按下时:若设置了该文件路径,则在“无 VC 流播放中”时播放该提示音(例如 "U:/static/start.mp3")\r
-KEY_PRESS_TONE_FILE = None\r
-\r
-# AI 键按下时:延迟播放按键音(ms),规避蓝牙刚连上不出声等(与 7PIN_1_5 一致)\r
-KEY_PRESS_TONE_DELAY_MS = 300\r
-\r
-# 单对话长按开麦:开录提示音(None=不播,流畅优先;可设 U:/static/start.mp3)\r
-RECORD_START_TONE_FILE = None\r
-RECORD_START_TONE_GAP_MS = 180\r
-# 单对话松手后延迟(ms)再发 audio.completed,利于尾包/ASR 收敛\r
-PTT_RELEASE_END_SPEECH_DELAY_MS = 200\r
-# 单对话打断:发 cancel 后等待 canceled 的最长时间(ms),超时仍开录\r
-PTT_CANCEL_WAIT_MS = 2500\r
-\r
-# PTT 提示语(单对话模式,未长按时的提示;是什么语言就写什么)\r
-PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "\r
-\r
+# 设备侧默认配置(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%)
+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 = "请您按住圆形的按钮开始讲话,讲完松开即可 "
+
index 243f326..bc5c56a 100644 (file)
-# 设备侧默认配置(freeze 内置)。\r
-#\r
-# 加载优先级(由 vc_config.py 执行):\r
-# 1) usr/default_config.py(或 usr/defualt_config.py)\r
-# 2) 本文件 default_config.py\r
-\r
-# 当前设备类型(编译期间确定;现场仅可用 usr/default_config.py 覆盖)\r
-DEV_TYPE = "7PIN"\r
-\r
-# 是否加载 quecgnss 并在定位管理器初始化时调用 gnssEnable(0)。\r
-# 无 GNSS 硬件的机型(如部分吊坠)可置 False,避免 import quecgnss 或底层驱动异常/阻塞。\r
-GNSS_ENABLED = False\r
-\r
-# 为 True 时,protocol 模块 import 过程会打印 [proto.import] 阶段标记(配合 qp 日志口排查卡点)。\r
-PROTOCOL_IMPORT_TRACE = True\r
-\r
-# 当前设备配置(仅保留“设备差异项”,不要放运行态变量)\r
-# led_gpio 使用 Pin 属性名(字符串),由 vc_config.py 转成 Pin.GPIOxx 常量。\r
-LED_GPIO = ("GPIO36", "GPIO44", "GPIO43")  # (R, G, B)\r
-\r
-# LED 电平极性(是否“反向”点亮)。\r
-# - 默认 LED_ACTIVE_LOW=False:高电平点亮(write(1)=亮)\r
-# - 若你的硬件是低电平点亮(write(0)=亮),将 LED_ACTIVE_LOW=True\r
-# - 若仅某一路反相,用 LED_ACTIVE_LOW_RGB 单独配置(按 LED_GPIO 顺序 R/G/B)\r
-LED_ACTIVE_LOW = False\r
-LED_ACTIVE_LOW_RGB = None  # 例如 (True, False, False) 表示仅红灯反相\r
-# LED_ACTIVE_LOW_RGB = (True, False, False)\r
-\r
-# idle 绿灯长慢闪:常亮 / 熄灭(ms);仅配 LED_IDLE_GREEN_BLINK_MS 时灭灯时长与常亮相同(对称慢闪)\r
-LED_IDLE_GREEN_BLINK_MS = 1000\r
-LED_IDLE_GREEN_BLINK_OFF_MS = 2000\r
-\r
-# 是否支持 UART 相关能力(例如 7PIN_1_5 才允许走串口)\r
-UART_SUPPORTED = False\r
-\r
-# 语言/地区(如 zh/en),用于少量文案/逻辑差异\r
-LANG = "zh"\r
-\r
-# --------------------\r
-# 硬件差异项(由每个设备的 default_config 固定)\r
-# --------------------\r
-LED_DEBUG = True\r
-LED_STATE_DEBUG = True\r
-KEY_LED_DEBUG = True\r
-HW_STATUS_DEBUG = True\r
-\r
-# --------------------\r
-# 电池 OCV / 硬件状态轮询(AI_hw_status,可在 usr/default_config.py 覆盖)\r
-# --------------------\r
-# True 时用三档电压门限估算 SoC,忽略 BATTERY_OCV_MAP\r
-USE_SIMPLE_VBATT_THRESH = False\r
-VBATT_CHARGE_FULL_MV = 4290   # USB 在位且电压≥此值视为充满(get_battery / AI_hw_status / AI_dev 共用)\r
-VBATT_LOW_MV = 3699           # 低电电压门限;simple 模式与 AI_dev 低电提示用\r
-VBATT_EMPTY_MV = 3629         # 空电电压门限;AI_dev 低电关机等\r
-BATTERY_LOW_SOC_PERCENT = 30  # OCV 模式:soc < 此值 → battery=low(REQ-BAT-02:10%~30%)\r
-BATTERY_EMPTY_SOC_PERCENT = 10  # OCV 模式:soc < 此值 → battery=empty(REQ-BAT-03:<10%)\r
-HW_STATUS_PERIOD_MS = 10000\r
-SIGNAL_WEAK_CSQ = 10\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
-# 是否需要初始化蓝牙电源/马达/音频IIS配置等(原先仅 7PIN_1_5)\r
-BT_POWER_MOTOR_ENABLED = False\r
-PIN_BT_ON_GPIO = None      # 例如 "GPIO31"\r
-PIN_MOT_GPIO = None\r
-# audio.set_qdai_cfg(...) 参数;若为 None 则不调用\r
-AUDIO_QDAI_CFG = None      # 例如 (1,0,0,2,0,1,1,1)\r
-\r
-# motor.py(motorEventManager):马达输出脚与节拍。MOTOR_GPIO 为 None 时依次尝试 PIN_MOT_GPIO、默认 GPIO36。\r
-MOTOR_PIN_GPIO = "GPIO20"          # 例如 "GPIO36";无马达硬件可保持 None\r
-MOTOR_TICK_MS = 100\r
-MOTOR_ACTIVE_LOW = False\r
-MOTOR_BOOT_VIBRATE_MS = 200 # 马达开机震动时间 200ms\r
-\r
-\r
-# AI 按键中断与电源键中断引脚(ExtInt.GPIOxx)\r
-EXTINT_AI_GPIO = "GPIO22"   # ai按键\r
-PWK_KEY_GPIO = "GPIO26"     # 侧边power按键 , 模式切换\r
-\r
-#btn_device_ec800m.py\r
-BTN_WECHAT_GPIO = None\r
-BTN_SOS_GPIO = None\r
-\r
-\r
-# 蓝牙唤醒 4G/4G 唤醒蓝牙相关(原先仅 7PIN_1_5)\r
-BT_WAKEUP_4G_ENABLED = False\r
-EXTINT_WAKEUP_CTA1_GPIO = None  # 例如 "GPIO22"\r
-PIN_WAKEUP_BT_GPIO = None       # 例如 "GPIO21"\r
-\r
-# --------------------\r
-# 按键/防抖差异\r
-# --------------------\r
-\r
-# 释放防抖阈值(KeyAI/pwk):原代码部分设备用 3,其他用 5\r
-KEY_RELEASE_DEBOUNCE_COUNT = 5\r
-\r
-# 是否允许 AI 键长按 8 秒触发关机逻辑(原 jswm/jswm_BOX)\r
-AI_LONG_PRESS_POWER_OFF_ENABLED = False\r
-\r
-# 是否启用 SOS:False 时禁用 AI 键长按 3s 拨紧急号、以及按键板上的 SOS 长按拨号入口\r
-SOS_ENABLED = False\r
-\r
-# VC WebSocket 文本保活 ping 间隔(秒)。原逻辑约每 4 秒一次,空闲功耗偏高;拉大间隔可降低唤醒与发包频率\r
-VC_WS_PING_INTERVAL_SEC = 55\r
-\r
-# --------------------\r
-# VC 空闲/超时(ai_VC._1S_timer_cb_ 每秒累计)\r
-# --------------------\r
-# 长对话:无有效业务下行累计达此秒数后结束会话;代码侧最小 5\r
-VC_IDLE_AUTO_CLOSE_SEC = 20\r
-# 单对话侧预留(当前与 idle+深睡统一处理,与 7PIN_1_5 对齐)\r
-VC_IDLE_AUTO_CLOSE_SEC_PTT = 0\r
-# 业务 idle 累计达此秒数触发深睡断开;代码侧最小 10;0 关闭请改 ENABLE\r
-VC_IDLE_DEEP_SLEEP_SEC = 60\r
-VC_IDLE_DEEP_SLEEP_ENABLE = 1\r
-\r
-# --------------------\r
-# 开机提示/语音参数默认值(与具体产品绑定,编译期决定)\r
-# --------------------\r
-\r
-# 若为 None 或 POWER_ON_TONE_ENABLED=False,则不开机播报提示词\r
-POWER_ON_TONE_ENABLED = True\r
-POWER_ON_TONE_DEFAULT = "你好,我来了"\r
-\r
-# VAD 静音阈值默认值(ms)\r
-VAD_SILENCE_DEFAULT = 300\r
-\r
-# 默认音色配置(与 ai_VC.py 中格式保持一致)\r
-VC_TTS_VOICE_TYPE_DEFAULT = None  # dict 或 None(None 表示沿用 ai_VC.py 内置兜底/存储值)\r
-VC_TTS_VOICE_KEY_DEFAULT = None   # 例如 "男"/"童"\r
-\r
-# VC WebSocket 路径(ws://{AMP_SERVER}/{VC_WS_PATH}...)\r
-VC_WS_PATH = "vc/agent"\r
-\r
-# 是否支持蓝牙 OTA(原先仅 7PIN_1_5 会检查 bt_ota)\r
-BT_OTA_ENABLED = False\r
-\r
-# 是否在“开始上传语音”时播放成功提示音(原逻辑:非 7PIN/7PIN_1_5/7PIN_coze 才播)\r
-UPLOAD_START_TONE_ENABLED = False\r
-\r
-# 正常待机 idle 灯效:off=灭灯(REQ-BAT-01);green_blink=旧工程绿灯慢闪\r
-LED_IDLE_MODE = "off"\r
-\r
-# LLM thinking 阶段 LED 策略: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
-# 本地指令:切换对话模式时的提示语(是什么语言就写什么)\r
-CMD_TOGGLE_MODE_TO_PTT_TTS = "已切换到单对话模式"\r
-CMD_TOGGLE_MODE_TO_LONG_TTS = "已切换到长对话模式"\r
-\r
-# 蓝牙/语音留言类本地指令是否启用(原先仅 7PIN_1_5)\r
-BT_VOICE_FEATURES_ENABLED = False\r
-\r
-# “关机”语音指令是否仅关闭对话(不触发 Power_off)。原逻辑:7PIN/7PIN_coze/7PIN_1_5 只 Close\r
-POWEROFF_CMD_CLOSE_ONLY = True\r
-\r
-# 关键词唤醒时在 PTT 模式下播报的提示语\r
-KWS_PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "\r
-\r
-# 对话智能体/工作流默认配置(编译期确定;可在 usr/default_config.py 覆盖)\r
-# 结构示例:\r
-# DIALOG_BOT_OPTION = {"bot_id":"...", "workflow_id":"...", "use_workflow":True}\r
-DIALOG_BOT_OPTION = None\r
-\r
-# VC Start 请求里 dialog_attributes.cfg.bot_option(与 DIALOG_BOT_OPTION 二选一或叠加:后者可再覆盖)\r
-# 允许空字符串 "";未定义键时沿用下方默认值;若两者都为空则自动 use_workflow=False\r
-VC_BOT_ID = "7462617881076351002"\r
-VC_WORKFLOW_ID = "7462621757327196187"\r
-\r
-# dialog_attributes.provider 覆盖(None 表示沿用存储配置 dialog_attributes_provider)\r
-DIALOG_PROVIDER_DEFAULT = None\r
-\r
-# 是否从 VC_TTS_voice_type 的 key 列表生成 ASR 热词\r
-KEYWORDS_FROM_VOICE_TYPE = False\r
-\r
-# --------------------\r
-# 按键音/提示音策略(设备差异项)\r
-# --------------------\r
-\r
-# AI 键按下时:若设置了该文件路径,则在“无 VC 流播放中”时播放该提示音(例如 "U:/static/start.mp3")\r
-KEY_PRESS_TONE_FILE = None\r
-\r
-# AI 键按下时:延迟播放按键音(ms),规避蓝牙刚连上不出声等(与 7PIN_1_5 一致)\r
-KEY_PRESS_TONE_DELAY_MS = 300\r
-\r
-# 单对话长按开麦:开录提示音(None=不播,流畅优先;可设 U:/static/start.mp3)\r
-RECORD_START_TONE_FILE = None\r
-RECORD_START_TONE_GAP_MS = 180\r
-# 单对话松手后延迟(ms)再发 audio.completed,利于尾包/ASR 收敛\r
-PTT_RELEASE_END_SPEECH_DELAY_MS = 200\r
-# 单对话打断:发 cancel 后等待 canceled 的最长时间(ms),超时仍开录\r
-PTT_CANCEL_WAIT_MS = 2500\r
-\r
-# PTT 提示语(单对话模式,未长按时的提示;是什么语言就写什么)\r
-PTT_HINT = "请您按住圆形的按钮开始讲话,讲完松开即可 "\r
-\r
+# 设备侧默认配置(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%)
+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 = "请您按住圆形的按钮开始讲话,讲完松开即可 "
+