# =============================================================================
HW_STATUS_PERIOD_MS = 10000
+# 电池电压:全机约每 10s 采样 1 次 getVbatt,10 点滑动平均(见 vc_hw._BatteryCfg)
+VBATT_AVG_LEN = 10
+VBATT_PUSH_MIN_MS = 9000 # 同周期内 AI_hw / protocol 去重,避免重复读电压
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
+# SoC 门限:仅低电提示/LED 空电闪(REQ-BAT-02/03);关机见 VBATT_EMPTY_MV
+BATTERY_LOW_SOC_PERCENT = 25 # <25% 低电语音
+BATTERY_EMPTY_SOC_PERCENT = 5 # <5% LED 空电态;不单独触发关机
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
+VBATT_CHARGE_FULL_MV = 4320 # 502631 ZCV 100% OCV≈4.322V;CV 4.35V
+VBATT_LOW_MV = 3630 # 约对应 ZCV 20%(4G 带载下不宜再抬高)
+VBATT_EMPTY_MV = 3400 # 未充电:滑动平均电压 < 此值 → 低电关机(唯一关机条件)
-# 开路电压(mV)→SoC(%) 插值表;None 则用 ai_VC 内置默认表
+# 502631-510 4.35V ZCV 放电 OCV 表(mV→SoC);0% 锚定 3400 而非实验室 3262
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,
+ 4322: 100, 4239: 95, 4177: 90, 4117: 85, 4059: 80, 4006: 75, 3942: 70, 3898: 65,
+ 3853: 60, 3814: 55, 3781: 50, 3755: 45, 3738: 40, 3724: 35, 3706: 30, 3678: 25,
+ 3634: 20, 3577: 15, 3522: 10, 3468: 5, 3400: 0,
}
# =============================================================================
# --------------------
# 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 # 空ç\94µç\94µå\8e\8bé\97¨é\99\90ï¼\9bAI_dev ä½\8eç\94µå\85³æ\9cºç\89
-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%)
+VBATT_CHARGE_FULL_MV = 4320 # 502631 满电 OCV≈4.322V
+VBATT_LOW_MV = 3630 # 约 ZCV 20%
+VBATT_EMPTY_MV = 3400 # æ\9cªå\85\85ç\94µï¼\9aå¹³å\9d\87ç\94µå\8e\8b < 3.40V â\86\92 ä½\8eç\94µå\85³æ\9cºï¼\88å\94¯ä¸\80å\85³æ\9cºæ\9d¡ä»¶ï¼\89
+BATTERY_LOW_SOC_PERCENT = 25 # 未充电:soc<25% 低电语音
+BATTERY_EMPTY_SOC_PERCENT = 5 # 未充电:soc<5% LED 空电闪;不触发关机
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
+VBATT_AVG_LEN = 10
+VBATT_PUSH_MIN_MS = 9000
SIGNAL_WEAK_CSQ = 10
-# 开路电压(mV) -> SoC(%),按电压线性插值;None 则用 ai_VC 内置默认表
+# 502631 ZCV OCV 表;0% 锚定 3400mV(实机 4G 场景)
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,
+ 4322: 100, 4239: 95, 4177: 90, 4117: 85, 4059: 80, 4006: 75, 3942: 70, 3898: 65,
+ 3853: 60, 3814: 55, 3781: 50, 3755: 45, 3738: 40, 3724: 35, 3706: 30, 3678: 25,
+ 3634: 20, 3577: 15, 3522: 10, 3468: 5, 3400: 0,
}
# 是否需要初始化蓝牙电源/马达/音频IIS配置等(原先仅 7PIN_1_5)
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_SOC_PERCENT = 25\r
+BATTERY_EMPTY_SOC_PERCENT = 5\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
+VBATT_CHARGE_FULL_MV = 4320\r
+VBATT_LOW_MV = 3630\r
+VBATT_EMPTY_MV = 3400\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
+ 4322: 100, 4239: 95, 4177: 90, 4117: 85, 4059: 80, 4006: 75, 3942: 70, 3898: 65,\r
+ 3853: 60, 3814: 55, 3781: 50, 3755: 45, 3738: 40, 3724: 35, 3706: 30, 3678: 25,\r
+ 3634: 20, 3577: 15, 3522: 10, 3468: 5, 3400: 0,\r
}\r
\r
# =============================================================================\r
# =============================================================================
HW_STATUS_PERIOD_MS = 10000
+# 电池电压:全机约每 10s 采样 1 次 getVbatt,10 点滑动平均(见 vc_hw._BatteryCfg)
+VBATT_AVG_LEN = 10
+VBATT_PUSH_MIN_MS = 9000 # 同周期内 AI_hw / protocol 去重,避免重复读电压
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
+# SoC 门限:仅低电提示/LED 空电闪(REQ-BAT-02/03);关机见 VBATT_EMPTY_MV
+BATTERY_LOW_SOC_PERCENT = 25 # <25% 低电语音
+BATTERY_EMPTY_SOC_PERCENT = 5 # <5% LED 空电态;不单独触发关机
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
+VBATT_CHARGE_FULL_MV = 4320 # 502631 ZCV 100% OCV≈4.322V;CV 4.35V
+VBATT_LOW_MV = 3630 # 约对应 ZCV 20%(4G 带载下不宜再抬高)
+VBATT_EMPTY_MV = 3400 # 未充电:滑动平均电压 < 此值 → 低电关机(唯一关机条件)
-# 开路电压(mV)→SoC(%) 插值表;None 则用 ai_VC 内置默认表
+# 502631-510 4.35V ZCV 放电 OCV 表(mV→SoC);0% 锚定 3400 而非实验室 3262
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,
+ 4322: 100, 4239: 95, 4177: 90, 4117: 85, 4059: 80, 4006: 75, 3942: 70, 3898: 65,
+ 3853: 60, 3814: 55, 3781: 50, 3755: 45, 3738: 40, 3724: 35, 3706: 30, 3678: 25,
+ 3634: 20, 3577: 15, 3522: 10, 3468: 5, 3400: 0,
}
# =============================================================================
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_SOC_PERCENT = 25
+BATTERY_EMPTY_SOC_PERCENT = 5
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
+VBATT_CHARGE_FULL_MV = 4320
+VBATT_LOW_MV = 3630
+VBATT_EMPTY_MV = 3400
-# 开路电压(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,
+ 4322: 100, 4239: 95, 4177: 90, 4117: 85, 4059: 80, 4006: 75, 3942: 70, 3898: 65,
+ 3853: 60, 3814: 55, 3781: 50, 3755: 45, 3738: 40, 3724: 35, 3706: 30, 3678: 25,
+ 3634: 20, 3577: 15, 3522: 10, 3468: 5, 3400: 0,
}
# =============================================================================
else:\r
charge_state = '0'\r
\r
- #temp = Power.getVbatt()\r
- temp = EventMesh.publish("get_curr_battery_level")\r
- vbat = EventMesh.publish("get_battery",temp)\r
- if vbat == None:\r
+ vbat = EventMesh.publish("get_battery")\r
+ if vbat is None:\r
vbat = 100\r
# EventMesh.debug_log("vbat",vbat)\r
# if temp != None :\r
self.public_report_event_cb()\r
#上报心跳 上报设备状态 检查闹钟 日程\r
def public_report_event_cb(self):\r
- """电池平均值"""\r
- temp = Power.getVbatt()\r
- if self.bat_count < 10:\r
- self.vbat_buf.append(temp)\r
- self.bat_count += 1\r
- else:\r
- for i in range(9):\r
- self.vbat_buf[i] = self.vbat_buf[i+1]\r
- self.vbat_buf[9] = temp\r
- curr_count = len(self.vbat_buf)\r
+ """电池平均值:与 vc_hw._BatteryCfg 共用 10 点滑动平均,避免重复 getVbatt。"""\r
bat_total_value = 0\r
- for i in range(curr_count):\r
- bat_total_value += self.vbat_buf[i]\r
- bat_total_value = bat_total_value//curr_count#一分钟更新一次电量 10个平均值\r
+ try:\r
+ from vc_hw import _BatteryCfg\r
+ bcfg = _BatteryCfg.instance()\r
+ try:\r
+ usb_st = EventMesh.publish("GET_USB_STATE")\r
+ except Exception:\r
+ usb_st = 0\r
+ bcfg.push_vbatt_sample(usb_status=usb_st)\r
+ bat_total_value = bcfg.soc_now()\r
+ except Exception:\r
+ bat_total_value = 0\r
# EventMesh.publish("AI_schedule_open")#读取闹钟数组\r
# print("AI_schedule_open!!!")\r
if EventMesh.publish("get_sos_flag") != None:\r
except Exception:\r
vc_config = None\r
from vc_common import dev_type\r
-from vc_hw import set_ipv4_datacall\r
+from vc_hw import set_ipv4_datacall, _BatteryCfg\r
from vc_uart import AI_UART\r
\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
EventMesh.publish("hw_usb_state", state)\r
except Exception:\r
pass\r
+ try:\r
+ _BatteryCfg.instance().on_usb_change(1 if state == 1 else 0)\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
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
+ _bcfg = _BatteryCfg.instance()\r
+ Vbatt = _bcfg.get_vbatt_mv_charge() if self.usb_status == 1 else _bcfg.get_vbatt_mv()\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
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
+ low_soc = 25\r
warn_interval_ms = 300000\r
try:\r
if "vc_config" in globals() and vc_config:\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 self.usb_status == 0:\r
+ if _bcfg.should_shutdown_empty(Vbatt):\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
+ print("[BAT] empty shutdown vbatt={} soc={} empty_mv={}".format(\r
+ Vbatt, soc, _bcfg.empty_mv))\r
except Exception:\r
pass\r
EventMesh.publish("publish_AI_btnInfo", {"func": self._battery_empty_shutdown_seq})\r
- elif soc < low_soc:\r
+ elif soc is not None and soc < low_soc:\r
if voice_record_satrt_flag == 0:\r
try:\r
now = utime.ticks_ms()\r
self.pwk_key_Long_Press()\r
\r
def get_battery(self, event=None, msg=None):\r
- # 返回 int 型 SoC(0~100):便于 TTS/上报/比较,且 OCV 插值本身比裸电压稳;不做小数避免频繁跳变\r
+ # SoC 基于 _BatteryCfg 10 点滑动平均电压,与 protocol 心跳/AI_hw 一致\r
try:\r
- Vbatt = Power.getVbatt()\r
+ return _BatteryCfg.instance().soc_now()\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
from vc_common import dev_type\r
\r
def _default_battery_ocv_map():\r
- # 与 mgr.py BATTERY_OCV_TABLE nix_coy_mnzo2 默认一致;可被 default_config.BATTERY_OCV_MAP 覆盖\r
+ # 无 BATTERY_OCV_MAP 配置时的兜底(502631 ZCV;0% 锚定 3400mV,适配 4G 带载)\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
+ 4322: 100, 4239: 95, 4177: 90, 4117: 85, 4059: 80, 4006: 75, 3942: 70, 3898: 65,\r
+ 3853: 60, 3814: 55, 3781: 50, 3755: 45, 3738: 40, 3724: 35, 3706: 30, 3678: 25,\r
+ 3634: 20, 3577: 15, 3522: 10, 3468: 5, 3400: 0,\r
}\r
\r
\r
\r
\r
class _BatteryCfg(object):\r
- """ä»\8e vc_config(default_config) å\8a è½½ç\94µæ± å\8f\82æ\95°ï¼\8cä¾\9b AI_hw_status / get_battery / AI_dev å\85±ç\94¨。"""\r
+ """ä»\8e vc_config(default_config) å\8a è½½ç\94µæ± å\8f\82æ\95°ï¼\9bå\85¨æ\9cºå\94¯ä¸\80 Power.getVbatt é\87\87æ ·ç\82¹ + 10 ç\82¹æ»\91å\8a¨å¹³å\9d\87。"""\r
_inst = None\r
\r
def __init__(self):\r
self.charge_full_mv = 4290\r
self.low_mv = 3699\r
self.empty_mv = 3629\r
- self.low_soc = 20\r
+ self.low_soc = 25\r
self.empty_soc = 5\r
self.vbatt_simple = None\r
+ self._vbat_buf = []\r
+ self._vbat_avg_mv = 0\r
+ self._vbat_chg_last_mv = 0\r
+ self._soc_frozen = None\r
+ self._usb_chg = 0\r
+ self._vbat_last_push_ms = 0\r
+ self._vbat_avg_len = 10\r
+ self._vbat_push_min_ms = 9000\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
+ self.charge_full_mv = int(vc_config.get_int("VBATT_CHARGE_FULL_MV", 4320))\r
+ self.low_mv = int(vc_config.get_int("VBATT_LOW_MV", 3630))\r
+ self.empty_mv = int(vc_config.get_int("VBATT_EMPTY_MV", 3400))\r
+ self.low_soc = int(vc_config.get_int("BATTERY_LOW_SOC_PERCENT", 25))\r
+ self.empty_soc = int(vc_config.get_int("BATTERY_EMPTY_SOC_PERCENT", 5))\r
+ self._vbat_avg_len = int(vc_config.get_int("VBATT_AVG_LEN", 10)) or 10\r
+ self._vbat_push_min_ms = int(vc_config.get_int("VBATT_PUSH_MIN_MS", 9000)) or 9000\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
cls._inst = cls()\r
return cls._inst\r
\r
+ def _push_vbatt_raw(self, raw_mv):\r
+ """与 protocol.py 一致:滑动窗口求算术平均(mV)。"""\r
+ try:\r
+ v = int(raw_mv)\r
+ except Exception:\r
+ return self._vbat_avg_mv\r
+ n = int(self._vbat_avg_len) or 10\r
+ buf = self._vbat_buf\r
+ if len(buf) < n:\r
+ buf.append(v)\r
+ else:\r
+ for i in range(n - 1):\r
+ buf[i] = buf[i + 1]\r
+ buf[n - 1] = v\r
+ total = 0\r
+ for x in buf:\r
+ total += x\r
+ self._vbat_avg_mv = total // len(buf)\r
+ return self._vbat_avg_mv\r
+\r
+ def _usb_is_charging(self):\r
+ try:\r
+ if self._usb_chg:\r
+ return 1\r
+ st = EventMesh.publish("GET_USB_STATE")\r
+ return 1 if st == 1 else 0\r
+ except Exception:\r
+ return 0\r
+\r
+ def on_usb_change(self, plugged_in):\r
+ """插拔 USB:充电电压偏高,与放电 OCV 不可混用;清空滑动窗并冻结/恢复 SoC。"""\r
+ try:\r
+ plug = 1 if plugged_in else 0\r
+ self._usb_chg = plug\r
+ if plug:\r
+ if self._vbat_buf and self._vbat_avg_mv > 0:\r
+ s = self.soc_from_vbatt(self._vbat_avg_mv)\r
+ self._soc_frozen = max(0, min(100, int(s) if s is not None else 0))\r
+ elif self._soc_frozen is None:\r
+ self._soc_frozen = 0\r
+ self._vbat_buf = []\r
+ self._vbat_avg_mv = 0\r
+ self._vbat_last_push_ms = 0\r
+ else:\r
+ self._vbat_buf = []\r
+ self._vbat_avg_mv = 0\r
+ self._vbat_chg_last_mv = 0\r
+ self._vbat_last_push_ms = 0\r
+ except Exception:\r
+ pass\r
+\r
+ def push_vbatt_sample(self, force=0, usb_status=None):\r
+ """采样一次 getVbatt:充电只记瞬时电压(判满);放电才进滑动平均(算 SoC/关机)。"""\r
+ try:\r
+ now = utime.ticks_ms()\r
+ chg = self._usb_chg if usb_status is None else (1 if usb_status == 1 else 0)\r
+ if not force and self._vbat_last_push_ms:\r
+ if utime.ticks_diff(now, self._vbat_last_push_ms) < int(self._vbat_push_min_ms):\r
+ if chg:\r
+ return self._vbat_chg_last_mv or self._vbat_avg_mv\r
+ return self._vbat_avg_mv\r
+ self._vbat_last_push_ms = now\r
+ raw = Power.getVbatt()\r
+ except Exception:\r
+ if self._usb_is_charging():\r
+ return self._vbat_chg_last_mv\r
+ return self._vbat_avg_mv\r
+ try:\r
+ chg = self._usb_chg if usb_status is None else (1 if usb_status == 1 else 0)\r
+ if chg:\r
+ self._vbat_chg_last_mv = int(raw)\r
+ return self._vbat_chg_last_mv\r
+ except Exception:\r
+ pass\r
+ return self._push_vbatt_raw(raw)\r
+\r
+ def get_vbatt_mv(self):\r
+ """放电路径滑动平均电压(mV);充电时不混用充电偏高电压。"""\r
+ if self._usb_is_charging():\r
+ return self._vbat_avg_mv if self._vbat_avg_mv > 0 else 0\r
+ if self._vbat_buf and self._vbat_avg_mv > 0:\r
+ return self._vbat_avg_mv\r
+ return self.push_vbatt_sample(force=1, usb_status=0)\r
+\r
+ def get_vbatt_mv_charge(self):\r
+ """充电时最近一次采样(判充满);未采样则触发一次。"""\r
+ if self._vbat_chg_last_mv > 0:\r
+ return self._vbat_chg_last_mv\r
+ return self.push_vbatt_sample(force=1, usb_status=1)\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
+ def soc_now(self):\r
+ """放电:OCV 表 + 滑动平均;充电:冻结插 USB 前的 SoC,避免充电电压算成 100%。"""\r
+ if self._usb_is_charging():\r
+ if self._soc_frozen is not None:\r
+ return int(self._soc_frozen)\r
+ return 100\r
+ v = self.get_vbatt_mv()\r
+ s = self.soc_from_vbatt(v)\r
+ if s is None:\r
+ return 0\r
+ if s < 0:\r
+ return 0\r
+ if s > 100:\r
+ return 100\r
+ return int(s)\r
+\r
+ def should_shutdown_empty(self, vbatt_mv=None):\r
+ """产品定义:仅未充电且平均电压 < VBATT_EMPTY_MV 时低电关机。"""\r
+ if self._usb_is_charging():\r
+ return False\r
+ try:\r
+ v = int(vbatt_mv if vbatt_mv is not None else self.get_vbatt_mv())\r
+ except Exception:\r
+ return False\r
+ return v > 0 and v < int(self.empty_mv)\r
+\r
+ def led_battery_level(self, soc=None, vbatt_mv=None):\r
+ """未充电时 LED 用电位/SoC;充电时由 charging 灯态接管,返回 normal。"""\r
+ if self._usb_is_charging():\r
+ return "normal"\r
+ try:\r
+ s = int(soc if soc is not None else self.soc_now())\r
+ except Exception:\r
+ s = -1\r
+ try:\r
+ v = int(vbatt_mv if vbatt_mv is not None else self.get_vbatt_mv())\r
+ except Exception:\r
+ v = 0\r
+ if v > 0 and v < int(self.empty_mv):\r
+ return "empty"\r
+ if s >= 0 and s < int(self.empty_soc):\r
+ return "empty"\r
+ if s >= 0 and s < int(self.low_soc):\r
+ return "low"\r
+ if v > 0 and v < int(self.low_mv):\r
+ return "low"\r
+ return "normal"\r
+\r
\r
class AI_hw_status(Abstract):\r
# 精简硬件状态采集:仅维护 LED 状态机所需的 charging/battery/signal\r
if self._usb == 0:\r
self._charge_full_latched = 0\r
self._charge_full_streak = 0\r
+ try:\r
+ self._bcfg.on_usb_change(self._usb)\r
+ except Exception:\r
+ pass\r
except Exception:\r
pass\r
try:\r
def _update(self):\r
# 每次刷新都“同步推送一次”当前硬件基础态到 LED(不依赖变化检测)\r
# 目的:保证 10 秒轮询和 USB callback 都能及时刷新充电/电量/信号的基础态显示\r
- # charging/battery\r
+ # charging/battery(本周期唯一 getVbatt;充电/放电分流)\r
try:\r
- v = Power.getVbatt()\r
+ usb_status = self._usb\r
+ if usb_status is None:\r
+ try:\r
+ usb_status = EventMesh.publish("GET_USB_STATE")\r
+ except Exception:\r
+ usb_status = 0\r
+ v = self._bcfg.push_vbatt_sample(usb_status=usb_status)\r
except Exception:\r
v = None\r
soc = None\r
usb_status = 0\r
except Exception:\r
usb_status = 0\r
- # charging:插入后先红灯;勿用瞬时 vbatt≥满电阈值直接判满(充电器接入电压即偏高)\r
+ # charging:用充电通道电压判满(勿用放电 OCV 平均)\r
if usb_status == 1:\r
+ v_chg = v\r
+ try:\r
+ v_chg = self._bcfg.get_vbatt_mv_charge()\r
+ except Exception:\r
+ pass\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
+ if v_chg is not None and v_chg >= 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
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
+ soc = None\r
+ try:\r
+ soc = self._bcfg.soc_now()\r
+ except Exception:\r
+ pass\r
+ bat = self._bcfg.led_battery_level(soc=soc, vbatt_mv=v if usb_status != 1 else None)\r
self._chg = chg\r
self._bat = bat\r
except Exception:\r
self.AI_TTS_str = "电量剩余百分之{}".format(battery)
return 0
if it == "query_voltage":
- Vbatt = Power.getVbatt()
+ try:
+ from vc_hw import _BatteryCfg
+ Vbatt = _BatteryCfg.instance().get_vbatt_mv()
+ except Exception:
+ Vbatt = Power.getVbatt()
self.AI_TTS_str = "当前电池电压{}".format(Vbatt)
return 0
if it == "query_iccid":