User:Kerio/bq27k.py
#!/usr/bin/env python
import time
import struct
RS = 20 # nominal, but sometimes 21 or 22 give more accurate results
class Bitstring(long):
"""Number with an easy way to access single bits or ranges of bits.
b[n] is the n-th bit of b, as a bool
b[a:b] is the bits from the a-th to the (b-1)th
"""
def __getitem__(self, item):
# something that's not enough like a slice...
try:
start, stop = item.start, item.stop
except AttributeError:
return bool(self & 1 << item)
# ...or something that is
if start is None: start = 0
if stop is None:
return Bitstring(self >> start)
else:
return Bitstring((self >> start) % (1 << (stop - start)))
class MODE(Bitstring):
"""Device Mode Register"""
@property
def SHIP(self):
"""Ship mode"""
return self[0]
@property
def FRST(self):
"""Full reset"""
return self[1]
@property
def POR(self):
"""Power on Reset"""
return self[2]
@property
def PRST(self):
"""Partial reset"""
return self[3]
@property
def DONE(self):
"""Write LMD to NAC"""
return self[4]
@property
def WRTNAC(self):
"""Write AR to NAC"""
return self[5]
@property
def GPSTAT(self):
"""GPIO pin"""
return self[6]
@property
def GPIEN(self):
"""State of the GPIO pin"""
return self[7]
@property
def CIO(self):
"""Calibrate internal offset"""
return self[4]
@property
def CEO(self):
"""Calibrate external offset"""
return self[5]
class FLAGS(Bitstring):
"""Status Flags"""
@property
def EDVF(self):
"""Final End-of-Discharge-Voltage"""
return self[0]
@property
def EDV1(self):
"""First End-of-Discharge-Voltage"""
return self[1]
@property
def VDQ(self):
"""Valid Discharge"""
return self[2]
@property
def CALIP(self):
"""Calibration-In-Progress"""
return self[3]
@property
def CI(self):
"""Capacity Inaccurate"""
return self[4]
@property
def IMIN(self):
"""Li-ion taper current detection"""
return self[5]
@property
def NOACT(self):
"""No Activity"""
return self[6]
@property
def CHGS(self):
"""Charge State"""
return self[7]
class DMFSD(Bitstring):
"""Digital Magnitude Filter and Self-Discharge Rate Constants"""
@property
def SD(self):
"""Self-Discharge Rate (%)"""
return 1.61 / self[:4]
@property
def DMF(self):
"""Digital Magnitude Filter (mA)"""
return self[4:] * 4.9 / RS
class TAPER(Bitstring):
"""Aging Estimate Enable, Charge Termination Taper Current"""
@property
def TAPER(self):
"""Charge Termination Taper Current (mA)"""
return self[:7] * 228.48 / RS
@property
def AEE(self):
"""Aging Estimate Enable"""
return self[7]
class PKCFG(Bitstring):
"""Pack Configuration Values"""
@property
def TCFIX(self):
"""Fixed temperature compensation"""
return self[0]
@property
def DCFIX(self):
"""Fixed discharge compensation"""
return self[1]
@property
def BOFF(self):
"""Board offset value (uV)"""
# 3-bit two's complement
return ((self[2:5] + 2) % 4 - 2) * 2.45
@property
def QV(self):
"""Qualification voltage for charge termination (mV)"""
return 3968 + 48 * self[5:7]
@property
def GPIEN(self):
"""State of the GPIO pin on initial power up"""
return self[7]
class DCOMP(Bitstring):
"""Discharge Rate Compensation Constants"""
@property
def DCOFF(self):
"""Discharge rate compensation threshold"""
return [0, 0.5, 0.25, 0.125][self[:2]]
@property
def DCGN(self):
"""Discharge rate compensation gain (%)"""
return self[2:] / 2.56
class TCOMP(Bitstring):
"""Temperature Compensation Constants"""
@property
def TOFF(self):
"""Temperature compensation offset (C)"""
return self[:4]
@property
def TCGN(self):
"""Temperature compensation gain (%)"""
return self[4:] / 10.24
class Bq27kData(object):
"""Class that provides data descriptors to fetch converted bq27k register
data from an indexable object. The underlying object must return bytes
curresponding to the bq27k registers on item access; alternatively, known
two-byte registers might be stored on the "low" register - the "high" one
must be 0, in this case.
"""
@property
def CTRL(self):
"""Device Control Register"""
return self[0x00]
@property
def MODE(self):
"""Device Mode Register"""
return MODE(self[0x01])
@property
def AR(self):
"""At-Rate (mA)"""
return word(self[0x02:0x04]) * 3.57 / RS
@property
def ARTTE(self):
"""At-Rate Time-to-Empty (min)"""
return word(self[0x04:0x06])
@property
def TEMP(self):
"""Reported Temperature (C)"""
return word(self[0x06:0x08]) * 0.25 - 273.15
@property
def VOLT(self):
"""Reported Voltage (mV)"""
return word(self[0x08:0x0a])
@property
def FLAGS(self):
"""Status Flags"""
return FLAGS(self[0x0a])
@property
def RSOC(self):
"""Relative State-of-Charge (%)"""
return self[0x0b]
@property
def NAC(self):
"""Nominal Available Capacity (mAh)"""
return word(self[0x0c:0x0e]) * 3.57 / RS
@property
def CACD(self):
"""Discharge Compensated NAC (mAh)"""
return word(self[0x0e:0x10]) * 3.57 / RS
@property
def CACT(self):
"""Temperature Compensated CACD (mAh)"""
return word(self[0x10:0x12]) * 3.57 / RS
@property
def LMD(self):
"""Last Measured Discharge (mAh)"""
return word(self[0x12:0x14]) * 3.57 / RS
@property
def AI(self):
"""Average Current (mA)"""
return word(self[0x14:0x16]) * 3.57 / RS
@property
def TTE(self):
"""Time-to-Empty (min)"""
return word(self[0x16:0x18])
@property
def TTF(self):
"""Time-to-Full (min)"""
return word(self[0x18:0x1a])
@property
def SI(self):
"""Standby Current (mA)"""
return word(self[0x1a:0x1c]) * 3.57 / RS
@property
def STTE(self):
"""Standby Time-to-Empty (min)"""
return word(self[0x1c:0x1e])
@property
def MLI(self):
"""Max Load Current (mA)"""
return word(self[0x1e:0x20]) * 3.57 / RS
@property
def MLTTE(self):
"""Max Load Time-to-Empty (min)"""
return word(self[0x20:0x22])
@property
def SAE(self):
"""Available Energy (mWh)"""
return word(self[0x22:0x24]) * 29.2 / RS
@property
def AP(self):
"""Average Power (mW)"""
return word(self[0x24:0x26]) * 29.2 / RS
@property
def TTECP(self):
"""Time-to-Empty At Constant Power (min)"""
return word(self[0x26:0x28])
@property
def CYCL(self):
"""Cycle Count Since Learning Cycle"""
return word(self[0x28:0x2a])
@property
def CYCT(self):
"""Cycle Count Total"""
return word(self[0x2a:0x2c])
@property
def CSOC(self):
"""Compensated State-of-Charge (%)"""
return self[0x2c]
@property
def CRES(self):
"""Calibration Result (uV)"""
# 16-bit two's complement
return ((word(self[0x5e:0x60]) + 0x8000) % 0x10000 - 0x8000) * 1.225
@property
def EE_EN(self):
"""EEPROM Program Enable"""
return self[0x6e]
@property
def ILMD(self):
"""Initial Last Measured Discharge (mAh)"""
return self[0x76] * 913.92 / RS
@property
def EDVF(self):
"""EDVF Threshold (mV)"""
return (self[0x77] + 256) * 8
@property
def EDV1(self):
"""EDV1 Threshold (mV)"""
return (self[0x78] + 256) * 8
@property
def ISLC(self):
"""Initial Standby Load Current (mA)"""
return self[0x79] * 7.14 / RS
@property
def DMFSD(self):
"""Digital Magnitude Filter and Self-Discharge Rate Constants"""
return DMFSD(self[0x7a])
@property
def TAPER(self):
"""Aging Estimate Enable, Charge Termination Taper Current"""
return TAPER(self[0x7b])
@property
def PKCFG(self):
"""Pack Configuration Values"""
return PKCFG(self[0x7c])
@property
def IMLC(self):
"""Initial Max Load Current (mA)"""
return self[0x7d] * 456.96 / RS
@property
def DCOMP(self):
"""Discharge Rate Compensation Constants"""
return DCOMP(self[0x7e])
@property
def TCOMP(self):
"""Temperature Compensation Constants"""
return TCOMP(self[0x7f])
@property
def ID(self):
"""Identification Bytes"""
return struct.pack("3B", *self[0x7f:0x7c:-1])
class Bq27kAuto(Bq27kData):
def __init__(self, grace=5):
if grace < 5: raise ValueError("grace must be at least 5 seconds")
self.grace = grace
self._cache = [0] * 128
self.refresh(refresh_all=True, force=True)
def __getitem__(self, item):
return self._cache[item]
def wait(self, minimum=0, refresh_all=False):
time.sleep(max(self._expire - time.time(), minimum))
self.refresh(refresh_all=refresh_all, force=True)
def refresh(self, refresh_all=False, force=False):
if not force and time.time() < self._expire:
return
if refresh_all:
self._read_all()
else:
self._read_ram()
self._expire = time.time() + self.grace
def _read_all(self, stop=0x80):
for line in open("/sys/class/power_supply/bq27200-0/registers", "rt"):
addr, _, val = line.strip().partition("=")
addr, val = int(addr, base=16), int(val, base=16)
if addr >= stop: break
self._cache[addr] = val
def _read_ram(self):
self._read_all(stop=0x2d)
class Bq27kList(list, Bq27kData):
"""A list with data descriptors to convert bq27k register data."""
def read_registers(path="/sys/class/power_supply/bq27200-0/registers"):
reg = [0] * 128
for line in open(path, "rt"):
addr, _, val = line.strip().partition("=")
addr, val = int(addr, base=16), int(val, base=16)
reg[addr] = val
return reg
def word(pair):
return pair[1] << 8 | pair[0]
if __name__ == "__main__":
import sys
if len(sys.argv) > 1:
RS = int(sys.argv[1])
reg = Bq27kAuto()
mode, flags, pkcfg = reg.MODE, reg.FLAGS, reg.PKCFG
print("Sense resistance (RS): %s mohm" % RS)
print("Device Control Register (CTRL): 0x%02x" % reg.CTRL)
print("Device Mode Register (MODE):")
print(" Ship mode (SHIP): %s" % mode.SHIP)
print(" Full reset (FRST): %s" % mode.FRST)
print(" Power on Reset (POR): %s" % mode.POR)
print(" Partial reset (PRST): %s" % mode.PRST)
print(" Write LMD to NAC (DONE): %s" % mode.DONE)
print(" Write AR to NAC (WRTNAC): %s" % mode.WRTNAC)
print(" GPIO pin (GPSTAT): %s" % mode.GPSTAT)
print(" State of the GPIO pin (GPIEN): %s" % mode.GPIEN)
print(" Calibrate internal offset (CIO): %s" % mode.CIO)
print(" Calibrate external offset (CEO): %s" % mode.CEO)
print("At-Rate (AR): %s mA" % reg.AR)
print("At-Rate Time-to-Empty (ARTTE): %s min" % reg.ARTTE)
print("Reported Temperature (TEMP): %s C" % reg.TEMP)
print("Reported Voltage (VOLT): %s mV" % reg.VOLT)
print("Status Flags (FLAGS):")
print(" Final End-of-Discharge-Voltage (EDVF): %s" % flags.EDVF)
print(" First End-of-Discharge-Voltage (EDV1): %s" % flags.EDV1)
print(" Valid Discharge (VDQ): %s" % flags.VDQ)
print(" Calibration-In-Progress (CALIP): %s" % flags.CALIP)
print(" Capacity Inaccurate (CI): %s" % flags.CI)
print(" Li-Ion taper current detection (IMIN): %s" % flags.IMIN)
print(" No Activity (NOACT): %s" % flags.NOACT)
print(" Charge State (CHGS): %s" % flags.CHGS)
print("Relative State-of-Charge (RSOC): %s%%" % reg.RSOC)
print("Nominal Available Capacity (NAC): %s mAh" % reg.NAC)
print("Discharge Compensated NAC (CACD): %s mAh" % reg.CACD)
print("Temperature Compensated CACD (CACT): %s mAh" % reg.CACT)
print("Last Measured Discharge (LMD): %s mAh" % reg.LMD)
print("Average Current (AI): %s mA" % reg.AI)
print("Time-to-Empty (TTE): %s min" % reg.TTE)
print("Time-to-Full (TTF): %s min" % reg.TTF)
print("Standby Current (SI): %s mA" % reg.SI)
print("Standby Time-to-Empty (STTE): %s min" % reg.STTE)
print("Max Load Current (MLI): %s mA" % reg.MLI)
print("Max Load Time-to-Empty (MLTTE): %s min" % reg.MLTTE)
print("Available Energy (SAE): %s mWh" % reg.SAE)
print("Average Power (AP): %s mW" % reg.AP)
print("Time-to-Empty At Constant Power (TTECP): %s min" % reg.TTECP)
print("Cycle Count Since Learning Cycle (CYCL): %s cycles" % reg.CYCL)
print("Cycle Count Total (CYCT): %s cycles" % reg.CYCT)
print("Compensated State-of-Charge (CSOC): %s%%" % reg.CSOC)
print("Calibration Result: %s uV" % reg.CRES)
print("EEPROM Program Enable (EE_EN): 0x%02x" % reg.EE_EN)
print("Initial Last Measured Discharge (ILMD): %s mAh" % reg.ILMD)
print("EDVF Threshold (EDVF): %s mV" % reg.EDVF)
print("EDV1 Threshold (EDV1): %s mV" % reg.EDV1)
print("Initial Standby Load Current (ISLC): %s mA" % reg.ISLC)
print(
"Digital Magnitude Filter and Self-Discharge Rate Constants (DMFSD):")
print(" Self-Discharge Rate (SD): %s%%" % reg.DMFSD.SD)
print(" Digital Magnitude Filter (DMF): %s mA" % reg.DMFSD.DMF)
print(
"Aging Estimate Enable, Charge Termination Taper Current (TAPER):")
print(" Charge Termination Taper Current (TAPER): %s mA"
% reg.TAPER.TAPER)
print(" Aging Estimate Enable (AEE): %s" % reg.TAPER.AEE)
print("Pack Configuration Values (PKCFG):")
print(" Fixed temperature compensation (TCFIX): %s" % pkcfg.TCFIX)
print(" Fixed discharge compensation (DCFIX): %s" % pkcfg.DCFIX)
print(" Board offset value (BOFF): %s uV" % pkcfg.BOFF)
print(" Qualification voltage for charge termination (QV): %s mV"
% pkcfg.QV)
print(" State of the GPIO pin on initial power up (GPIEN): %s"
% pkcfg.GPIEN)
print("Initial Max Load Current (IMLC): %s mA" % reg.IMLC)
print("Discharge Rate Compensation Constants (DCOMP):")
print(" Discharge rate compensation threshold (DCOFF): %s"
% reg.DCOMP.DCOFF)
print(" Discharge rate compensation gain (DCGN): %s%%"
% reg.DCOMP.DCGN)
print("Temperature Compensation Constants (TCOMP):")
print(" Temperature compensation offset (TOFF): %s C"
% reg.TCOMP.TOFF)
print(" Temperature compensation gain (TCGN): %s%%"
% reg.TCOMP.TCGN)
print("Identification Bytes (ID): %r" % reg.ID)