comparison epro.py @ 0:1de08e57774f

Initial comit of code to talk to EnerDrive ePro battery monitor
author Daniel O'Connor <darius@dons.net.au>
date Mon, 20 Jul 2015 00:30:01 +0930
parents
children 6d0fe22566ab
comparison
equal deleted inserted replaced
-1:000000000000 0:1de08e57774f
1 #!/usr/bin/env python
2
3 # View facing ePro from the back
4 # +---+
5 # +-| |-|
6 # | |
7 # | |
8 # +-------+
9 # 1 ... 6
10 #
11 # RJ12 plug (Jaycar cable colours)
12 # 1 white temp out
13 # 2 black temp in
14 # 3 red GND
15 # 4 green TX TTL (5V)
16 # 5 yellow RX TTL (5V)
17 # 6 blue VCC (12V)
18 #
19 # 2400bps 8E1
20 # s = serial.Serial('/dev/cu.usbserial-AM01Z7TZ', 2400, parity='E')
21
22 class Packet(object):
23 MSGTYPE = -1
24
25 def __init__(self, dstadr, srcadr, devid, msgtype, data):
26 self.dstadr = dstadr
27 self.srcadr = srcadr
28 self.devid = devid
29 self.msgtype = msgtype
30 self.data = data
31
32 def __repr__(self):
33 hdr = "Src: 0x%02x Dst: 0x%02x DevID: 0x%02x MsgType: 0x%02x" % (
34 self.dstadr, self.srcadr, self.devid, self.msgtype)
35 # MSGTYPE is overridden for subclasses which also have their own repr so don't dumb data here
36 if self.MSGTYPE == -1:
37 hdr += " Data:"
38 for d in self.data:
39 hdr += " 0x%02x" % (d)
40 else:
41 hdr += " Name: " + self.MSGNAME
42
43 return hdr
44
45 class MainVoltage(Packet):
46 """Main Battery Voltage"""
47 MSGTYPE = 0x60
48 MSGNAME = "Main Voltage"
49 LEN = 3
50
51 def __init__(self, dstadr, srcadr, devid, msgtype, data):
52 super(MainVoltage, self).__init__(dstadr, srcadr, devid, msgtype, data)
53 self.volts = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x03) << 14) / 100.0
54
55 def __repr__(self):
56 s = super(MainVoltage, self).__repr__()
57 s += ": %.2f V" % (self.volts)
58 return s
59
60 class BatteryCurrent(Packet):
61 """Main Battery Current"""
62 MSGTYPE = 0x61
63 MSGNAME = "Battery Current"
64 LEN = 3
65
66 def __init__(self, dstadr, srcadr, devid, msgtype, data):
67 super(BatteryCurrent, self).__init__(dstadr, srcadr, devid, msgtype, data)
68 self.amps = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x3f) << 14) / 100.0
69 if data[2] & 0x40:
70 self.amps *= -1
71
72 def __repr__(self):
73 s = super(BatteryCurrent, self).__repr__()
74 s += ": %.2f A" % (self.amps)
75 return s
76
77 class AmpHours(Packet):
78 """Number of amp hours removed from the battery"""
79 MSGTYPE = 0x62
80 MSGNAME = "Amp Hours"
81 LEN = 3
82
83 def __init__(self, dstadr, srcadr, devid, msgtype, data):
84 super(AmpHours, self).__init__(dstadr, srcadr, devid, msgtype, data)
85 self.amphrs = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x3f) << 14) / 100.0
86 if data[2] & 0x40:
87 self.amphrs *= -1
88
89 def __repr__(self):
90 s = super(AmpHours, self).__repr__()
91 s += ": %.2f Ah" % (self.amphrs)
92 return s
93
94 class StateOfCharge(Packet):
95 """State of battery charge"""
96 MSGTYPE = 0x64
97 MSGNAME = "State Of Charge"
98 LEN = 3
99
100 def __init__(self, dstadr, srcadr, devid, msgtype, data):
101 super(StateOfCharge, self).__init__(dstadr, srcadr, devid, msgtype, data)
102 self.soc = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x03) << 14) / 10.0
103
104 def __repr__(self):
105 s = super(StateOfCharge, self).__repr__()
106 s += ": %.2f%%" % (self.soc)
107 return s
108
109 class TimeRemaining(Packet):
110 """Time remaining until battery needs charging"""
111 MSGTYPE = 0x65
112 MSGNAME = "Time remaining"
113 LEN = 3
114
115 def __init__(self, dstadr, srcadr, devid, msgtype, data):
116 super(TimeRemaining, self).__init__(dstadr, srcadr, devid, msgtype, data)
117 self.time = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x3f) << 14)
118 if data[2] & 0x40:
119 self.time *= -1
120
121 def __repr__(self):
122 s = super(TimeRemaining, self).__repr__()
123 s += ": %.2f min" % (self.time)
124 return s
125
126 class BatteryTemperature(Packet):
127 """Battery temperature (degrees Celcius)"""
128 MSGTYPE = 0x66
129 MSGNAME = "Battery Temperature"
130 LEN = 3
131
132 def __init__(self, dstadr, srcadr, devid, msgtype, data):
133 super(BatteryTemperature, self).__init__(dstadr, srcadr, devid, msgtype, data)
134 self.time = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x3f) << 14) / 10.0
135 if data[2] & 0x40:
136 self.time *= -1
137
138 def __repr__(self):
139 s = super(BatteryTemperature, self).__repr__()
140 s += ": %.2f degC" % (self.time)
141 return s
142
143 class MonitorStatus(Packet):
144 """Monitor status"""
145 MSGTYPE = 0x67
146 MSGNAME = "Monitor Status"
147 LEN = 3
148
149 def __init__(self, dstadr, srcadr, devid, msgtype, data):
150 super(MonitorStatus, self).__init__(dstadr, srcadr, devid, msgtype, data)
151 self.autosyncvolt = data[0] & 0x10
152 self.autosyncamp = data[0] & 0x08
153 self.autosyncchrg = data[0] & 0x04
154 self.e501compat = data[0] & 0x02
155 self.alarmtst = data[0] & 0x01
156 self.backlight = data[1] & 0x40
157 self.disptst = data[1] & 0x20
158 self.tempsense = data[1] & 0x10 # Seems to be inverted from data sheet
159 self.auxhv = data[1] & 0x08
160 self.auxlv = data[1] & 0x04
161 self.lock = data[1] & 0x02
162 self.mainhv = data[1] & 0x01
163 self.mainlv = data[2] & 0x40
164 self.lowbatalarm = data[2] & 0x20
165 self.batflat = data[2] & 0x10
166 self.batfull = data[2] & 0x08
167 self.charged = data[2] & 0x04
168 self.nosync = data[2] & 0x02
169 self.monreset = data[2] & 0x01
170
171 def __repr__(self):
172 s = super(MonitorStatus, self).__repr__()
173 stats = ( ( "ASV", self.autosyncvolt ), ( "ASA", self.autosyncamp ), ( "ASC" , self.autosyncchrg ),
174 ( "E501", self.e501compat ), ( "Alarm test", self.alarmtst ), ( "Light", self.backlight ),
175 ( "Temperature Sensor", self.tempsense ), ( "Aux HV", self.auxhv ), ( "Aux LV", self.auxlv ),
176 ( "Lock", self.lock ), ( "Main HV", self.mainhv ), ( "Main LV", self.mainlv ),
177 ( "Low Battery", self.lowbatalarm ), ( "Battery Flat", self.batfull ),
178 ( "Battery Full", self.batfull ), ( "Battery Charged", self.charged ),
179 ( "No Sync", self.nosync ), ( "Monitor Reset", self.monreset ) )
180 for (short, var) in stats:
181 if var:
182 s += " %s : True" % (short)
183 else:
184 s += " %s : False" % (short)
185
186 return s
187
188 class AuxVoltage(Packet):
189 """Aux Battery Voltage"""
190 MSGTYPE = 0x68
191 MSGNAME = "Aux Voltage"
192 LEN = 3
193
194 def __init__(self, dstadr, srcadr, devid, msgtype, data):
195 super(AuxVoltage, self).__init__(dstadr, srcadr, devid, msgtype, data)
196 self.volts = (data[2] & 0x7f | (data[1] & 0x7f) << 7 | (data[0] & 0x03) << 14) / 100.0
197
198 def __repr__(self):
199 s = super(AuxVoltage, self).__repr__()
200 s += ": %.2f V" % (self.volts)
201 return s
202
203 class Processor(object):
204 PKT_TYPES = { MainVoltage.MSGTYPE : MainVoltage, BatteryCurrent.MSGTYPE : BatteryCurrent,
205 AmpHours.MSGTYPE : AmpHours, StateOfCharge.MSGTYPE : StateOfCharge,
206 TimeRemaining.MSGTYPE : TimeRemaining, BatteryTemperature.MSGTYPE : BatteryTemperature,
207 MonitorStatus.MSGTYPE : MonitorStatus, AuxVoltage.MSGTYPE : AuxVoltage }
208
209 def __init__(self):
210 self.state = 0
211 self.packets = []
212
213 def process(self, dat):
214 for d in dat:
215 d = ord(d)
216 if d == 0xff and self.state != 4:
217 print "Packet corruption"
218 continue
219
220 if self.state == 0:
221 # Waiting for destination address (MSB set but not 0xff as that is EOM)
222 if d == 0xff or d & 0x80 == 0:
223 print "Skipping byte"
224 continue
225 self.dstadr = d & 0x7f
226 self.data = []
227 self.state += 1
228 elif self.state == 1:
229 # Source address
230 self.srcadr = d
231 self.state += 1
232 elif self.state == 2:
233 # Device ID
234 self.devid = d
235 self.state += 1
236 elif self.state == 3:
237 # Message type
238 self.msgtype = d
239 self.state += 1
240 elif self.state == 4:
241 # Data
242 if d != 0xff:
243 self.data.append(d)
244 continue
245 self.state = 0
246 if self.msgtype in Processor.PKT_TYPES:
247 t = self.PKT_TYPES[self.msgtype]
248 if len(self.data) != t.LEN:
249 print "Packet length incorrect, expected %d got %d" % (t.LEN, len(self.data))
250 continue
251
252 p = self.PKT_TYPES[self.msgtype](self.dstadr, self.srcadr, self.devid, self.msgtype, self.data)
253 else:
254 p = Packet(self.dstadr, self.srcadr, self.devid, self.msgtype, self.data)
255
256 print p
257 self.packets.append(p)
258
259 if __name__ == '__main__':
260 main()