# HG changeset patch # User Daniel O'Connor # Date 1437318001 -34200 # Node ID 1de08e57774f79285403aca278958adc232203d8 Initial comit of code to talk to EnerDrive ePro battery monitor diff -r 000000000000 -r 1de08e57774f .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Mon Jul 20 00:30:01 2015 +0930 @@ -0,0 +1,2 @@ +.*\.pyc + diff -r 000000000000 -r 1de08e57774f epro.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/epro.py Mon Jul 20 00:30:01 2015 +0930 @@ -0,0 +1,260 @@ +#!/usr/bin/env python + +# View facing ePro from the back +# +---+ +# +-| |-| +# | | +# | | +# +-------+ +# 1 ... 6 +# +# RJ12 plug (Jaycar cable colours) +# 1 white temp out +# 2 black temp in +# 3 red GND +# 4 green TX TTL (5V) +# 5 yellow RX TTL (5V) +# 6 blue VCC (12V) +# +# 2400bps 8E1 +# s = serial.Serial('/dev/cu.usbserial-AM01Z7TZ', 2400, parity='E') + +class Packet(object): + MSGTYPE = -1 + + def __init__(self, dstadr, srcadr, devid, msgtype, data): + self.dstadr = dstadr + self.srcadr = srcadr + self.devid = devid + self.msgtype = msgtype + self.data = data + + def __repr__(self): + hdr = "Src: 0x%02x Dst: 0x%02x DevID: 0x%02x MsgType: 0x%02x" % ( + self.dstadr, self.srcadr, self.devid, self.msgtype) + # MSGTYPE is overridden for subclasses which also have their own repr so don't dumb data here + if self.MSGTYPE == -1: + hdr += " Data:" + for d in self.data: + hdr += " 0x%02x" % (d) + else: + hdr += " Name: " + self.MSGNAME + + return hdr + +class MainVoltage(Packet): + """Main Battery Voltage""" + MSGTYPE = 0x60 + MSGNAME = "Main Voltage" + LEN = 3 + + def __init__(self, dstadr, srcadr, devid, msgtype, data): + super(MainVoltage, self).__init__(dstadr, srcadr, devid, msgtype, data) + self.volts = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x03) << 14) / 100.0 + + def __repr__(self): + s = super(MainVoltage, self).__repr__() + s += ": %.2f V" % (self.volts) + return s + +class BatteryCurrent(Packet): + """Main Battery Current""" + MSGTYPE = 0x61 + MSGNAME = "Battery Current" + LEN = 3 + + def __init__(self, dstadr, srcadr, devid, msgtype, data): + super(BatteryCurrent, self).__init__(dstadr, srcadr, devid, msgtype, data) + self.amps = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x3f) << 14) / 100.0 + if data[2] & 0x40: + self.amps *= -1 + + def __repr__(self): + s = super(BatteryCurrent, self).__repr__() + s += ": %.2f A" % (self.amps) + return s + +class AmpHours(Packet): + """Number of amp hours removed from the battery""" + MSGTYPE = 0x62 + MSGNAME = "Amp Hours" + LEN = 3 + + def __init__(self, dstadr, srcadr, devid, msgtype, data): + super(AmpHours, self).__init__(dstadr, srcadr, devid, msgtype, data) + self.amphrs = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x3f) << 14) / 100.0 + if data[2] & 0x40: + self.amphrs *= -1 + + def __repr__(self): + s = super(AmpHours, self).__repr__() + s += ": %.2f Ah" % (self.amphrs) + return s + +class StateOfCharge(Packet): + """State of battery charge""" + MSGTYPE = 0x64 + MSGNAME = "State Of Charge" + LEN = 3 + + def __init__(self, dstadr, srcadr, devid, msgtype, data): + super(StateOfCharge, self).__init__(dstadr, srcadr, devid, msgtype, data) + self.soc = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x03) << 14) / 10.0 + + def __repr__(self): + s = super(StateOfCharge, self).__repr__() + s += ": %.2f%%" % (self.soc) + return s + +class TimeRemaining(Packet): + """Time remaining until battery needs charging""" + MSGTYPE = 0x65 + MSGNAME = "Time remaining" + LEN = 3 + + def __init__(self, dstadr, srcadr, devid, msgtype, data): + super(TimeRemaining, self).__init__(dstadr, srcadr, devid, msgtype, data) + self.time = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x3f) << 14) + if data[2] & 0x40: + self.time *= -1 + + def __repr__(self): + s = super(TimeRemaining, self).__repr__() + s += ": %.2f min" % (self.time) + return s + +class BatteryTemperature(Packet): + """Battery temperature (degrees Celcius)""" + MSGTYPE = 0x66 + MSGNAME = "Battery Temperature" + LEN = 3 + + def __init__(self, dstadr, srcadr, devid, msgtype, data): + super(BatteryTemperature, self).__init__(dstadr, srcadr, devid, msgtype, data) + self.time = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x3f) << 14) / 10.0 + if data[2] & 0x40: + self.time *= -1 + + def __repr__(self): + s = super(BatteryTemperature, self).__repr__() + s += ": %.2f degC" % (self.time) + return s + +class MonitorStatus(Packet): + """Monitor status""" + MSGTYPE = 0x67 + MSGNAME = "Monitor Status" + LEN = 3 + + def __init__(self, dstadr, srcadr, devid, msgtype, data): + super(MonitorStatus, self).__init__(dstadr, srcadr, devid, msgtype, data) + self.autosyncvolt = data[0] & 0x10 + self.autosyncamp = data[0] & 0x08 + self.autosyncchrg = data[0] & 0x04 + self.e501compat = data[0] & 0x02 + self.alarmtst = data[0] & 0x01 + self.backlight = data[1] & 0x40 + self.disptst = data[1] & 0x20 + self.tempsense = data[1] & 0x10 # Seems to be inverted from data sheet + self.auxhv = data[1] & 0x08 + self.auxlv = data[1] & 0x04 + self.lock = data[1] & 0x02 + self.mainhv = data[1] & 0x01 + self.mainlv = data[2] & 0x40 + self.lowbatalarm = data[2] & 0x20 + self.batflat = data[2] & 0x10 + self.batfull = data[2] & 0x08 + self.charged = data[2] & 0x04 + self.nosync = data[2] & 0x02 + self.monreset = data[2] & 0x01 + + def __repr__(self): + s = super(MonitorStatus, self).__repr__() + stats = ( ( "ASV", self.autosyncvolt ), ( "ASA", self.autosyncamp ), ( "ASC" , self.autosyncchrg ), + ( "E501", self.e501compat ), ( "Alarm test", self.alarmtst ), ( "Light", self.backlight ), + ( "Temperature Sensor", self.tempsense ), ( "Aux HV", self.auxhv ), ( "Aux LV", self.auxlv ), + ( "Lock", self.lock ), ( "Main HV", self.mainhv ), ( "Main LV", self.mainlv ), + ( "Low Battery", self.lowbatalarm ), ( "Battery Flat", self.batfull ), + ( "Battery Full", self.batfull ), ( "Battery Charged", self.charged ), + ( "No Sync", self.nosync ), ( "Monitor Reset", self.monreset ) ) + for (short, var) in stats: + if var: + s += " %s : True" % (short) + else: + s += " %s : False" % (short) + + return s + +class AuxVoltage(Packet): + """Aux Battery Voltage""" + MSGTYPE = 0x68 + MSGNAME = "Aux Voltage" + LEN = 3 + + def __init__(self, dstadr, srcadr, devid, msgtype, data): + super(AuxVoltage, self).__init__(dstadr, srcadr, devid, msgtype, data) + self.volts = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x03) << 14) / 100.0 + + def __repr__(self): + s = super(AuxVoltage, self).__repr__() + s += ": %.2f V" % (self.volts) + return s + +class Processor(object): + PKT_TYPES = { MainVoltage.MSGTYPE : MainVoltage, BatteryCurrent.MSGTYPE : BatteryCurrent, + AmpHours.MSGTYPE : AmpHours, StateOfCharge.MSGTYPE : StateOfCharge, + TimeRemaining.MSGTYPE : TimeRemaining, BatteryTemperature.MSGTYPE : BatteryTemperature, + MonitorStatus.MSGTYPE : MonitorStatus, AuxVoltage.MSGTYPE : AuxVoltage } + + def __init__(self): + self.state = 0 + self.packets = [] + + def process(self, dat): + for d in dat: + d = ord(d) + if d == 0xff and self.state != 4: + print "Packet corruption" + continue + + if self.state == 0: + # Waiting for destination address (MSB set but not 0xff as that is EOM) + if d == 0xff or d & 0x80 == 0: + print "Skipping byte" + continue + self.dstadr = d & 0x7f + self.data = [] + self.state += 1 + elif self.state == 1: + # Source address + self.srcadr = d + self.state += 1 + elif self.state == 2: + # Device ID + self.devid = d + self.state += 1 + elif self.state == 3: + # Message type + self.msgtype = d + self.state += 1 + elif self.state == 4: + # Data + if d != 0xff: + self.data.append(d) + continue + self.state = 0 + if self.msgtype in Processor.PKT_TYPES: + t = self.PKT_TYPES[self.msgtype] + if len(self.data) != t.LEN: + print "Packet length incorrect, expected %d got %d" % (t.LEN, len(self.data)) + continue + + p = self.PKT_TYPES[self.msgtype](self.dstadr, self.srcadr, self.devid, self.msgtype, self.data) + else: + p = Packet(self.dstadr, self.srcadr, self.devid, self.msgtype, self.data) + + print p + self.packets.append(p) + +if __name__ == '__main__': + main() diff -r 000000000000 -r 1de08e57774f sample.dat Binary file sample.dat has changed