Files
shaggy-solar/eg4battery/tmp/eg4-snapshot
2026-04-24 16:34:10 -04:00

127 lines
5.4 KiB
Python
Executable File

#!/home/noise/.local/share/uv/tools/powermon/bin/python
"""eg4-snapshot — single-shot register dump for correlation against the
EG4 BMS Tool UI. Prints a 47-row table with:
reg | raw u16 | hex | our decode | BMS Tool UI field
----+---------+-----+------------------------------+-------------------
Run while the BMS Tool is open on the same pack; fill in the BMS Tool
column by reading the matching values off the UI. Send the result back
and we refine `decode_eg4_modbus_regs` in `bin/eg4-battery`.
Usage:
eg4-snapshot # auto-detect FTDI, query 0x40
eg4-snapshot <port> [addr] # explicit port / address
"""
import glob, sys, time, serial
PORT = sys.argv[1] if len(sys.argv) > 1 else None
ADDR = int(sys.argv[2], 0) if len(sys.argv) > 2 else 0x40
if PORT is None:
hits = sorted(glob.glob("/dev/serial/by-id/usb-FTDI_*"))
if not hits:
sys.exit("no FTDI adapter visible")
PORT = hits[0]
def crc16(data):
c = 0xFFFF
for b in data:
c ^= b
for _ in range(8):
c = (c >> 1) ^ 0xA001 if c & 1 else c >> 1
return c
def modbus_read_47(addr):
body = bytes([addr, 0x03, 0, 0, 0, 47])
cr = crc16(body)
return body + bytes([cr & 0xFF, cr >> 8])
def _s16(v):
return v - 0x10000 if v & 0x8000 else v
# What we THINK each register means right now. Keep in sync with
# bin/eg4-battery::decode_eg4_modbus_regs.
MAP = [
# (name, decode fn)
("Total_Voltage", lambda v, regs: f"{v/100:.2f} V"),
("Current_I (signed)", lambda v, regs: f"{_s16(v)/100:+.2f} A"),
("Vol_Cell01 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell02 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell03 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell04 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell05 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell06 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell07 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell08 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell09 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell10 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell11 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell12 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell13 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell14 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell15 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Vol_Cell16 (mV)", lambda v, regs: f"{v/1000:.3f} V"),
("Temp_01", lambda v, regs: f"{v} °C"),
("Temp_02", lambda v, regs: f"{v} °C"),
("Temp_03", lambda v, regs: f"{v} °C"),
("Temp_04 (MOS?)", lambda v, regs: f"{v} °C"),
("SOC", lambda v, regs: f"{v} %"),
("SOH", lambda v, regs: f"{v} %"),
("Temp_PCB", lambda v, regs: f"{v} °C"),
("? reserved-25", lambda v, regs: f"{v}"),
("? reserved-26", lambda v, regs: f"{v}"),
("? reserved-27", lambda v, regs: f"{v}"),
("? reserved-28", lambda v, regs: f"{v}"),
("? reserved-29", lambda v, regs: f"{v}"),
("Heater/Status", lambda v, regs: f"{v}"),
("MAX_Curren", lambda v, regs: f"{v/100:.2f} A"),
("? reg-32", lambda v, regs: f"{v} (0x{v:04x})"),
("Warning bits", lambda v, regs: f"0x{v:04x} ({bin(v)[2:].zfill(16)})"),
("Protection bits", lambda v, regs: f"0x{v:04x} ({bin(v)[2:].zfill(16)})"),
("Error_Code", lambda v, regs: f"{v}"),
("Cell_Num", lambda v, regs: f"{v}"),
("Capacity", lambda v, regs: f"{v/10:.1f} Ah"),
("Remaining", lambda v, regs: f"{v/100:.2f} Ah"),
("CycleNum", lambda v, regs: f"{v}"),
("Battery_Mode", lambda v, regs: f"{v}"),
("BMS_Version (hi)", lambda v, regs: f"0x{v:04x}"),
("BMS_Version (lo)", lambda v, regs: f"0x{v:04x}"),
("? reg-43", lambda v, regs: f"{v}"),
("? reg-44", lambda v, regs: f"{v}"),
("? reg-45", lambda v, regs: f"{v}"),
("uptime counter", lambda v, regs: f"{v}"),
]
assert len(MAP) == 47, f"MAP has {len(MAP)} entries, expected 47"
with serial.Serial(PORT, 9600, bytesize=8, parity="N", stopbits=1, timeout=0.3) as p:
p.reset_input_buffer()
p.write(modbus_read_47(ADDR))
time.sleep(0.5)
buf = p.read(512)
if len(buf) < 5 + 94 or buf[0] != ADDR or buf[1] != 0x03:
sys.exit(f"bad response ({len(buf)} B): {buf.hex(' ')[:80]}")
bc = buf[2]
data = buf[3:3 + bc]
regs = [(data[i] << 8) | data[i + 1] for i in range(0, bc, 2)]
print(f"# eg4-snapshot port={PORT} addr=0x{ADDR:02x} time={time.strftime('%Y-%m-%d %H:%M:%S')}")
print()
print(f"{'reg':>4} {'raw':>6} {'hex':>7} {'our decode':<34} BMS Tool UI value (fill me in)")
print(f"{'---':>4} {'-----':>6} {'-----':>7} {'-'*34} {'-'*40}")
for i, (label, fn) in enumerate(MAP):
raw = regs[i]
try:
dec = fn(raw, regs)
except Exception as e:
dec = f"(decode error: {e})"
print(f"{i:>4} {raw:>6} 0x{raw:04x} {label:<34} {dec}")