Mercurial > ~darius > hgwebdir.cgi > epro
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() |