]>
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: | |
80b6feeb UH |
77 | hexstring += str(x[3]) if x[3] != '' else ' ' |
78 | s = 'Oregon ' + self.ver + ' \"' + hexstring.upper() + '\"\n' | |
63642c0b | 79 | self.put(start, finish, self.out_binary, |
80b6feeb | 80 | [0, bytes([ord(c) for c in s])]) |
63642c0b S |
81 | |
82 | def oregon_put_pre_and_sync(self, len_pream, len_sync, ver): | |
83 | ook = self.decoded | |
84 | self.decode_pos = len_pream | |
80b6feeb | 85 | self.ss, self.es = ook[0][0], ook[self.decode_pos][0] |
63642c0b S |
86 | self.putx([1, ['Oregon ' + ver + ' Preamble', ver + ' Preamble', |
87 | ver + ' Pre', ver]]) | |
88 | self.decode_pos += len_sync | |
80b6feeb | 89 | self.ss, self.es = ook[len_pream][0], ook[self.decode_pos][0] |
63642c0b S |
90 | self.putx([1, ['Sync', 'Syn', 'S']]) |
91 | ||
92 | # Strip off preamble and sync bits. | |
93 | self.decoded = self.decoded[self.decode_pos:] | |
94 | self.ookstring = self.ookstring[self.decode_pos:] | |
95 | self.ver = ver | |
96 | ||
97 | def oregon(self): | |
98 | self.ookstring = '' | |
99 | self.decode_pos = 0 | |
100 | ook = self.decoded | |
101 | for i in range(len(ook)): | |
102 | self.ookstring += ook[i][2] | |
103 | if '10011001' in self.ookstring[:40]: | |
104 | (preamble, data) = self.ookstring.split('10011001', 1) | |
105 | if len(data) > 0 and len(preamble) > 16: | |
106 | self.oregon_put_pre_and_sync(len(preamble), 8, 'v2.1') | |
107 | self.oregon_v2() | |
108 | elif 'E1100' in self.ookstring[:17]: | |
109 | (preamble, data) = self.ookstring.split('E1100', 1) | |
110 | if len(data) > 0 and len(preamble) <= 12: | |
111 | self.oregon_put_pre_and_sync(len(preamble), 5, 'v1') | |
112 | self.oregon_v1() | |
113 | elif '0101' in self.ookstring[:28]: | |
114 | (preamble, data) = self.ookstring.split('0101', 1) | |
115 | if len(data) > 0 and len(preamble) > 12: | |
116 | self.oregon_put_pre_and_sync(len(preamble), 4, 'v3') | |
117 | self.oregon_v3() | |
118 | elif len(self.ookstring) > 16: # Ignore short packets. | |
119 | error_message = 'Not Oregon or wrong preamble' | |
80b6feeb | 120 | self.ss, self.es = ook[0][0], ook[len(ook) - 1][1] |
63642c0b S |
121 | self.putx([1,[error_message]]) |
122 | ||
123 | def oregon_v1(self): | |
124 | ook = self.decoded | |
125 | self.decode_pos = 0 | |
126 | self.decoded_nibbles = [] | |
127 | if len(self.decoded) >= 32: # Check there are at least 8 nibbles. | |
128 | self.oregon_put_nib('RollingCode', ook[self.decode_pos][0], | |
129 | ook[self.decode_pos + 3][1], 4) | |
130 | self.oregon_put_nib('Ch', ook[self.decode_pos][0], | |
131 | ook[self.decode_pos + 3][1], 4) | |
132 | self.oregon_put_nib('Temp', ook[self.decode_pos][0], | |
133 | ook[self.decode_pos + 15][1], 16) | |
134 | self.oregon_put_nib('Checksum', ook[self.decode_pos][0], | |
135 | ook[self.decode_pos + 7][1], 8) | |
136 | ||
137 | self.dump_oregon_hex(ook[0][0], ook[len(ook) - 1][1]) | |
138 | ||
139 | # L2 decode. | |
140 | self.oregon_temp(2) | |
141 | self.oregon_channel(1) | |
142 | self.oregon_battery(2) | |
143 | self.oregon_checksum_v1() | |
144 | ||
145 | def oregon_v2(self): # Convert to v3 format - discard odd bits. | |
146 | self.decode_pos = 0 | |
147 | self.ookstring = self.ookstring[1::2] | |
148 | for i in range(len(self.decoded)): | |
149 | if i % 2 == 1: | |
150 | self.decoded[i][0] = self.decoded[i - 1][0] # Re-align start pos. | |
151 | self.decoded = self.decoded[1::2] # Discard left hand bits. | |
152 | self.oregon_v3() # Decode with v3 decoder. | |
153 | ||
154 | def oregon_nibbles(self, ookstring): | |
155 | num_nibbles = int(len(ookstring) / 4) | |
156 | nibbles = [] | |
157 | for i in range(num_nibbles): | |
158 | nibble = ookstring[4 * i : 4 * i + 4] | |
159 | nibble = nibble[::-1] # Reversed from right. | |
160 | nibbles.append(nibble) | |
161 | return nibbles | |
162 | ||
163 | def oregon_put_nib(self, label, start, finish, numbits): | |
164 | param = self.ookstring[self.decode_pos:self.decode_pos + numbits] | |
165 | param = self.oregon_nibbles(param) | |
166 | if 'E' in ''.join(param): # Blank out fields with errors. | |
167 | result = '' | |
168 | else: | |
169 | result = hex(int(''.join(param), 2))[2:] | |
170 | if len(result) < numbits / 4: # Reinstate leading zeros. | |
171 | result = '0' * (int(numbits / 4) - len(result)) + result | |
172 | if label != '': | |
173 | label += ': ' | |
174 | self.put(start, finish, self.out_ann, [1, [label + result, result]]) | |
175 | if label == '': # No label - use nibble position. | |
176 | label = int(self.decode_pos / 4) | |
177 | for i in range(len(param)): | |
178 | ss = self.decoded[self.decode_pos + (4 * i)][0] | |
179 | es = self.decoded[self.decode_pos + (4 * i) + 3][1] | |
80b6feeb UH |
180 | # Blank out nibbles with errors. |
181 | result = '' if ('E' in param[i]) else hex(int(param[i], 2))[2:] | |
63642c0b S |
182 | # Save nibbles for L2 decoder. |
183 | self.decoded_nibbles.append([ss, es, label, result]) | |
184 | self.decode_pos += numbits | |
185 | ||
186 | def oregon_v3(self): | |
187 | self.decode_pos = 0 | |
188 | self.decoded_nibbles = [] | |
189 | ook = self.decoded | |
190 | ||
191 | if len(self.decoded) >= 32: # Check there are at least 8 nibbles. | |
192 | self.oregon_put_nib('SensorID', ook[self.decode_pos][0], | |
193 | ook[self.decode_pos + 16][0], 16) | |
194 | self.oregon_put_nib('Ch', ook[self.decode_pos][0], | |
195 | ook[self.decode_pos + 3][1], 4) | |
196 | self.oregon_put_nib('RollingCode', ook[self.decode_pos][0], | |
197 | ook[self.decode_pos + 7][1], 8) | |
198 | self.oregon_put_nib('Flags1', ook[self.decode_pos][0], | |
199 | ook[self.decode_pos + 3][1], 4) | |
200 | ||
201 | rem_nibbles = len(self.ookstring[self.decode_pos:]) // 4 | |
202 | for i in range(rem_nibbles): # Display and save rest of nibbles. | |
203 | self.oregon_put_nib('', ook[self.decode_pos][0], | |
204 | ook[self.decode_pos + 3][1], 4) | |
205 | self.dump_oregon_hex(ook[0][0], ook[len(ook) - 1][1]) | |
206 | self.oregon_level2() # Level 2 decode. | |
207 | else: | |
208 | error_message = 'Too short to decode' | |
209 | self.put(ook[0][0], ook[-1][1], self.out_ann, [1, [error_message]]) | |
210 | ||
211 | def oregon_put_l2_param(self, offset, digits, dec_point, pre_label, label): | |
212 | nib = self.decoded_nibbles | |
213 | result = 0 | |
214 | out_string = ''.join(str(x[3]) for x in nib[offset:offset + digits]) | |
215 | if len(out_string) == digits: | |
216 | for i in range(dec_point, 0, -1): | |
217 | result += int(nib[offset + dec_point - i][3], 16) / pow(10, i) | |
218 | for i in range(dec_point, digits): | |
219 | result += int(nib[offset + i][3], 16) * pow(10, i - dec_point) | |
220 | result = '%g' % (result) | |
221 | else: | |
222 | result = '' | |
223 | es = nib[offset + digits - 1][1] | |
224 | if label == '\u2103': | |
225 | es = nib[offset + digits][1] # Align temp to include +/- nibble. | |
226 | self.put(nib[offset][0], es, self.out_ann, | |
227 | [2, [pre_label + result + label, result]]) | |
228 | ||
229 | def oregon_temp(self, offset): | |
230 | nib = self.decoded_nibbles | |
231 | if nib[offset + 3][3] != '': | |
232 | temp_sign = str(int(nib[offset + 3][3], 16)) | |
80b6feeb | 233 | temp_sign = '-' if temp_sign != '0' else '+' |
63642c0b S |
234 | else: |
235 | temp_sign = '?' | |
236 | self.oregon_put_l2_param(offset, 3, 1, temp_sign, '\u2103') | |
237 | ||
238 | def oregon_baro(self, offset): | |
239 | nib = self.decoded_nibbles | |
240 | baro = '' | |
241 | if not (nib[offset + 2][3] == '' or nib[offset + 1][3] == '' | |
242 | or nib[offset][3] == ''): | |
243 | baro = str(int(nib[offset + 1][3] + nib[offset][3], 16) + 856) | |
244 | self.put(nib[offset][0], nib[offset + 3][1], | |
245 | self.out_ann, [2, [baro + ' mb', baro]]) | |
246 | ||
247 | def oregon_wind_dir(self, offset): | |
248 | nib = self.decoded_nibbles | |
249 | if nib[offset][3] != '': | |
250 | w_dir = int(int(nib[offset][3], 16) * 22.5) | |
251 | w_compass = dir_table[math.floor((w_dir + 11.25) / 22.5)] | |
252 | self.put(nib[offset][0], nib[offset][1], self.out_ann, | |
253 | [2, [w_compass + ' (' + str(w_dir) + '\u00b0)', w_compass]]) | |
254 | ||
255 | def oregon_channel(self, offset): | |
256 | nib = self.decoded_nibbles | |
257 | channel = '' | |
258 | if nib[offset][3] != '': | |
259 | ch = int(nib[offset][3], 16) | |
260 | if self.ver != 'v3': # May not be true for all v2.1 sensors. | |
261 | if ch != 0: | |
262 | bit_pos = 0 | |
263 | while ((ch & 1) == 0): | |
264 | bit_pos += 1 | |
265 | ch = ch >> 1 | |
266 | if self.ver == 'v2.1': | |
267 | bit_pos += 1 | |
268 | channel = str(bit_pos) | |
269 | elif self.ver == 'v3': # Not sure if this applies to all v3's. | |
270 | channel = str(ch) | |
271 | if channel != '': | |
272 | self.put(nib[offset][0], nib[offset][1], | |
273 | self.out_ann, [2, ['Ch ' + channel, channel]]) | |
274 | ||
275 | def oregon_battery(self, offset): | |
276 | nib = self.decoded_nibbles | |
277 | batt = 'OK' | |
278 | if nib[offset][3] != '': | |
279 | if (int(nib[offset][3], 16) >> 2) & 0x1 == 1: | |
280 | batt = 'Low' | |
281 | self.put(nib[offset][0], nib[offset][1], | |
282 | self.out_ann, [2, ['Batt ' + batt, batt]]) | |
283 | ||
284 | def oregon_level2(self): # v2 and v3 level 2 decoder. | |
285 | nib = self.decoded_nibbles | |
286 | self.sensor_id = (nib[0][3] + nib[1][3] + nib[2][3] + nib[3][3]).upper() | |
287 | nl, sensor_type = sensor.get(self.sensor_id, [['Unknown'], 'Unknown']) | |
288 | names = ','.join(nl) | |
289 | # Allow user to try decoding an unknown sensor. | |
290 | if sensor_type == 'Unknown' and self.unknown != 'Unknown': | |
291 | sensor_type = self.unknown | |
292 | self.put(nib[0][0], nib[3][1], self.out_ann, | |
293 | [2, [names + ' - ' + sensor_type, names, nl[0]]]) | |
294 | self.oregon_channel(4) | |
295 | self.oregon_battery(7) | |
296 | if sensor_type == 'Rain': | |
297 | self.oregon_put_l2_param(8, 4, 2, '', ' in/hr') # Rain rate | |
298 | self.oregon_put_l2_param(12, 6, 3, 'Total ', ' in') # Rain total | |
299 | self.oregon_checksum(18) | |
300 | if sensor_type == 'Rain1': | |
301 | self.oregon_put_l2_param(8, 3, 1, '', ' mm/hr') # Rain rate | |
302 | self.oregon_put_l2_param(11, 5, 1, 'Total ', ' mm') # Rain total | |
303 | self.oregon_checksum(18) | |
304 | if sensor_type == 'Temp': | |
305 | self.oregon_temp(8) | |
306 | self.oregon_checksum(12) | |
307 | if sensor_type == 'Temp_Hum_Baro': | |
308 | self.oregon_temp(8) | |
309 | self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum | |
310 | self.oregon_baro(15) # Baro | |
311 | self.oregon_checksum(19) | |
312 | if sensor_type == 'Temp_Hum_Baro1': | |
313 | self.oregon_temp(8) | |
314 | self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum | |
315 | self.oregon_baro(14) # Baro | |
316 | if sensor_type == 'Temp_Hum': | |
317 | self.oregon_temp(8) | |
318 | self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum | |
319 | self.oregon_checksum(15) | |
320 | if sensor_type == 'Temp_Hum1': | |
321 | self.oregon_temp(8) | |
322 | self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum | |
323 | self.oregon_checksum(14) | |
324 | if sensor_type == 'UV': | |
325 | self.oregon_put_l2_param(8, 2, 0, '', '') # UV | |
326 | if sensor_type == 'UV1': | |
327 | self.oregon_put_l2_param(11, 2, 0,'' ,'') # UV | |
328 | if sensor_type == 'Wind': | |
329 | self.oregon_wind_dir(8) | |
330 | self.oregon_put_l2_param(11, 3, 1, 'Gust ', ' m/s') # Wind gust | |
331 | self.oregon_put_l2_param(14, 3, 1, 'Speed ', ' m/s') # Wind speed | |
332 | self.oregon_checksum(17) | |
333 | ||
334 | def oregon_put_checksum(self, nibbles, checksum): | |
335 | nib = self.decoded_nibbles | |
336 | result = 'BAD' | |
337 | if (nibbles + 1) < len(nib): | |
338 | if (nib[nibbles + 1][3] != '' and nib[nibbles][3] != '' | |
339 | and checksum != -1): | |
340 | if self.ver != 'v1': | |
341 | if checksum == (int(nib[nibbles + 1][3], 16) * 16 + | |
342 | int(nib[nibbles][3], 16)): | |
343 | result = 'OK' | |
344 | else: | |
345 | if checksum == (int(nib[nibbles][3], 16) * 16 + | |
80b6feeb | 346 | int(nib[nibbles + 1][3], 16)): |
63642c0b S |
347 | result = 'OK' |
348 | rx_check = (nib[nibbles + 1][3] + nib[nibbles][3]).upper() | |
349 | details = '%s Calc %s Rx %s ' % (result, hex(checksum)[2:].upper(), | |
350 | rx_check) | |
351 | self.put(nib[nibbles][0], nib[nibbles + 1][1], | |
352 | self.out_ann, [2, ['Checksum ' + details, result]]) | |
353 | ||
354 | def oregon_checksum(self, nibbles): | |
355 | checksum = 0 | |
356 | for i in range(nibbles): # Add reversed nibbles. | |
357 | nibble = self.ookstring[i * 4 : i * 4 + 4] | |
358 | nibble = nibble[::-1] # Reversed from right. | |
359 | if 'E' in nibble: # Abort checksum if there are errors. | |
360 | checksum = -1 | |
361 | break | |
362 | checksum += int(nibble, 2) | |
363 | if checksum > 255: | |
364 | checksum -= 255 # Make it roll over at 255. | |
365 | chk_ver, comment = sensor_checksum.get(self.sensor_id, | |
366 | ['Unknown', 'Unknown']) | |
367 | if chk_ver != 'Unknown': | |
368 | self.ver = chk_ver | |
369 | if self.ver == 'v2.1': | |
370 | checksum -= 10 # Subtract 10 from v2 checksums. | |
371 | self.oregon_put_checksum(nibbles, checksum) | |
372 | ||
373 | def oregon_checksum_v1(self): | |
374 | nib = self.decoded_nibbles | |
375 | checksum = 0 | |
376 | for i in range(3): # Add the first three bytes. | |
377 | if nib[2 * i][3] == '' or nib[2 * i + 1][3] == '': # Abort if blank. | |
378 | checksum = -1 | |
379 | break | |
380 | checksum += ((int(nib[2 * i][3], 16) & 0xF) << 4 | | |
381 | (int(nib[2 * i + 1][3], 16) & 0xF)) | |
382 | if checksum > 255: | |
383 | checksum -= 255 # Make it roll over at 255. | |
384 | self.oregon_put_checksum(6, checksum) | |
385 | ||
386 | def decode(self, ss, es, data): | |
387 | self.decoded = data | |
388 | self.oregon() |