except Exception:
return False
def _vc_is_business_idle(self):
- # Level2:LED ai_state 为 idle,且无录音/流播/音乐闹钟等(与 WS ping 无关)
+ # Level2:LED ai_state 为 idle,且无录音/流播/静默录音/音乐闹钟等
+ if self._vc_session_background_active():
+ return False
try:
if getattr(self, "music_play_flag", 0) == 1:
return False
self.Close()
except Exception:
pass
+ try:
+ self._vc_shutdown_idle_services("idle_L2")
+ except Exception:
+ pass
+ try:
+ if not self._vc_is_business_idle():
+ try:
+ print("[VC][idle_L2] 仍有后台活跃,推迟 SYSTEM_SLEEP")
+ except Exception:
+ pass
+ try:
+ self._vc_idle_deep_armed_suppress = 0
+ except Exception:
+ pass
+ return
+ except Exception:
+ pass
try:
utime.sleep_ms(100)
except Exception:
except Exception:
pass
def _vc_rx_resets_long_idle_timer(self, recv_data):
- """是否应因本帧下行清零 close_timerout(排除保活/心跳,含 V2.5 文本 ping 对端应答)。"""
+ """是否清零 close_timerout:仅 TTS 反馈(tts.* / started / 音频字节),asr/vad/llm 不计。"""
try:
if recv_data is None:
return False
try:
jd = ujson.loads(t)
except Exception:
- return True
+ return False
ev = jd.get("event")
if ev is None or ev == "":
ev = jd.get("type")
if ev is None:
+ return False
+ evl = str(ev).strip().lower()
+ if evl in _VC_KEEPALIVE_RX_EVENTS:
+ return False
+ if evl == "started" or evl.startswith("tts."):
return True
- if str(ev).strip().lower() in _VC_KEEPALIVE_RX_EVENTS:
+ return False
+ except Exception:
+ return False
+ def _vc_long_idle_timer_armed(self):
+ """长对话 L1:会话已 started 且已连接时才开始计 20s 无 TTS。"""
+ try:
+ if getattr(self, "VC_mode", -1) != 0:
+ return False
+ if getattr(self, "VC_satrt_flag", 0) != 1:
+ return False
+ if getattr(self, "AI_client_VC_isconnect", 0) != 1:
+ return False
+ if getattr(self, "_vc_session_started", 0) != 1:
return False
return True
except Exception:
- return True
+ return False
+ def _vc_session_background_active(self):
+ """是否仍有 AI 会话/采播/静默录音等需保持活跃的后台。"""
+ try:
+ if getattr(self, "VC_satrt_flag", 0) == 1:
+ return True
+ if getattr(self, "AI_client_VC_isconnect", 0) == 1:
+ return True
+ if getattr(self, "VC_record_state_flag", 0) != 0:
+ return True
+ if getattr(self, "VC_Play_flag", 0) != 0:
+ return True
+ if getattr(self, "music_play_flag", 0) == 1:
+ return True
+ if getattr(self, "alarm_play_flag", 0) == 1:
+ return True
+ if EventMesh.publish("get_voice_record_flag") == 1:
+ return True
+ except Exception:
+ pass
+ return False
+ def _vc_shutdown_idle_services(self, reason="idle"):
+ """业务 idle/深睡前关停后台服务(静默录音、TTS 播、VC 链路等)。"""
+ try:
+ print("[VC][idle_shutdown] reason={} VC_satrt={} ws={} rec={} play={} voice_rec={}".format(
+ reason,
+ getattr(self, "VC_satrt_flag", -1),
+ getattr(self, "AI_client_VC_isconnect", -1),
+ getattr(self, "VC_record_state_flag", -1),
+ getattr(self, "VC_Play_flag", -1),
+ EventMesh.publish("get_voice_record_flag"),
+ ))
+ except Exception:
+ pass
+ try:
+ EventMesh.publish("stop_voice_record")
+ except Exception:
+ pass
+ try:
+ self.VC_stop_play_audio_flag = 0
+ except Exception:
+ pass
+ try:
+ self.VC_Play_End()
+ except Exception:
+ pass
+ try:
+ if getattr(self, "aud", None) is not None:
+ self.aud.stopPlayStream()
+ self.aud.stopAll()
+ except Exception:
+ pass
+ try:
+ if getattr(self, "VC_record_state_flag", 0) != 0:
+ self._uplink_request_stop(reason)
+ except Exception:
+ pass
+ try:
+ if getattr(self, "AI_client_VC_isconnect", 0) == 1:
+ self.websocket_deconnect_VC()
+ except Exception:
+ pass
+ try:
+ EventMesh.publish("sleep_BT")
+ except Exception:
+ pass
+ try:
+ self.VC_pingping_timerout = 0
+ self.VC_satrt_kws_flag = 0
+ except Exception:
+ pass
+ def _vc_prepare_for_idle_close(self):
+ """结束对话进 idle:先停麦/清 FSM 聆听锁,避免 Close 后仍录音或 listen_hold 高电流。"""
+ try:
+ self.Close_flag = 1
+ except Exception:
+ pass
+ try:
+ self._vc_pending_interrupt_uplink = 0
+ self.is_ruppt_tts = 0
+ self._drop_tts_audio = 0
+ except Exception:
+ pass
+ try:
+ if getattr(self, "VC_record_state_flag", 0) != 0:
+ self._uplink_request_stop("idle_close_prepare")
+ except Exception:
+ pass
+ try:
+ if hasattr(self, "fsm") and self.fsm is not None:
+ self.fsm.speech_uplink_open = 0
+ self.fsm.turn = VCTurnState.Idle
+ self.fsm.await_played = False
+ except Exception:
+ pass
def _handle_pwk_short_release(self, event=None, msg=None, merged_ai_tail=False):
"""电源键短按:读留言标志、退会议/翻译;否则 Close,或单键模式下走 AI_key_release。"""
print("pwk_key_release")
return False
def get_vc_hold_listening_led(self, event=None, msg=None):
+ try:
+ if getattr(self, "_vc_idle_close_in_progress", 0) == 1:
+ return 0
+ except Exception:
+ pass
try:
return 1 if self._vc_should_hold_listening_led() else 0
except Exception:
if self._ai_key_press_long_dialogue_session():
return True
if self._ai_key_press_long_dialogue_wake():
- EventMesh.publish(constants.EVENT.SYSTEM_UP)
+ EventMesh.publish(constants.EVENT.SYSTEM_UP)#唤醒休眠
return True
print("[KEY] AI_key_press fallback -> AI_VC_start ws={} started={}".format(
getattr(self, "AI_client_VC_isconnect", -1),
except Exception:
pass
try:
- if self._vc_should_hold_listening_led():
+ if (
+ getattr(self, "_vc_idle_close_in_progress", 0) == 0
+ and self._vc_should_hold_listening_led()
+ ):
if ai_state != "listening":
if self._vc_led_state_debug_enabled():
print("[LED] publish coerce ai={} -> listening reason={} (listen_hold)".format(
self.close_timerout = 0
self._idle_deep_sec = 0
self._vc_idle_deep_armed_suppress = 0
+ self._vc_idle_close_in_progress = 0
+ self._vc_idle_l1_close = 0
self.LocalRespondingEnded_timerout = 0
self.VC_Play_flag = 0
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 等)
- # 与浅休眠/灭屏解耦;单对话不走本计时
+ _bg_active = False
+ try:
+ _bg_active = self._vc_session_background_active()
+ except Exception:
+ pass
+ if _bg_active:
+ EventMesh.publish("system_sleep_rest")
+ # --- Level 1:仅长对话,idle_sec 内无 TTS 下行 → Close 进 idle(需再按 AI 进入)
+ # close_timerout:仅 tts.* / started / TTS 音频重置;见 _vc_rx_resets_long_idle_timer
idle_sec = 20
try:
if "vc_config" in globals() and vc_config:
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:
+ if self._vc_long_idle_timer_armed():
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(
+ print("[VC][idle_L1] 长对话无TTS反馈超时: 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")#休眠蓝牙
+ try:
+ print("[VC][idle_L1] 处理=Close(无TTS反馈进idle,需再次按键进入)")
+ except Exception:
+ pass
+ print("[VC][long_idle] branch=no_tts_timeout -> Close")
+ try:
+ self._vc_idle_l1_close = 1
+ except Exception:
+ pass
+ EventMesh.publish("publish_AI_btnInfo", {"func": self.Close})
# --- Level 2:长对话+单对话,业务 idle 累计 VC_IDLE_DEEP_SLEEP_SEC → 断开(若仍连)+ 深睡事件
deep_sec = 60
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 驱动自动开麦
+ if _bg_active:
+ # 统一兜底表(按状态机状态触发)
+ try:
+ self._fsm_timeout_table_tick(utime.ticks_ms())
+ except Exception:
+ pass
- # print("get_audio_state !!!!!{} {} {}\r\n".format(self.play_end,EventMesh.publish("get_audio_state"),self.play_flag))
- # ===== 播放结束检测:用 FSM 驱动 play_end 动作链 =====
- try:
- self._vc_try_play_end_tick()
- except Exception:
- pass
- if self.online_kws == 1:
- if self.VC_satrt_kws_flag == 1 and self.VC_satrt_flag == 0:#要打开录音上传数据实现kws
+ # ===== 播放结束检测:用 FSM 驱动 play_end 动作链 =====
+ try:
+ self._vc_try_play_end_tick()
+ except Exception:
pass
+ if self.online_kws == 1:
+ if self.VC_satrt_kws_flag == 1 and self.VC_satrt_flag == 0:
+ 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()
+ # VC WebSocket 文本 ping
+ try:
+ _ping_iv = int(vc_config.get_int("VC_WS_PING_INTERVAL_SEC", 4))
+ except Exception:
+ _ping_iv = 4
+ if _ping_iv > 0 and getattr(self, "AI_client_VC_isconnect", 0) == 1:
+ 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
def Close_VC(self,event,msg):
EventMesh.publish("publish_AI_btnInfo",{"func":self.Close})
def Close(self):
+ try:
+ self._vc_idle_close_in_progress = 1
+ except Exception:
+ pass
+ try:
+ if getattr(self, "VC_mode", 0) == 0:
+ self._vc_prepare_for_idle_close()
+ except Exception:
+ pass
try:
if getattr(self, "VC_mode", 0) != 1:
print("[VC][long_idle] Close() enter VC_mode={} {} VC_satrt_flag={} VC_record_state_flag={}".format(
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"})
+ try:
+ self._uplink_request_stop("Close")
+ except Exception as e:
+ usys.print_exception(e)
print("ai_VC Close1.1!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
EventMesh.publish("set_led_state", {"ai_state": "idle"})
self.VC_init()
self.VC_satrt_flag = 0
+ try:
+ self._vc_shutdown_idle_services("Close")
+ except Exception:
+ pass
# Level2:会话结束后仍可继续 1s 节拍累计 idle 直至深睡(依赖 VC_IDLE_DEEP_SLEEP_SEC)
try:
_dsec = 60
self._1S_timer.start(1000, 1, self._1S_timer_cb)
except Exception:
pass
+ try:
+ self._vc_idle_close_in_progress = 0
+ self._vc_idle_l1_close = 0
+ except Exception:
+ pass
def VC_init2(self):
self.VC_init()