added ook_oregon
[libsigrokdecode.git] / decoders / ook_oregon / pd.py
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 2018 Steve R <steversig@virginmedia.com>
5 ##
6 ## This program is free software; you can redistribute it and/or modify
7 ## it under the terms of the GNU General Public License as published by
8 ## the Free Software Foundation; either version 2 of the License, or
9 ## (at your option) any later version.
10 ##
11 ## This program is distributed in the hope that it will be useful,
12 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 ## GNU General Public License for more details.
15 ##
16 ## You should have received a copy of the GNU General Public License
17 ## along with this program; if not, see <http://www.gnu.org/licenses/>.
18 ##
19
20 import sigrokdecode as srd
21 import math
22 from .lists import *
23
24 class Decoder(srd.Decoder):
25     api_version = 3
26     id = 'ook_oregon'
27     name = 'Oregon'
28     longname = 'Oregon Scientific'
29     desc = 'Oregon Scientific weather sensor protocol.'
30     license = 'gplv2+'
31     inputs = ['ook']
32     outputs = []
33     annotations = (
34         ('bit', 'Bit'),
35         ('field', 'Field'),
36         ('l2', 'Level 2'),
37         ('pre', 'Preamble'),
38         ('syn', 'Sync'),
39         ('id', 'SensorID'),
40         ('ch', 'Channel'),
41         ('roll', 'Rolling code'),
42         ('f1', 'Flags1'),
43     )
44     annotation_rows = (
45         ('bits', 'Bits', (0,)),
46         ('fields', 'Fields', (1, 3, 4)),
47         ('l2', 'Level 2', (2,)),
48     )
49     binary = (
50         ('data-hex', 'Hex data'),
51     )
52     options = (
53         {'id': 'unknown', 'desc': 'Unknown type is', 'default': 'Unknown',
54          'values': ('Unknown', 'Temp', 'Temp_Hum', 'Temp_Hum1', 'Temp_Hum_Baro',
55                     'Temp_Hum_Baro1', 'UV', 'UV1', 'Wind', 'Rain', 'Rain1')},
56     )
57
58     def __init__(self):
59         self.reset()
60
61     def reset(self):
62         self.decoded = [] # Local cache of decoded OOK.
63         self.skip = None
64
65     def start(self):
66         self.out_ann = self.register(srd.OUTPUT_ANN)
67         self.out_binary = self.register(srd.OUTPUT_BINARY)
68         self.unknown = self.options['unknown']
69
70     def putx(self, data):
71         self.put(self.ss, self.es, self.out_ann, data)
72
73     def dump_oregon_hex(self, start, finish):
74         nib = self.decoded_nibbles
75         hexstring = ''
76         for x in nib:
77             if x[3] != '':
78                 hexstring += str(x[3])
79             else:
80                 hexstring += ' '
81         mystring = 'Oregon ' + self.ver + ' \"' + hexstring.upper() + '\"\n'
82         self.put(start, finish, self.out_binary,
83                 [0, bytes([ord(c) for c in mystring])])
84
85     def oregon_put_pre_and_sync(self, len_pream, len_sync, ver):
86         ook = self.decoded
87         self.decode_pos = len_pream
88         self.ss = ook[0][0]
89         self.es = ook[self.decode_pos][0]
90         self.putx([1, ['Oregon ' + ver + ' Preamble', ver + ' Preamble',
91                         ver + ' Pre', ver]])
92         self.decode_pos += len_sync
93         self.ss = ook[len_pream][0]
94         self.es = ook[self.decode_pos][0]
95         self.putx([1, ['Sync', 'Syn', 'S']])
96
97         # Strip off preamble and sync bits.
98         self.decoded = self.decoded[self.decode_pos:]
99         self.ookstring = self.ookstring[self.decode_pos:]
100         self.ver = ver
101
102     def oregon(self):
103         self.ookstring = ''
104         self.decode_pos = 0
105         ook = self.decoded
106         for i in range(len(ook)):
107             self.ookstring += ook[i][2]
108         if '10011001' in self.ookstring[:40]:
109             (preamble, data) = self.ookstring.split('10011001', 1)
110             if len(data) > 0 and len(preamble) > 16:
111                 self.oregon_put_pre_and_sync(len(preamble), 8, 'v2.1')
112                 self.oregon_v2()
113         elif 'E1100' in self.ookstring[:17]:
114             (preamble, data) = self.ookstring.split('E1100', 1)
115             if len(data) > 0 and len(preamble) <= 12:
116                 self.oregon_put_pre_and_sync(len(preamble), 5, 'v1')
117                 self.oregon_v1()
118         elif '0101' in self.ookstring[:28]:
119             (preamble, data) = self.ookstring.split('0101', 1)
120             if len(data) > 0 and len(preamble) > 12:
121                 self.oregon_put_pre_and_sync(len(preamble), 4, 'v3')
122                 self.oregon_v3()
123         elif len(self.ookstring) > 16: # Ignore short packets.
124             error_message = 'Not Oregon or wrong preamble'
125             self.ss = ook[0][0]
126             self.es = ook[len(ook)-1][1]
127             self.putx([1,[error_message]])
128
129     def oregon_v1(self):
130         ook = self.decoded
131         self.decode_pos = 0
132         self.decoded_nibbles = []
133         if len(self.decoded) >= 32: # Check there are at least 8 nibbles.
134             self.oregon_put_nib('RollingCode', ook[self.decode_pos][0],
135                                 ook[self.decode_pos + 3][1], 4)
136             self.oregon_put_nib('Ch', ook[self.decode_pos][0],
137                                 ook[self.decode_pos + 3][1], 4)
138             self.oregon_put_nib('Temp', ook[self.decode_pos][0],
139                                 ook[self.decode_pos + 15][1], 16)
140             self.oregon_put_nib('Checksum', ook[self.decode_pos][0],
141                                 ook[self.decode_pos + 7][1], 8)
142
143             self.dump_oregon_hex(ook[0][0], ook[len(ook) - 1][1])
144
145             # L2 decode.
146             self.oregon_temp(2)
147             self.oregon_channel(1)
148             self.oregon_battery(2)
149             self.oregon_checksum_v1()
150
151     def oregon_v2(self): # Convert to v3 format - discard odd bits.
152         self.decode_pos = 0
153         self.ookstring = self.ookstring[1::2]
154         for i in range(len(self.decoded)):
155             if i % 2 == 1:
156                 self.decoded[i][0] = self.decoded[i - 1][0] # Re-align start pos.
157         self.decoded = self.decoded[1::2] # Discard left hand bits.
158         self.oregon_v3() # Decode with v3 decoder.
159
160     def oregon_nibbles(self, ookstring):
161         num_nibbles = int(len(ookstring) / 4)
162         nibbles = []
163         for i in range(num_nibbles):
164             nibble = ookstring[4 * i : 4 * i + 4]
165             nibble = nibble[::-1] # Reversed from right.
166             nibbles.append(nibble)
167         return nibbles
168
169     def oregon_put_nib(self, label, start, finish, numbits):
170         param = self.ookstring[self.decode_pos:self.decode_pos + numbits]
171         param = self.oregon_nibbles(param)
172         if 'E' in ''.join(param): # Blank out fields with errors.
173             result = ''
174         else:
175             result = hex(int(''.join(param), 2))[2:]
176             if len(result) < numbits / 4: # Reinstate leading zeros.
177                 result = '0' * (int(numbits / 4) - len(result)) + result
178         if label != '':
179             label += ': '
180         self.put(start, finish, self.out_ann, [1, [label + result, result]])
181         if label == '': # No label - use nibble position.
182             label = int(self.decode_pos / 4)
183         for i in range(len(param)):
184             ss = self.decoded[self.decode_pos + (4 * i)][0]
185             es = self.decoded[self.decode_pos + (4 * i) + 3][1]
186             if 'E' in param[i]: # Blank out nibbles with errors.
187                 result = ''
188             else:
189                 result = hex(int(param[i], 2))[2:]
190             # Save nibbles for L2 decoder.
191             self.decoded_nibbles.append([ss, es, label, result])
192         self.decode_pos += numbits
193
194     def oregon_v3(self):
195         self.decode_pos = 0
196         self.decoded_nibbles = []
197         ook = self.decoded
198
199         if len(self.decoded) >= 32: # Check there are at least 8 nibbles.
200             self.oregon_put_nib('SensorID', ook[self.decode_pos][0],
201                                 ook[self.decode_pos + 16][0], 16)
202             self.oregon_put_nib('Ch', ook[self.decode_pos][0],
203                                 ook[self.decode_pos + 3][1], 4)
204             self.oregon_put_nib('RollingCode', ook[self.decode_pos][0],
205                                 ook[self.decode_pos + 7][1], 8)
206             self.oregon_put_nib('Flags1', ook[self.decode_pos][0],
207                                 ook[self.decode_pos + 3][1], 4)
208
209             rem_nibbles = len(self.ookstring[self.decode_pos:]) // 4
210             for i in range(rem_nibbles): # Display and save rest of nibbles.
211                 self.oregon_put_nib('', ook[self.decode_pos][0],
212                                     ook[self.decode_pos + 3][1], 4)
213             self.dump_oregon_hex(ook[0][0], ook[len(ook) - 1][1])
214             self.oregon_level2() # Level 2 decode.
215         else:
216             error_message = 'Too short to decode'
217             self.put(ook[0][0], ook[-1][1], self.out_ann, [1, [error_message]])
218
219     def oregon_put_l2_param(self, offset, digits, dec_point, pre_label, label):
220         nib = self.decoded_nibbles
221         result = 0
222         out_string = ''.join(str(x[3]) for x in nib[offset:offset + digits])
223         if len(out_string) == digits:
224             for i in range(dec_point, 0, -1):
225                 result += int(nib[offset + dec_point - i][3], 16) / pow(10, i)
226             for i in range(dec_point, digits):
227                 result += int(nib[offset + i][3], 16) * pow(10, i - dec_point)
228             result = '%g' % (result)
229         else:
230             result = ''
231         es = nib[offset + digits - 1][1]
232         if label == '\u2103':
233             es = nib[offset + digits][1] # Align temp to include +/- nibble.
234         self.put(nib[offset][0], es, self.out_ann,
235                 [2, [pre_label + result + label, result]])
236
237     def oregon_temp(self, offset):
238         nib = self.decoded_nibbles
239         if nib[offset + 3][3] != '':
240             temp_sign = str(int(nib[offset + 3][3], 16))
241             if temp_sign != '0':
242                 temp_sign = '-'
243             else:
244                 temp_sign = '+'
245         else:
246             temp_sign = '?'
247         self.oregon_put_l2_param(offset, 3, 1, temp_sign, '\u2103')
248
249     def oregon_baro(self, offset):
250         nib = self.decoded_nibbles
251         baro = ''
252         if not (nib[offset + 2][3] == '' or nib[offset + 1][3] == ''
253            or nib[offset][3] == ''):
254             baro = str(int(nib[offset + 1][3] + nib[offset][3], 16) + 856)
255         self.put(nib[offset][0], nib[offset + 3][1],
256                  self.out_ann, [2, [baro + ' mb', baro]])
257
258     def oregon_wind_dir(self, offset):
259         nib = self.decoded_nibbles
260         if nib[offset][3] != '':
261             w_dir = int(int(nib[offset][3], 16) * 22.5)
262             w_compass = dir_table[math.floor((w_dir + 11.25) / 22.5)]
263             self.put(nib[offset][0], nib[offset][1], self.out_ann,
264                 [2, [w_compass + ' (' + str(w_dir) + '\u00b0)', w_compass]])
265
266     def oregon_channel(self, offset):
267         nib = self.decoded_nibbles
268         channel = ''
269         if nib[offset][3] != '':
270             ch = int(nib[offset][3], 16)
271             if self.ver != 'v3': # May not be true for all v2.1 sensors.
272                 if ch != 0:
273                     bit_pos = 0
274                     while ((ch & 1) == 0):
275                         bit_pos += 1
276                         ch = ch >> 1
277                     if self.ver == 'v2.1':
278                         bit_pos += 1
279                     channel = str(bit_pos)
280             elif self.ver == 'v3': # Not sure if this applies to all v3's.
281                 channel = str(ch)
282         if channel != '':
283             self.put(nib[offset][0], nib[offset][1],
284                      self.out_ann, [2, ['Ch ' + channel, channel]])
285
286     def oregon_battery(self, offset):
287         nib = self.decoded_nibbles
288         batt = 'OK'
289         if nib[offset][3] != '':
290             if (int(nib[offset][3], 16) >> 2) & 0x1 == 1:
291                 batt = 'Low'
292             self.put(nib[offset][0], nib[offset][1],
293                      self.out_ann, [2, ['Batt ' + batt, batt]])
294
295     def oregon_level2(self): # v2 and v3 level 2 decoder.
296         nib = self.decoded_nibbles
297         self.sensor_id = (nib[0][3] + nib[1][3] + nib[2][3] + nib[3][3]).upper()
298         nl, sensor_type = sensor.get(self.sensor_id, [['Unknown'], 'Unknown'])
299         names = ','.join(nl)
300         # Allow user to try decoding an unknown sensor.
301         if sensor_type == 'Unknown' and self.unknown != 'Unknown':
302             sensor_type = self.unknown
303         self.put(nib[0][0], nib[3][1], self.out_ann,
304             [2, [names + ' - ' + sensor_type, names, nl[0]]])
305         self.oregon_channel(4)
306         self.oregon_battery(7)
307         if sensor_type == 'Rain':
308             self.oregon_put_l2_param(8, 4, 2, '', ' in/hr')     # Rain rate
309             self.oregon_put_l2_param(12, 6, 3, 'Total ', ' in') # Rain total
310             self.oregon_checksum(18)
311         if sensor_type == 'Rain1':
312             self.oregon_put_l2_param(8, 3, 1, '', ' mm/hr')     # Rain rate
313             self.oregon_put_l2_param(11, 5, 1, 'Total ', ' mm') # Rain total
314             self.oregon_checksum(18)
315         if sensor_type == 'Temp':
316             self.oregon_temp(8)
317             self.oregon_checksum(12)
318         if sensor_type == 'Temp_Hum_Baro':
319             self.oregon_temp(8)
320             self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum
321             self.oregon_baro(15)                            # Baro
322             self.oregon_checksum(19)
323         if sensor_type == 'Temp_Hum_Baro1':
324             self.oregon_temp(8)
325             self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum
326             self.oregon_baro(14)                            # Baro
327         if sensor_type == 'Temp_Hum':
328             self.oregon_temp(8)
329             self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum
330             self.oregon_checksum(15)
331         if sensor_type == 'Temp_Hum1':
332             self.oregon_temp(8)
333             self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum
334             self.oregon_checksum(14)
335         if sensor_type == 'UV':
336             self.oregon_put_l2_param(8, 2, 0, '', '') # UV
337         if sensor_type == 'UV1':
338             self.oregon_put_l2_param(11, 2, 0,'' ,'') # UV
339         if sensor_type == 'Wind':
340             self.oregon_wind_dir(8)
341             self.oregon_put_l2_param(11, 3, 1, 'Gust ', ' m/s')  # Wind gust
342             self.oregon_put_l2_param(14, 3, 1, 'Speed ', ' m/s') # Wind speed
343             self.oregon_checksum(17)
344
345     def oregon_put_checksum(self, nibbles, checksum):
346         nib = self.decoded_nibbles
347         result = 'BAD'
348         if (nibbles + 1) < len(nib):
349             if (nib[nibbles + 1][3] != '' and nib[nibbles][3] != ''
350                  and checksum != -1):
351                 if self.ver != 'v1':
352                     if checksum == (int(nib[nibbles + 1][3], 16) * 16 +
353                                     int(nib[nibbles][3], 16)):
354                         result = 'OK'
355                 else:
356                     if checksum == (int(nib[nibbles][3], 16) * 16 +
357                                     int(nib[nibbles+1][3], 16)):
358                         result = 'OK'
359             rx_check = (nib[nibbles + 1][3] + nib[nibbles][3]).upper()
360             details = '%s Calc %s Rx %s ' % (result, hex(checksum)[2:].upper(),
361                                              rx_check)
362             self.put(nib[nibbles][0], nib[nibbles + 1][1],
363                      self.out_ann, [2, ['Checksum ' + details, result]])
364
365     def oregon_checksum(self, nibbles):
366         checksum = 0
367         for i in range(nibbles):        # Add reversed nibbles.
368             nibble = self.ookstring[i * 4 : i * 4 + 4]
369             nibble = nibble[::-1]       # Reversed from right.
370             if 'E' in nibble:           # Abort checksum if there are errors.
371                 checksum = -1
372                 break
373             checksum += int(nibble, 2)
374             if checksum > 255:
375                 checksum -= 255         # Make it roll over at 255.
376         chk_ver, comment = sensor_checksum.get(self.sensor_id,
377                                                ['Unknown', 'Unknown'])
378         if chk_ver != 'Unknown':
379             self.ver = chk_ver
380         if self.ver == 'v2.1':
381             checksum -= 10              # Subtract 10 from v2 checksums.
382         self.oregon_put_checksum(nibbles, checksum)
383
384     def oregon_checksum_v1(self):
385         nib = self.decoded_nibbles
386         checksum = 0
387         for i in range(3):              # Add the first three bytes.
388             if nib[2 * i][3] == '' or nib[2 * i + 1][3] == '': # Abort if blank.
389                 checksum = -1
390                 break
391             checksum += ((int(nib[2 * i][3], 16) & 0xF) << 4 |
392                          (int(nib[2 * i + 1][3], 16) & 0xF))
393             if checksum > 255:
394                 checksum -= 255         # Make it roll over at 255.
395         self.oregon_put_checksum(6, checksum)
396
397     def decode(self, ss, es, data):
398         self.decoded = data
399         self.oregon()