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", 20))
+ self.empty_soc = int(vc_config.get_int("BATTERY_EMPTY_SOC_PERCENT", 5))
+ _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):
print("[HW] __init__ enter")
except Exception:
pass
- # 参考 mgr.py 的 BATTERY_OCV_TABLE(nix_coy_mnzo2),按电压插值换算 SoC
- # 仅保留 LED 状态机所需:>=20 正常;<20 低电;<5 极低/空电
- self._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,
- }
- self._ocv_volts = None
- try:
- self._ocv_volts = sorted(self._ocv_map.keys(), reverse=True)
- except Exception:
- self._ocv_volts = None
+ _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._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
- # 允许覆盖 OCV 表(用电压门限的方式降级兼容)
- try:
- if vc_config.get_bool("USE_SIMPLE_VBATT_THRESH", False):
- cf = vc_config.get_int("VBATT_CHARGE_FULL_MV", 4300)
- v20 = vc_config.get_int("VBATT_LOW_MV", 3699)
- v5 = vc_config.get_int("VBATT_EMPTY_MV", 3629)
- self._vbatt_simple = (int(cf), int(v20), int(v5))
- else:
- self._vbatt_simple = None
- except Exception:
- self._vbatt_simple = None
except Exception:
pass
# 若开启了 LED/按键调试,也一并打开硬件监控打印,便于关联分析
usb_status = 0
# charging
if usb_status == 1:
- # 充满:默认用 4290mV 对齐 mgr OCV 100%,也允许用 simple 门限覆盖
- full_mv = 4290
- try:
- if getattr(self, "_vbatt_simple", None):
- full_mv = int(self._vbatt_simple[0])
- except Exception:
- full_mv = 4290
- if v is not None and v >= full_mv:
+ if v is not None and v >= int(self._charge_full_mv):
chg = "full"
else:
chg = "charging"
# battery level for LED table
if usb_status != 1 and v is not None:
- soc = self._soc_from_vbatt(v)
+ soc = self._bcfg.soc_from_vbatt(v)
if soc is not None:
- if soc < 5:
+ if soc < int(self._empty_soc):
bat = "empty"
- elif soc < 20:
+ elif soc < int(self._low_soc):
bat = "low"
self._chg = chg
self._bat = bat
pass
def _soc_from_vbatt(self, volt_mv):
- # 返回 0~100;异常返回 None
- try:
- v = int(volt_mv)
- except Exception:
- return None
- # 若启用 simple 门限,直接按门限输出近似 soc(仅用于 LED 分级)
- try:
- if getattr(self, "_vbatt_simple", None):
- _full, _v20, _v5 = self._vbatt_simple
- if v >= _full:
- return 100
- if v <= _v5:
- return 0
- if v <= _v20:
- return 10
- return 60
- except Exception:
- pass
- try:
- volts = self._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 = self._ocv_map.get(vv, 0)
- soc2 = self._ocv_map.get(pre_volt, 0)
- break
- else:
- pre_volt = vv
- if pre_volt == 0:
- return soc1
- if volt_not_under == 0:
- return 0
- # 线性插值(对齐 mgr.__get_soc_from_dict)
- return int(soc2 - (soc2 - soc1) * (pre_volt - v) // (pre_volt - vv))
- except Exception:
- return None
+ return self._bcfg.soc_from_vbatt(volt_mv)
+
+
def set_ipv4_datacall():
# 获取第一路的APN,ipType 等信息
pdpCtx = dataCall.getPDPContext(1)
audio.set_qdai_cfg(*qdai_cfg) # 用iis做音频输入输出
except Exception:
pass
- self.VBATT_tab = {"charge_full":4300,"100" :4250 , "90":4179 , "80":4070, "60": 3883, "40":3770, "20":3699, "10":3652,"5":3600,}
self.KeyAI_status = 0
self.KeyAI_timer_count = 0
self.KeyAI_long_press = 0
EventMesh.publish("set_led_state", {"charging": "charging"})#充电红灯常亮
except Exception:
pass
- if self.usb_status == 1 and Vbatt>=self.VBATT_tab["charge_full"]and VC_satrt_flag == 0 and voice_record_satrt_flag == 0:#充电充满
+ _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"})
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
- if self.usb_status == 0 and Vbatt<self.VBATT_tab["20"]and VC_satrt_flag == 0 and self.Low_power_warning_flag ==0 and voice_record_satrt_flag == 0:#低电报警
+ if self.usb_status == 0 and Vbatt < _bcfg.low_mv and VC_satrt_flag == 0 and self.Low_power_warning_flag == 0 and voice_record_satrt_flag == 0:
self.Low_power_warning_flag = 1
try:
if not ("vc_config" in globals() and vc_config and vc_config.get_bool("USE_MIN_HW_STATUS", True)):
# if self.Low_power_warning_timer >= 2:
# self.Low_power_warning_timer = 0
EventMesh.publish("audio_montage_play",["Low_power_warning.mp3"])
- if self.usb_status == 0 and Vbatt>=self.VBATT_tab["20"]and VC_satrt_flag == 0:#没有充电,电量又正常,关灯
+ if self.usb_status == 0 and Vbatt >= _bcfg.low_mv and VC_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": "none", "battery": "normal"})
except Exception:
pass
- if self.usb_status == 0 and Vbatt<self.VBATT_tab["5"]:
+ if self.usb_status == 0 and Vbatt < _bcfg.empty_mv:
EventMesh.publish("audio_montage_play",["didianguanji.mp3"])
utime.sleep_ms(4500)
self.aud.stopPlayStream()
self.pwk_key_Long_Press()
def get_battery(self, event=None, msg=None):
- Vbatt = Power.getVbatt()
- if Vbatt>self.VBATT_tab["90"]:
+ # 返回 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
- if Vbatt>self.VBATT_tab["80"]:
- return 90
- if Vbatt>self.VBATT_tab["60"]:
- return 80
- if Vbatt>self.VBATT_tab["40"]:
- return 60
- if Vbatt>self.VBATT_tab["20"]:
- return 40
- if Vbatt>self.VBATT_tab["10"]:
- return 20
- if Vbatt<=self.VBATT_tab["10"]:
- return 10
+ return int(soc)
def audio_montage_play(self,event,msg):
self.wakeup_BT()
# 抢占规则:流播放(TTS)可打断本地;本地提示音不可打断流播放