#!/usr/bin/env python3 """Kookabus-01 reader for Linux Python (Raspberry Pi, Jetson...). pip install smbus2 && python3 kookabus.py [bus] [addr] """ import sys import time R_SHUNT = 0.005 # ohm; trim against a multimeter if current reads off I_LSB = 0.0005 # A per CURRENT bit CAL = round(0.00512 / (I_LSB * R_SHUNT)) # 2048 CONFIG = 0x4527 # 16-sample average, 1.1 ms conversions, continuous CELLS = 5 R_PATH = 0.03 + 0.10 # ohm: board + an assumed pack resistance, for sag correction V_CUTOFF = 3.0 * CELLS # Resting Li-ion cell voltage -> % (typical NMC; tune for your packs) OCV = ((3.00, 0), (3.30, 5), (3.45, 10), (3.60, 20), (3.70, 35), (3.80, 50), (3.90, 65), (4.00, 78), (4.10, 90), (4.20, 100)) def soc(pack_v, cells=CELLS): v = pack_v / cells if v <= OCV[0][0]: return 0 for (v0, p0), (v1, p1) in zip(OCV, OCV[1:]): if v <= v1: return p0 + (p1 - p0) * (v - v0) / (v1 - v0) return 100 class Kookabus: def __init__(self, bus=1, addr=0x40): from smbus2 import SMBus self.bus, self.addr = SMBus(bus), addr if self._r(0xFE) != 0x5449: raise OSError(f"no INA226 at 0x{addr:02x}") self._w(0x00, CONFIG) self._w(0x05, CAL) # The INA226 is big-endian; SMBus word transfers are little-endian. def _w(self, reg, val): self.bus.write_i2c_block_data(self.addr, reg, [val >> 8, val & 0xFF]) def _r(self, reg): hi, lo = self.bus.read_i2c_block_data(self.addr, reg, 2) return hi << 8 | lo def voltage(self): return self._r(0x02) * 0.00125 def current(self): raw = self._r(0x04) return (raw - 0x10000 if raw & 0x8000 else raw) * I_LSB def power(self): return self._r(0x03) * 25 * I_LSB if __name__ == "__main__": kb = Kookabus(int(sys.argv[1]) if len(sys.argv) > 1 else 1, int(sys.argv[2], 0) if len(sys.argv) > 2 else 0x40) while True: v, i, p = kb.voltage(), kb.current(), kb.power() low = " LOW - stop the load" if v < V_CUTOFF else "" print(f"{v:6.2f} V {i:6.3f} A {p:6.1f} W ~{soc(v + i * R_PATH):3.0f} %{low}", flush=True) time.sleep(1)