Bugfixes
[rtl-433.git] / src / devices / oregon_scientific.c
1 #include "rtl_433.h"
2 #include "data.h"
3 #include "util.h"
4 /// Documentation for Oregon Scientific protocols can be found here:
5 /// http://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf
6 // Sensors ID
7 #define ID_THGR122N 0x1d20
8 #define ID_THGR968  0x1d30
9 #define ID_BHTR968  0x5d60
10 #define ID_RGR968   0x2d10
11 #define ID_THR228N  0xec40
12 #define ID_THN132N  0xec40 // same as THR228N but different packet size
13 #define ID_RTGN318  0x0cc3 // warning: id is from 0x0cc3 and 0xfcc3
14 #define ID_THGR810  0xf824
15 #define ID_THN802   0xc844
16 #define ID_PCR800   0x2914
17 #define ID_PCR800a  0x2d14 // Different PCR800 ID - AU version I think
18 #define ID_THGR81   0xf824
19 #define ID_WGR800   0x1984
20 #define ID_WGR968   0x3d00
21 #define ID_UV800    0xd874
22 #define ID_THN129   0xcc43  // THN129 Temp only
23 #define ID_BTHGN129 0x5d53  // Baro, Temp, Hygro sensor
24
25 float get_os_temperature(unsigned char *message, unsigned int sensor_id) {
26   // sensor ID included  to support sensors with temp in different position
27   float temp_c = 0;
28   temp_c = (((message[5]>>4)*100)+((message[4]&0x0f)*10) + ((message[4]>>4)&0x0f)) / 10.0F;
29   if (message[5] & 0x0f)
30     temp_c = -temp_c;
31   return temp_c;
32 }
33
34 float get_os_rain_rate(unsigned char *message, unsigned int sensor_id) {
35   float rain_rate = 0;  // Nibbles 11..8 rain rate, LSD = 0.01 inches per hour
36   rain_rate = (((message[5]&0x0f) * 1000) +((message[5]>>4)*100)+((message[4]&0x0f)*10) + ((message[4]>>4)&0x0f)) / 100.0F;
37   return rain_rate;
38 }
39
40 float get_os_total_rain(unsigned char *message, unsigned int sensor_id) {
41   float total_rain = 0.0F; // Nibbles 17..12 Total rain, LSD = 0.001, 543210 = 012.345 inches
42   total_rain = (message[8]&0x0f) * 100.0F +((message[8]>>4)&0x0f)*10.0F   +(message[7]&0x0f)
43    + ((message[7]>>4)&0x0f) / 10.0F + (message[6]&0x0f) / 100.0F + ((message[6]>>4)&0x0f)/1000.0F;
44   return total_rain;
45 }
46
47 unsigned int get_os_humidity(unsigned char *message, unsigned int sensor_id) {
48   // sensor ID included to support sensors with humidity in different position
49   int humidity = 0;
50   humidity = ((message[6]&0x0f)*10)+(message[6]>>4);
51   return humidity;
52 }
53
54 float get_os_pressure(unsigned char *message, unsigned int sensor_id) {
55   // sensor ID included to support sensors with pressure in different position/format
56   if (debug_output)
57   {
58     fprintf(stdout," raw pressure data : %02x %02x\n",(int)message[8],(int)message[7]);
59     /*int i;
60     for (i=0; message[i];i++)
61     {
62       fprintf(stdout,"%d %02x ",i,(int)message[i]);
63     }
64     fprintf(stdout,"\n");*/
65   }
66   float pressure = 0;
67   pressure = ((message[8]<<4)+message[7])/100.0F/0.0295299830714;
68   return pressure;
69 }
70
71
72 unsigned int get_os_uv(unsigned char *message, unsigned int sensor_id) {
73   // sensor ID included to support sensors with uv in different position
74   int uvidx = 0;
75   uvidx = ((message[4]&0x0f)*10)+(message[4]>>4);
76   return uvidx;
77 }
78
79 unsigned int get_os_channel(unsigned char *message, unsigned int sensor_id) {
80   // sensor ID included to support sensors with channel in different position
81   int channel = 0;
82   channel = ((message[2] >> 4)&0x0f);
83   if ((channel == 4) && (sensor_id & 0x0fff) != ID_RTGN318 && sensor_id != ID_THGR810)
84     channel = 3; // sensor 3 channel number is 0x04
85   return channel;
86 }
87
88 unsigned int get_os_battery(unsigned char *message, unsigned int sensor_id) {
89   // sensor ID included to support sensors with battery in different position
90   int battery_low = 0;
91   battery_low = (message[3] >> 2 & 0x01);
92   return battery_low;
93 }
94
95 unsigned int get_os_rollingcode(unsigned char *message, unsigned int sensor_id) {
96   // sensor ID included to support sensors with rollingcode in different position
97   int rc = 0;
98   rc = (message[2]&0x0F) + (message[3]&0xF0);
99   return rc;
100 }
101
102 unsigned short int power(const unsigned char* d) {
103   unsigned short int val = 0;
104   val = ( d[4] << 8) + d[3];
105   val = val & 0xFFF0;
106   val = val * 1.00188;
107   return val;
108 }
109
110 unsigned long long total(const unsigned char* d) {
111   unsigned long long val = 0;
112   if ( (d[1]&0x0F) == 0 ){
113     // Sensor returns total only if nibble#4 == 0
114     val =  (unsigned long long)d[10]<< 40;
115     val += (unsigned long long)d[9] << 32;
116     val += (unsigned long)d[8]<<24;
117     val += (unsigned long)d[7] << 16;
118     val += d[6] << 8;
119     val += d[5];
120   }
121   return val ;
122 }
123
124 static int validate_os_checksum(unsigned char *msg, int checksum_nibble_idx) {
125   // Oregon Scientific v2.1 and v3 checksum is a  1 byte  'sum of nibbles' checksum.
126   // with the 2 nibbles of the checksum byte  swapped.
127   int i;
128   unsigned int checksum, sum_of_nibbles=0;
129   for (i=0; i<(checksum_nibble_idx-1);i+=2) {
130     unsigned char val=msg[i>>1];
131     sum_of_nibbles += ((val>>4) + (val &0x0f));
132   }
133   if (checksum_nibble_idx & 1) {
134     sum_of_nibbles += (msg[checksum_nibble_idx>>1]>>4);
135     checksum = (msg[checksum_nibble_idx>>1] & 0x0f) | (msg[(checksum_nibble_idx+1)>>1]&0xf0);
136   } else {
137     checksum = (msg[checksum_nibble_idx>>1]>>4) | ((msg[checksum_nibble_idx>>1]&0x0f)<<4);
138   }
139   sum_of_nibbles &= 0xff;
140
141   if (sum_of_nibbles == checksum) {
142     return 0;
143   } else {
144     if(debug_output) {
145       fprintf(stderr, "Checksum error in Oregon Scientific message.  Expected: %02x  Calculated: %02x\n", checksum, sum_of_nibbles);
146       fprintf(stderr, "Message: "); int i; for (i=0 ;i<((checksum_nibble_idx+4)>>1) ; i++) fprintf(stdout, "%02x ", msg[i]); fprintf(stdout, "\n\n");
147    }
148     return 1;
149   }
150 }
151
152 static int validate_os_v2_message(unsigned char * msg, int bits_expected, int valid_v2_bits_received,
153    int nibbles_in_checksum) {
154   // Oregon scientific v2.1 protocol sends each bit using the complement of the bit, then the bit  for better error checking.  Compare number of valid bits processed vs number expected
155   if (bits_expected == valid_v2_bits_received) {
156     return (validate_os_checksum(msg, nibbles_in_checksum));
157   } else {
158     if(debug_output) {
159       fprintf(stderr, "Bit validation error on Oregon Scientific message.  Expected %d bits, received error after bit %d \n",        bits_expected, valid_v2_bits_received);
160       fprintf(stderr, "Message: "); int i; for (i=0 ;i<(bits_expected+7)/8 ; i++) fprintf(stdout, "%02x ", msg[i]); fprintf(stdout, "\n\n");
161     }
162   }
163   return 1;
164 }
165
166 static int oregon_scientific_v2_1_parser(bitbuffer_t *bitbuffer) {
167   bitrow_t *bb = bitbuffer->bb;
168   // Check 2nd and 3rd bytes of stream for possible Oregon Scientific v2.1 sensor data (skip first byte to get past sync/startup bit errors)
169   if( ((bb[0][1] == 0x55) && (bb[0][2] == 0x55)) ||
170       ((bb[0][1] == 0xAA) && (bb[0][2] == 0xAA))) {
171     int i,j;
172     unsigned char msg[BITBUF_COLS] = {0};
173
174     // Possible  v2.1 Protocol message
175     int num_valid_v2_bits = 0;
176
177     unsigned int sync_test_val = (bb[0][3]<<24) | (bb[0][4]<<16) | (bb[0][5]<<8) | (bb[0][6]);
178     int dest_bit = 0;
179     int pattern_index;
180     // Could be extra/dropped bits in stream.  Look for sync byte at expected position +/- some bits in either direction
181     for(pattern_index=0; pattern_index<8; pattern_index++) {
182       unsigned int mask     = (unsigned int) (0xffff0000>>pattern_index);
183       unsigned int pattern  = (unsigned int)(0x55990000>>pattern_index);
184       unsigned int pattern2 = (unsigned int)(0xaa990000>>pattern_index);
185
186       //fprintf(stdout, "OS v2.1 sync byte search - test_val=%08x pattern=%08x  mask=%08x\n", sync_test_val, pattern, mask);
187
188       if (((sync_test_val & mask) == pattern) ||
189           ((sync_test_val & mask) == pattern2)) {
190         //  Found sync byte - start working on decoding the stream data.
191         // pattern_index indicates  where sync nibble starts, so now we can find the start of the payload
192         int start_byte = 5 + (pattern_index>>3);
193         int start_bit = pattern_index & 0x07;
194         //fprintf(stdout, "OS v2.1 Sync test val %08x found, starting decode at byte index %d bit %d\n", sync_test_val, start_byte, start_bit);
195         int bits_processed = 0;
196         unsigned char last_bit_val = 0;
197         j=start_bit;
198         for (i=start_byte;i<BITBUF_COLS;i++) {
199           while (j<8) {
200             if (bits_processed & 0x01) {
201               unsigned char bit_val = ((bb[0][i] & (0x80 >> j)) >> (7-j));
202
203               // check if last bit received was the complement of the current bit
204               if ((num_valid_v2_bits == 0) && (last_bit_val == bit_val))
205                 num_valid_v2_bits = bits_processed; // record position of first bit in stream that doesn't verify correctly
206               last_bit_val = bit_val;
207
208               // copy every other bit from source stream to dest packet
209               msg[dest_bit>>3] |= (((bb[0][i] & (0x80 >> j)) >> (7-j)) << (7-(dest_bit & 0x07)));
210
211               //fprintf(stdout,"i=%d j=%d dest_bit=%02x bb=%02x msg=%02x\n",i, j, dest_bit, bb[0][i], msg[dest_bit>>3]);
212               if ((dest_bit & 0x07) == 0x07) {
213                 // after assembling each dest byte, flip bits in each nibble to convert from lsb to msb bit ordering
214                 int k = (dest_bit>>3);
215                 unsigned char indata = msg[k];
216                 // flip the 4 bits in the upper and lower nibbles
217                 msg[k] = ((indata & 0x11) << 3) | ((indata & 0x22) << 1) |
218                   ((indata & 0x44) >> 1) | ((indata & 0x88) >> 3);
219               }
220               dest_bit++;
221             } else  {
222               last_bit_val = ((bb[0][i] & (0x80 >> j)) >> (7-j)); // used for v2.1 bit error detection
223             }
224             bits_processed++;
225             j++;
226           }
227           j=0;
228         }
229         break;
230       } //if (sync_test_val...
231     } // for (pattern...
232
233     data_t *data;
234     char time_str[LOCAL_TIME_BUFLEN];
235     local_time_str(0, time_str);
236
237     int sensor_id = (msg[0] << 8) | msg[1];
238     if ((sensor_id == ID_THGR122N) || (sensor_id == ID_THGR968)){
239       if (validate_os_v2_message(msg, 153, num_valid_v2_bits, 15) == 0) {
240         data = data_make(
241             "time",          "",            DATA_STRING, time_str,
242             "brand",         "",            DATA_STRING, "OS",
243             "model",         "",            DATA_STRING, (sensor_id == ID_THGR122N) ? "THGR122N": "THGR968",
244             "id",            "House Code",  DATA_INT,    get_os_rollingcode(msg, sensor_id),
245             "channel",       "Channel",     DATA_INT,    get_os_channel(msg, sensor_id),
246             "battery",       "Battery",     DATA_STRING, get_os_battery(msg, sensor_id) ? "LOW" : "OK",
247             "temperature_C", "Temperature", DATA_FORMAT, "%.02f C", DATA_DOUBLE, get_os_temperature(msg, sensor_id),
248             "humidity",      "Humidity",    DATA_FORMAT, "%u %%",   DATA_INT,    get_os_humidity(msg, sensor_id),
249             NULL);
250         data_acquired_handler(data);
251       }
252       return 1;
253     } else if (sensor_id == ID_WGR968) {
254       if (validate_os_v2_message(msg, 189, num_valid_v2_bits, 17) == 0) {
255         float quadrant = (((msg[4] &0x0f)*10) + ((msg[4]>>4)&0x0f) + (((msg[5]>>4)&0x0f) * 100));
256         float avgWindspeed = ((msg[7]>>4)&0x0f) / 10.0F + (msg[7]&0x0f) *1.0F + ((msg[8]>>4)&0x0f) / 10.0F;
257         float gustWindspeed = (msg[5]&0x0f) /10.0F + ((msg[6]>>4)&0x0f) *1.0F + (msg[6]&0x0f) / 10.0F;
258         data = data_make(
259             "time",       "",           DATA_STRING, time_str,
260             "brand",      "",           DATA_STRING, "OS",
261             "model",      "",           DATA_STRING, "WGR968",
262             "id",         "House Code", DATA_INT,    get_os_rollingcode(msg, sensor_id),
263             "channel",    "Channel",    DATA_INT,    get_os_channel(msg, sensor_id),
264             "battery",    "Battery",    DATA_STRING, get_os_battery(msg, sensor_id) ? "LOW" : "OK",
265             "gust",       "Gust",       DATA_FORMAT, "%2.1f m/s",DATA_DOUBLE, gustWindspeed,
266             "average",    "Average",    DATA_FORMAT, "%2.1f m/s",DATA_DOUBLE, avgWindspeed,
267             "direction",  "Direction",  DATA_FORMAT, "%3.1f degrees",DATA_DOUBLE, quadrant,
268             NULL);
269         data_acquired_handler(data);
270       }
271       return 1;
272     } else if (sensor_id == ID_BHTR968) {
273       if (validate_os_v2_message(msg, 185, num_valid_v2_bits, 19) == 0) {
274         unsigned int comfort = msg[7] >>4;
275         char *comfort_str="Normal";
276         if      (comfort == 4)   comfort_str = "Comfortable";
277         else if (comfort == 8)   comfort_str = "Dry";
278         else if (comfort == 0xc) comfort_str = "Humid";
279         unsigned int forecast = msg[9]>>4;
280         char *forecast_str="Cloudy";
281         if      (forecast == 3)   forecast_str = "Rainy";
282         else if (forecast == 6)   forecast_str = "Partly Cloudy";
283         else if (forecast == 0xc) forecast_str = "Sunny";
284         float temp_c = get_os_temperature(msg, sensor_id);
285         // fprintf(stdout,"Weather Sensor BHTR968  Indoor    Temp: %3.1fC  %3.1fF   Humidity: %d%%", temp_c, ((temp_c*9)/5)+32, get_os_humidity(msg, sensor_id));
286         // fprintf(stdout, " (%s) Pressure: %dmbar (%s)\n", comfort_str, ((msg[7] & 0x0f) | (msg[8] & 0xf0))+856, forecast_str);
287         data = data_make(
288             "time",       "",               DATA_STRING, time_str,
289             "brand",      "",               DATA_STRING, "OS",
290             "model",      "",               DATA_STRING, "BHTR968",
291             "id",         "House Code",     DATA_INT,    get_os_rollingcode(msg, sensor_id),
292             "channel",    "Channel",        DATA_INT,    get_os_channel(msg, sensor_id),
293             "battery",    "Battery",        DATA_STRING, get_os_battery(msg, sensor_id) ? "LOW" : "OK",
294             "temperature_C",  "Celcius",    DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp_c,
295             "temperature_F",  "Fahrenheit", DATA_FORMAT, "%.02f F", DATA_DOUBLE, ((temp_c*9)/5)+32,
296             "humidity",   "Humidity",       DATA_FORMAT, "%u %%",   DATA_INT,    get_os_humidity(msg, sensor_id),
297             "pressure",   "Pressure",       DATA_FORMAT, "%d mbar",   DATA_INT,    ((msg[7] & 0x0f) | (msg[8] & 0xf0))+856,
298             NULL);
299         data_acquired_handler(data);
300       }
301       return 1;
302     } else if (sensor_id == ID_RGR968) {
303       if (validate_os_v2_message(msg, 161, num_valid_v2_bits, 16) == 0) {
304         float rain_rate = (((msg[4] &0x0f)*100)+((msg[4]>>4)*10) + ((msg[5]>>4)&0x0f)) /10.0F;
305         float total_rain = (((msg[7]&0xf)*10000)+((msg[7]>>4)*1000) + ((msg[6]&0xf)*100)+((msg[6]>>4)*10) + (msg[5]&0xf))/10.0F;
306
307         data = data_make(
308             "time",       "",           DATA_STRING, time_str,
309             "brand",      "",           DATA_STRING, "OS",
310             "model",      "",           DATA_STRING, "RGR968",
311             "id",         "House Code", DATA_INT,    get_os_rollingcode(msg, sensor_id),
312             "channel",    "Channel",    DATA_INT,    get_os_channel(msg, sensor_id),
313             "battery",    "Battery",    DATA_STRING, get_os_battery(msg, sensor_id) ? "LOW" : "OK",
314             "rain_rate",  "Rain Rate",  DATA_FORMAT, "%.02f mm/hr", DATA_DOUBLE, rain_rate,
315             "total_rain", "Total Rain", DATA_FORMAT, "%.02f mm", DATA_DOUBLE, total_rain,
316             NULL);
317         data_acquired_handler(data);
318       }
319       return 1;
320     } else if (sensor_id == ID_THR228N && num_valid_v2_bits==153) {
321       if (validate_os_v2_message(msg, 153, num_valid_v2_bits, 12) == 0) {
322
323         float temp_c = get_os_temperature(msg, sensor_id);
324         data = data_make(
325             "time",          "",            DATA_STRING, time_str,
326             "brand",         "",            DATA_STRING, "OS",
327             "model",         "",            DATA_STRING, "THR228N",
328             "id",            "House Code",  DATA_INT,    get_os_rollingcode(msg, sensor_id),
329             "channel",       "Channel",     DATA_INT,    get_os_channel(msg, sensor_id),
330             "battery",       "Battery",     DATA_STRING, get_os_battery(msg, sensor_id) ? "LOW" : "OK",
331             "temperature_C",  "Celcius",    DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp_c,
332             "temperature_F",  "Fahrenheit", DATA_FORMAT, "%.02f F", DATA_DOUBLE, ((temp_c*9)/5)+32,
333             NULL);
334         data_acquired_handler(data);
335       }
336       return 1;
337     } else if (sensor_id == ID_THN132N && num_valid_v2_bits==129) {
338       if (validate_os_v2_message(msg, 129, num_valid_v2_bits, 12) == 0) {
339
340         float temp_c = get_os_temperature(msg, sensor_id);
341         data = data_make(
342             "time",          "",            DATA_STRING, time_str,
343             "brand",         "",            DATA_STRING, "OS",
344             "model",         "",            DATA_STRING, "THN132N",
345             "id",            "House Code",  DATA_INT,    get_os_rollingcode(msg, sensor_id),
346             "channel",       "Channel",     DATA_INT,    get_os_channel(msg, sensor_id),
347             "battery",       "Battery",     DATA_STRING, get_os_battery(msg, sensor_id) ? "LOW" : "OK",
348             "temperature_C",  "Celcius",    DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp_c,
349             "temperature_F",  "Fahrenheit", DATA_FORMAT, "%.02f F", DATA_DOUBLE, ((temp_c*9)/5)+32,
350             NULL);
351         data_acquired_handler(data);
352       }
353       return 1;
354     } else if ((sensor_id & 0x0fff) == ID_RTGN318) {
355       if (num_valid_v2_bits==153 && (validate_os_v2_message(msg, 153, num_valid_v2_bits, 15) == 0)) {
356         float temp_c = get_os_temperature(msg, sensor_id);
357         data = data_make(
358             "time",          "",            DATA_STRING, time_str,
359             "brand",         "",            DATA_STRING, "OS",
360             "model",         "",            DATA_STRING, "RTGN318",
361             "id",            "House Code",  DATA_INT,    get_os_rollingcode(msg, sensor_id),
362             "channel",       "Channel",     DATA_INT,    get_os_channel(msg, sensor_id), // 1 to 5
363             "battery",       "Battery",     DATA_STRING, get_os_battery(msg, sensor_id) ? "LOW" : "OK",
364             "temperature_C",  "Celcius",    DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp_c,
365             "temperature_F",  "Fahrenheit", DATA_FORMAT, "%.02f F", DATA_DOUBLE, ((temp_c*9)/5)+32,
366             "humidity",      "Humidity",    DATA_FORMAT, "%u %%",   DATA_INT,    get_os_humidity(msg, sensor_id),
367             NULL);
368         data_acquired_handler(data);
369       } else if (num_valid_v2_bits==201 && (validate_os_v2_message(msg, 201, num_valid_v2_bits, 21) == 0)) {
370
371         // RF Clock message ??
372       }
373       return 1;
374     } else if (sensor_id  == ID_THN129) {
375       if ((validate_os_v2_message(msg, 137, num_valid_v2_bits, 12) == 0)) {
376         float temp_c = get_os_temperature(msg, sensor_id);
377         data = data_make(
378             "time",          "",            DATA_STRING, time_str,
379             "brand",         "",            DATA_STRING, "OS",
380             "model",         "",            DATA_STRING, "THN129",
381             "id",            "House Code",  DATA_INT,    get_os_rollingcode(msg, sensor_id),
382             "channel",       "Channel",     DATA_INT,    get_os_channel(msg, sensor_id), // 1 to 5
383             "battery",       "Battery",     DATA_STRING, get_os_battery(msg, sensor_id) ? "LOW" : "OK",
384             "temperature_C",  "Celcius",    DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp_c,
385             NULL);
386         data_acquired_handler(data);
387       }
388        
389       return 1;
390     } else if (sensor_id  == ID_BTHGN129) {
391       //if ((validate_os_v2_message(msg, 137, num_valid_v2_bits, 12) == 0)) {
392         float temp_c = get_os_temperature(msg, sensor_id);
393         float pressure = get_os_pressure(msg, sensor_id);
394         data = data_make(
395             "time",          "",            DATA_STRING, time_str,
396             "brand",         "",            DATA_STRING, "OS",
397             "model",         "",            DATA_STRING, "BTHGN129",
398             "id",            "House Code",  DATA_INT,    get_os_rollingcode(msg, sensor_id),
399             "channel",       "Channel",     DATA_INT,    get_os_channel(msg, sensor_id), // 1 to 5
400             "battery",       "Battery",     DATA_STRING, get_os_battery(msg, sensor_id) ? "LOW" : "OK",
401             "temperature_C",  "Celcius",    DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp_c,
402             "humidity",       "Humidity",   DATA_FORMAT, "%u %%", DATA_INT, get_os_humidity(msg, sensor_id),
403             "pressure",       "Pressure",   DATA_FORMAT, "%.02f mbar", DATA_DOUBLE,pressure,
404             NULL);
405         data_acquired_handler(data);
406       //}
407        
408       return 1;
409     }else if (num_valid_v2_bits > 16) {
410       if(debug_output) {
411         fprintf(stdout, "%d bit message received from unrecognized Oregon Scientific v2.1 sensor with device ID %x.\n", num_valid_v2_bits, sensor_id);
412         fprintf(stdout, "Message: "); for (i=0 ; i<20 ; i++) fprintf(stdout, "%02x ", msg[i]); fprintf(stdout,"\n");
413       }
414     } else {
415       if(debug_output) {
416         fprintf(stdout, "\nPossible Oregon Scientific v2.1 message, but sync nibble wasn't found\n"); fprintf(stdout, "Raw Data: "); for (i=0 ; i<BITBUF_COLS ; i++) fprintf(stdout, "%02x ", bb[0][i]); fprintf(stdout,"\n\n");
417       }
418     }
419   } else {
420     if (bb[0][3] != 0) {
421       if(debug_output) {
422         int i;
423         fprintf(stdout, "\nBadly formatted OS v2.1 message encountered.\n");
424         for (i=0 ; i<BITBUF_COLS ; i++)
425           fprintf(stdout, "%02x ", bb[0][i]);
426         fprintf(stdout,"\n\n");
427       }
428     }
429 }
430 return 0;
431 }
432
433 static int oregon_scientific_v3_parser(bitbuffer_t *bitbuffer) {
434   bitrow_t *bb = bitbuffer->bb;
435   data_t *data;
436   char time_str[LOCAL_TIME_BUFLEN];
437   local_time_str(0, time_str);
438
439   // Check stream for possible Oregon Scientific v3 protocol data (skip part of first and last bytes to get past sync/startup bit errors)
440   if ((((bb[0][0]&0xf) == 0x0f) && (bb[0][1] == 0xff) && ((bb[0][2]&0xc0) == 0xc0)) ||
441       (((bb[0][0]&0xf) == 0x00) && (bb[0][1] == 0x00) && ((bb[0][2]&0xc0) == 0x00))) {
442     int i,j;
443     unsigned char msg[BITBUF_COLS] = {0};
444     unsigned int sync_test_val = (bb[0][2]<<24) | (bb[0][3]<<16) | (bb[0][4]<<8);
445     int dest_bit = 0;
446     int pattern_index;
447     // Could be extra/dropped bits in stream.  Look for sync byte at expected position +/- some bits in either direction
448     for(pattern_index=0; pattern_index<16; pattern_index++) {
449       unsigned int     mask = (unsigned int)(0xfff00000>>pattern_index);
450       unsigned int  pattern = (unsigned int)(0xffa00000>>pattern_index);
451       unsigned int pattern2 = (unsigned int)(0xff500000>>pattern_index);
452       unsigned int pattern3 = (unsigned int)(0x00500000>>pattern_index);
453       unsigned int pattern4 = (unsigned int)(0x04600000>>pattern_index);
454       //fprintf(stdout, "OS v3 Sync nibble search - test_val=%08x pattern=%08x  mask=%08x\n", sync_test_val, pattern, mask);
455       if (((sync_test_val & mask) == pattern)  || ((sync_test_val & mask) == pattern2) ||
456           ((sync_test_val & mask) == pattern3) || ((sync_test_val & mask) == pattern4)) {
457         // Found sync byte - start working on decoding the stream data.
458         // pattern_index indicates  where sync nibble starts, so now we can find the start of the payload
459         int start_byte = 3 + (pattern_index>>3);
460         int start_bit = (pattern_index+4) & 0x07;
461         //fprintf(stdout, "Oregon Scientific v3 Sync test val %08x ok, starting decode at byte index %d bit %d\n", sync_test_val, start_byte, start_bit);
462         j = start_bit;
463         for (i=start_byte;i<BITBUF_COLS;i++) {
464           while (j<8) {
465             unsigned char bit_val = ((bb[0][i] & (0x80 >> j)) >> (7-j));
466
467             // copy every  bit from source stream to dest packet
468             msg[dest_bit>>3] |= (((bb[0][i] & (0x80 >> j)) >> (7-j)) << (7-(dest_bit & 0x07)));
469
470             //fprintf(stdout,"i=%d j=%d dest_bit=%02x bb=%02x msg=%02x\n",i, j, dest_bit, bb[0][i], msg[dest_bit>>3]);
471             if ((dest_bit & 0x07) == 0x07) {
472               // after assembling each dest byte, flip bits in each nibble to convert from lsb to msb bit ordering
473               int k = (dest_bit>>3);
474               unsigned char indata = msg[k];
475               // flip the 4 bits in the upper and lower nibbles
476               msg[k] = ((indata & 0x11) << 3) | ((indata & 0x22) << 1) |
477                 ((indata & 0x44) >> 1) | ((indata & 0x88) >> 3);
478             }
479             dest_bit++;
480             j++;
481           }
482           j=0;
483         }
484         break;
485       }
486     }
487     int sensor_id = (msg[0] << 8) | msg[1];
488     if (sensor_id == ID_THGR810)    {
489       if (validate_os_checksum(msg, 15) == 0) {
490         float temp_c = get_os_temperature(msg, sensor_id);
491         int humidity = get_os_humidity(msg, sensor_id);
492         data = data_make(
493           "time",           "",           DATA_STRING, time_str,
494           "brand",          "",           DATA_STRING, "OS",
495           "model",          "",           DATA_STRING, "THGR810",
496           "id",             "House Code", DATA_INT,    get_os_rollingcode(msg, sensor_id),
497           "channel",        "Channel",    DATA_INT,    get_os_channel(msg, sensor_id),
498           "battery",        "Battery",    DATA_STRING, get_os_battery(msg, sensor_id)?"LOW":"OK",
499           "temperature_C",  "Celcius",    DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp_c,
500           "temperature_F",  "Fahrenheit", DATA_FORMAT, "%.02f F", DATA_DOUBLE, ((temp_c*9)/5)+32,
501           "humidity",       "Humidity",   DATA_FORMAT, "%u %%", DATA_INT, humidity,
502           NULL);
503         data_acquired_handler(data);
504       }
505       return 1;                  //msg[k] = ((msg[k] & 0x0F) << 4) + ((msg[k] & 0xF0) >> 4);
506     } else if (sensor_id == ID_THN802)    {
507         if (validate_os_checksum(msg, 12) == 0) {
508           float temp_c = get_os_temperature(msg, sensor_id);
509           data = data_make(
510             "time",           "",           DATA_STRING, time_str,
511             "brand",          "",           DATA_STRING, "OS",
512             "model",          "",           DATA_STRING, "THN802",
513             "id",             "House Code", DATA_INT,    get_os_rollingcode(msg, sensor_id),
514             "channel",        "Channel",    DATA_INT,    get_os_channel(msg, sensor_id),
515             "battery",        "Battery",    DATA_STRING, get_os_battery(msg, sensor_id)?"LOW":"OK",
516             "temperature_C",  "Celcius",    DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp_c,
517             "temperature_F",  "Fahrenheit", DATA_FORMAT, "%.02f F", DATA_DOUBLE, ((temp_c*9)/5)+32,
518             NULL);
519           data_acquired_handler(data);
520         }
521         return 1;
522     } else if (sensor_id == ID_UV800) {
523       if (validate_os_checksum(msg, 13) == 0) {   // ok
524         int uvidx = get_os_uv(msg, sensor_id);
525         data = data_make(
526           "time",           "",           DATA_STRING, time_str,
527           "brand",          "",           DATA_STRING, "OS",
528           "model",          "",           DATA_STRING, "UV800",
529           "id",             "House Code", DATA_INT,    get_os_rollingcode(msg, sensor_id),
530           "channel",        "Channel",    DATA_INT,    get_os_channel(msg, sensor_id),
531           "battery",        "Battery",    DATA_STRING, get_os_battery(msg, sensor_id)?"LOW":"OK",
532           "uv",             "UV Index",   DATA_FORMAT, "%u", DATA_INT, uvidx,
533           NULL);
534         data_acquired_handler(data);
535       }
536     } else if (sensor_id == ID_PCR800) {
537       if (validate_os_checksum(msg, 18) == 0) {
538         float rain_rate=get_os_rain_rate(msg, sensor_id);
539         float total_rain=get_os_total_rain(msg, sensor_id);
540         data = data_make(
541           "time",       "",           DATA_STRING, time_str,
542           "brand",      "",           DATA_STRING, "OS",
543           "model",      "",           DATA_STRING, "PCR800",
544           "id",         "House Code", DATA_INT,    get_os_rollingcode(msg, sensor_id),
545           "channel",    "Channel",    DATA_INT,    get_os_channel(msg, sensor_id),
546           "battery",    "Battery",    DATA_STRING, get_os_battery(msg, sensor_id)?"LOW":"OK",
547           "rain_rate",  "Rain Rate",  DATA_FORMAT, "%3.1f in/hr", DATA_DOUBLE, rain_rate,
548           "rain_total", "Total Rain", DATA_FORMAT, "%3.1f in", DATA_DOUBLE, total_rain,
549           NULL);
550         data_acquired_handler(data);
551         }
552         return 1;
553 } else if (sensor_id == ID_PCR800a) {
554   if (validate_os_checksum(msg, 18) == 0) {
555     float rain_rate=get_os_rain_rate(msg, sensor_id);
556     float total_rain=get_os_total_rain(msg, sensor_id);
557     data = data_make(
558       "time",       "",           DATA_STRING, time_str,
559       "brand",      "",           DATA_STRING, "OS",
560       "model",      "",           DATA_STRING, "PCR800a",
561       "id",         "House Code", DATA_INT,    get_os_rollingcode(msg, sensor_id),
562       "channel",    "Channel",    DATA_INT,    get_os_channel(msg, sensor_id),
563       "battery",    "Battery",    DATA_STRING, get_os_battery(msg, sensor_id)?"LOW":"OK",
564       "rain_rate",  "Rain Rate",  DATA_FORMAT, "%3.1f in/hr", DATA_DOUBLE, rain_rate,
565       "rain_total", "Total Rain", DATA_FORMAT, "%3.1f in", DATA_DOUBLE, total_rain,
566       NULL);
567     data_acquired_handler(data);
568     }
569 return 1;
570     } else if (sensor_id == ID_WGR800) {
571       if (validate_os_checksum(msg, 17) == 0) {
572         float gustWindspeed = (msg[5]&0x0f) /10.0F + ((msg[6]>>4)&0x0f) *1.0F + (msg[6]&0x0f) * 10.0F;
573         float avgWindspeed = ((msg[7]>>4)&0x0f) / 10.0F + (msg[7]&0x0f) *1.0F + ((msg[8]>>4)&0x0f) * 10.0F;
574         float quadrant = (0x0f&(msg[4]>>4))*22.5F;
575         data = data_make(
576           "time",       "",           DATA_STRING,  time_str,
577           "brand",      "",           DATA_STRING,  "OS",
578           "model",      "",           DATA_STRING,  "WGR800",
579           "id",         "House Code", DATA_INT,     get_os_rollingcode(msg, sensor_id),
580           "channel",    "Channel",    DATA_INT,     get_os_channel(msg, sensor_id),
581           "battery",    "Battery",    DATA_STRING,  get_os_battery(msg, sensor_id)?"LOW":"OK",
582           "gust",       "Gust",       DATA_FORMAT,  "%2.1f m/s",DATA_DOUBLE, gustWindspeed,
583           "average",    "Average",    DATA_FORMAT,  "%2.1f m/s",DATA_DOUBLE, avgWindspeed,
584           "direction",  "Direction",  DATA_FORMAT,  "%3.1f degrees",DATA_DOUBLE, quadrant,
585           NULL);
586         data_acquired_handler(data);
587       }
588       return 1;
589     } else if ((msg[0] == 0x20) || (msg[0] == 0x21) || (msg[0] == 0x22) || (msg[0] == 0x23) || (msg[0] == 0x24)) { //  Owl CM160 Readings
590       msg[0]=msg[0] & 0x0f;
591       if (validate_os_checksum(msg, 22) == 0) {
592         float rawAmp = (msg[4] >> 4 << 8 | (msg[3] & 0x0f )<< 4 | msg[3] >> 4);
593         unsigned short int ipower = (rawAmp /(0.27*230)*1000);
594         data = data_make(
595             "time",   "",           DATA_STRING,  time_str,
596             "brand",  "",           DATA_STRING, "OS",
597             "model",  "",           DATA_STRING,  "CM160",
598             "id",     "House Code", DATA_INT, msg[1]&0x0F,
599             "power",  "Power",      DATA_FORMAT,  "%d W", DATA_INT, ipower,
600             NULL);
601           data_acquired_handler(data);
602       }
603     } else if (msg[0] == 0x26) { //  Owl CM180 readings
604         msg[0]=msg[0] & 0x0f;
605         int valid = validate_os_checksum(msg, 23);
606         int k;
607         for (k=0; k<BITBUF_COLS;k++) {  // Reverse nibbles
608             msg[k] = (msg[k] & 0xF0) >> 4 |  (msg[k] & 0x0F) << 4;
609         }
610         unsigned short int ipower = power(msg);
611         unsigned long long itotal = total(msg);
612         float total_energy = itotal/3600/1000.0;
613         if (itotal && valid == 0) {
614             data = data_make(
615               "time",       "",           DATA_STRING,  time_str,
616               "brand",      "",           DATA_STRING, "OS",
617               "model",      "",           DATA_STRING,  "CM180",
618               "id",         "House Code", DATA_INT, msg[1]&0x0F,
619               "power",      "Power",      DATA_FORMAT,  "%d W",DATA_INT, ipower,
620               "energy_kWh", "Energy",     DATA_FORMAT,  "%2.1f kWh",DATA_DOUBLE, total_energy,
621               NULL);
622             data_acquired_handler(data);
623         } else if (!itotal) {
624             data = data_make(
625               "time",   "",           DATA_STRING,  time_str,
626               "brand",  "",           DATA_STRING, "OS",
627               "model",  "",           DATA_STRING,  "CM180",
628               "id",     "House Code", DATA_INT, msg[1]&0x0F,
629               "power",  "Power",      DATA_FORMAT,  "%d W",DATA_INT, ipower,
630               NULL);
631             data_acquired_handler(data);
632         }
633     } else if ((msg[0] != 0) && (msg[1]!= 0)) { //  sync nibble was found  and some data is present...
634       if(debug_output) {
635         fprintf(stderr, "Message received from unrecognized Oregon Scientific v3 sensor.\n");
636         fprintf(stderr, "Message: "); for (i=0 ; i<BITBUF_COLS ; i++) fprintf(stdout, "%02x ", msg[i]); fprintf(stdout, "\n");
637         fprintf(stderr, "    Raw: "); for (i=0 ; i<BITBUF_COLS ; i++) fprintf(stdout, "%02x ", bb[0][i]); fprintf(stdout,"\n");
638       }
639     } else if (bb[0][3] != 0 ) {
640       if(debug_output) {
641         fprintf(stdout, "\nPossible Oregon Scientific v3 message, but sync nibble wasn't found\n");
642         fprintf(stdout, "Raw Data: "); for (i=0 ; i<BITBUF_COLS ; i++) fprintf(stdout, "%02x ", bb[0][i]); fprintf(stdout,"\n\n");
643       }
644     }
645   }
646   else { // Based on first couple of bytes, either corrupt message or something other than an Oregon Scientific v3 message
647     if(debug_output) {
648       if (bb[0][3] != 0) { fprintf(stdout, "\nUnrecognized Msg in v3: "); int i; for (i=0 ; i<BITBUF_COLS ; i++) fprintf(stdout, "%02x ", bb[0][i]); fprintf(stdout,"\n\n"); }
649     }
650   }
651   return 0;
652 }
653
654 static int oregon_scientific_callback(bitbuffer_t *bitbuffer) {
655   int ret = oregon_scientific_v2_1_parser(bitbuffer);
656   if (ret == 0)
657     ret = oregon_scientific_v3_parser(bitbuffer);
658   return ret;
659 }
660
661 static char *output_fields[] = {
662   "time",
663   "model",
664   "id",
665   "channel",
666   "battery",
667   "temperature_C",
668   "humidity",
669   "rain_rate",
670   "rain_total",
671   NULL
672 };
673
674 r_device oregon_scientific = {
675   .name           = "Oregon Scientific Weather Sensor",
676   .modulation     = OOK_PULSE_MANCHESTER_ZEROBIT,
677   .short_limit    = 440, // Nominal 1024Hz (488µs), but pulses are shorter than pauses
678   .long_limit     = 0, // not used
679   .reset_limit    = 2400,
680   .json_callback  = &oregon_scientific_callback,
681   .disabled       = 0,
682   .demod_arg      = 0,
683   .fields         = output_fields
684 };