From: SnickerSun Date: Sun, 17 May 2026 12:07:27 +0000 (+0800) Subject: 电池统一通过配置进行电压计算,并去掉电池相关硬编码。 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=7194593c0b67dbc7632e750e453a2bb0ee3c27c2;p=mirrors%2FASR1603_G5.git 电池统一通过配置进行电压计算,并去掉电池相关硬编码。 --- diff --git a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/ai_VC.py b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/ai_VC.py index 3623398b..8204a6af 100644 --- a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/ai_VC.py +++ b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/ai_VC.py @@ -377,6 +377,115 @@ class AI_led(Abstract): 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): @@ -385,17 +494,14 @@ class AI_hw_status(Abstract): 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 @@ -410,17 +516,6 @@ class AI_hw_status(Abstract): 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/按键调试,也一并打开硬件监控打印,便于关联分析 @@ -512,24 +607,17 @@ class AI_hw_status(Abstract): 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 @@ -572,48 +660,9 @@ class AI_hw_status(Abstract): 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) @@ -1447,7 +1496,6 @@ class AI_dev(Abstract): 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 @@ -2152,7 +2200,8 @@ class AI_dev(Abstract): 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"}) @@ -2162,7 +2211,7 @@ class AI_dev(Abstract): 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= 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 Vbattself.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)可打断本地;本地提示音不可打断流播放 diff --git a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/default_config.py b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/default_config.py index 0ba4676c..1978dd37 100644 --- a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/default_config.py +++ b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/default_config.py @@ -43,6 +43,25 @@ LED_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 = 20 # OCV 模式:soc < 此值 → battery=low +BATTERY_EMPTY_SOC_PERCENT = 5 # OCV 模式:soc < 此值 → battery=empty +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" diff --git a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/configs/7PIN/default_config.py b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/configs/7PIN/default_config.py index 0ba4676c..1978dd37 100644 --- a/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/configs/7PIN/default_config.py +++ b/2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/configs/7PIN/default_config.py @@ -43,6 +43,25 @@ LED_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 = 20 # OCV 模式:soc < 此值 → battery=low +BATTERY_EMPTY_SOC_PERCENT = 5 # OCV 模式:soc < 此值 → battery=empty +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"