]>
Commit | Line | Data |
---|---|---|
63642c0b S |
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() |