127 lines
5.4 KiB
Python
Executable File
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}")
|