User:Kerio/bq27k.py

 
Line 1: Line 1:
This script provides details regarding bq27k registers; it also doubles as a python library to access bq27k registers in a programmatic way.
This script provides details regarding bq27k registers; it also doubles as a python library to access bq27k registers in a programmatic way.
 +
 +
As it is, it will only work if the bq27x00_battery module is loaded; i.e. if Pali's BME replacement is installed. It should be possible to change Bq27kAuto._read_ram() and Bq27kAuto._read_all(), even by subclassing, to allow for other ways to get the bq27k register data.
<pre>#!/usr/bin/env python
<pre>#!/usr/bin/env python

Latest revision as of 19:22, 22 April 2013

This script provides details regarding bq27k registers; it also doubles as a python library to access bq27k registers in a programmatic way.

As it is, it will only work if the bq27x00_battery module is loaded; i.e. if Pali's BME replacement is installed. It should be possible to change Bq27kAuto._read_ram() and Bq27kAuto._read_all(), even by subclassing, to allow for other ways to get the bq27k register data.

#!/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)