fixed battery SoC and various other regs

This commit is contained in:
2026-05-16 07:33:22 -04:00
parent 38ac9ca8e8
commit 231e03081d

View File

@@ -338,23 +338,28 @@ def decode_eg4_modbus_regs(regs: list[int], expose_raw: bool = False) -> dict[st
out["cell_lowest"] = cells_v.index(vmin) + 1
out["cell_highest"] = cells_v.index(vmax) + 1
# --- temperatures (regs 18-21 = Temp_01..04, reg 24 = Temp_PCB) ---
# --- temperatures (regs 18-20 = Temp_01..03) ---
# Regs 21 and 24 were previously labeled temperature_04 / temperature_pcb
# (carryover from a misread of the lv_host.app schema) but are actually
# the BMS's SoC tracker — see the SoC block below. The pack only exposes
# three real cell temps.
out["temperature_01"] = regs[18]
out["temperature_02"] = regs[19]
out["temperature_03"] = regs[20]
out["temperature_04"] = regs[21]
out["temperature_pcb"] = regs[24]
# --- SoC / SoH (regs 22, 23) ---
out["soc"] = regs[22]
out["soh"] = regs[23]
# --- heater / status (regs 25-30) ---
# reg 30 has been observed = 1 on a healthy pack; treat as binary
out["heater"] = "on" if regs[30] & 0x01 else "off"
# --- max charge/discharge current limit (reg 31), A ---
out["max_current_limit"] = round(regs[31] / 100.0, 2)
# --- SoC (regs 21 / 24) ---
# Identified 2026-05-16 by comparing a week of register snapshots against
# the bank's known SoC drift (95% → ~40%): only regs 21 and 24 moved in
# lockstep with the physical state change. regs 22/23 are pegged at 100
# across all observed conditions — they are emitted below as raw fields
# for visibility but do NOT name them soc/soh until we know what they are.
# r24 stays within ±1 of r21 (probably a re-snapshotted copy from later
# in the BMS poll loop); kept as `soc_alt` (diagnostic) for now so we
# can watch whether the relationship holds.
out["soc"] = regs[21]
out["soc_alt"] = regs[24]
out["bms_field_22"] = regs[22] # was misnamed `soc` — purpose unknown
out["bms_field_23"] = regs[23] # was misnamed `soh` — purpose unknown
# --- error / alarm word (reg 35) ---
# reg 33/34 used to decode as warning_/protection_ bitfields but were
@@ -362,12 +367,28 @@ def decode_eg4_modbus_regs(regs: list[int], expose_raw: bool = False) -> dict[st
# reg 35 is the canonical alarm word; 0 on healthy packs.
out["error_code"] = regs[35]
# --- static-ish (regs 36, 37) ---
# --- static-ish (regs 36, 37, 38) ---
out["cell_count"] = regs[36]
out["capacity_ah"] = round(regs[37] / 10.0, 1)
out["remaining_ah"] = round(regs[38] / 100.0, 2)
out["cycle_count"] = regs[39]
out["battery_mode"] = regs[40]
# Several other regs in this block were previously decoded with names that
# turned out not to match reality. Removed 2026-05-16, pending re-derivation:
# reg 30 → was "heater" via bit-0 test; bit 0 is always 0 across all
# 6 packs in all observed states, so the heater claim is
# unprovable. The reg itself has values 6 / 38 / 42 that
# vary by pack batch (older packs ~38-42, newer ~6) and
# look like a status-flag bitfield, not heater on/off.
# reg 31 → was "max_current_limit" in A; value 5493 → 54.93 sits
# squarely in the float-voltage range (54-55 V) and is
# identical across all packs regardless of charge state.
# Almost certainly V*100 of a config threshold, not amps.
# reg 39 → was "cycle_count"; pegged at 0 across all packs after
# weeks of cycling. Not the cycle counter.
# reg 40 → was "battery_mode"; pegged at 7 across all packs in all
# states. Possibly a battery-type code or fixed mode flag,
# but value never changes so naming it "mode" was misleading.
# All four entities + their HA discovery configs were retracted at deploy.
# Use --expose-raw / register_NN entities to investigate further.
# regs 41-42: u16 version codes — superseded by `firmware_version` ASCII
# decode below; available via expose_raw if needed
@@ -703,14 +724,12 @@ _FIELD_META.update({
"temperature_01": ("°C", "temperature", "measurement", "mdi:thermometer"),
"temperature_02": ("°C", "temperature", "measurement", "mdi:thermometer"),
"temperature_03": ("°C", "temperature", "measurement", "mdi:thermometer"),
"temperature_04": ("°C", "temperature", "measurement", "mdi:thermometer"),
"temperature_pcb": ("°C", "temperature", "measurement", "mdi:chip"),
"heater": (None, None, None, "mdi:heating-coil"),
"max_current_limit": ("A", "current", "measurement", "mdi:current-dc"),
"soc_alt": ("%", "battery", "measurement", "mdi:battery-50"),
"bms_field_22": (None, None, "measurement", "mdi:numeric"),
"bms_field_23": (None, None, "measurement", "mdi:numeric"),
"error_code": (None, None, None, "mdi:alert-octagon"),
"cell_count": (None, None, "measurement", "mdi:numeric"),
"remaining_ah": ("Ah", None, "measurement", "mdi:battery-clock"),
"battery_mode": (None, None, None, "mdi:state-machine"),
"model": (None, None, None, "mdi:battery-outline"),
"firmware_version": (None, None, None, "mdi:chip"),
"firmware_date": (None, None, None, "mdi:calendar"),
@@ -726,9 +745,10 @@ def field_meta(key: str) -> tuple[str | None, str | None, str | None, str | None
_FIELD_PRECISION: dict[str, int] = {
"pack_voltage": 2,
"pack_current": 2,
"max_current_limit": 2,
"soc": 0,
"soh": 0,
"soc_alt": 0,
"bms_field_22": 0,
"bms_field_23": 0,
"cycle_count": 0,
"cell_voltage_min": 3,
"cell_voltage_max": 3,
@@ -739,13 +759,11 @@ _FIELD_PRECISION: dict[str, int] = {
"capacity_ah": 1,
"remaining_ah": 1,
"error_code": 0,
"battery_mode": 0,
}
for _i in range(1, 17):
_FIELD_PRECISION[f"cell_{_i:02d}_voltage"] = 3
for _i in range(1, 7):
_FIELD_PRECISION[f"temperature_{_i}"] = 0
_FIELD_PRECISION["temperature_pcb"] = 0
def field_precision(key: str) -> int | None: