4 /// Documentation for Oregon Scientific protocols can be found here:
5 /// http://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf
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
25 float get_os_temperature(unsigned char *message, unsigned int sensor_id) {
26 // sensor ID included to support sensors with temp in different position
28 temp_c = (((message[5]>>4)*100)+((message[4]&0x0f)*10) + ((message[4]>>4)&0x0f)) / 10.0F;
29 if (message[5] & 0x0f)
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;
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;
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
50 humidity = ((message[6]&0x0f)*10)+(message[6]>>4);
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
58 fprintf(stdout," raw pressure data : %02x %02x\n",(int)message[8],(int)message[7]);
60 for (i=0; message[i];i++)
62 fprintf(stdout,"%d %02x ",i,(int)message[i]);
64 fprintf(stdout,"\n");*/
67 pressure = ((message[8]<<4)+message[7])/100.0F/0.0295299830714;
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
75 uvidx = ((message[4]&0x0f)*10)+(message[4]>>4);
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
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
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
91 battery_low = (message[3] >> 2 & 0x01);
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
98 rc = (message[2]&0x0F) + (message[3]&0xF0);
102 unsigned short int power(const unsigned char* d) {
103 unsigned short int val = 0;
104 val = ( d[4] << 8) + d[3];
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;
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.
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));
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);
137 checksum = (msg[checksum_nibble_idx>>1]>>4) | ((msg[checksum_nibble_idx>>1]&0x0f)<<4);
139 sum_of_nibbles &= 0xff;
141 if (sum_of_nibbles == checksum) {
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");
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));
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");
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))) {
172 unsigned char msg[BITBUF_COLS] = {0};
174 // Possible v2.1 Protocol message
175 int num_valid_v2_bits = 0;
177 unsigned int sync_test_val = (bb[0][3]<<24) | (bb[0][4]<<16) | (bb[0][5]<<8) | (bb[0][6]);
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);
186 //fprintf(stdout, "OS v2.1 sync byte search - test_val=%08x pattern=%08x mask=%08x\n", sync_test_val, pattern, mask);
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;
198 for (i=start_byte;i<BITBUF_COLS;i++) {
200 if (bits_processed & 0x01) {
201 unsigned char bit_val = ((bb[0][i] & (0x80 >> j)) >> (7-j));
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;
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)));
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);
222 last_bit_val = ((bb[0][i] & (0x80 >> j)) >> (7-j)); // used for v2.1 bit error detection
230 } //if (sync_test_val...
234 char time_str[LOCAL_TIME_BUFLEN];
235 local_time_str(0, time_str);
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) {
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),
250 data_acquired_handler(data);
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;
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,
269 data_acquired_handler(data);
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);
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,
299 data_acquired_handler(data);
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;
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,
317 data_acquired_handler(data);
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) {
323 float temp_c = get_os_temperature(msg, sensor_id);
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,
334 data_acquired_handler(data);
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) {
340 float temp_c = get_os_temperature(msg, sensor_id);
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,
351 data_acquired_handler(data);
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);
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),
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)) {
371 // RF Clock message ??
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);
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,
386 data_acquired_handler(data);
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);
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,
405 data_acquired_handler(data);
409 }else if (num_valid_v2_bits > 16) {
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");
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");
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");
433 static int oregon_scientific_v3_parser(bitbuffer_t *bitbuffer) {
434 bitrow_t *bb = bitbuffer->bb;
436 char time_str[LOCAL_TIME_BUFLEN];
437 local_time_str(0, time_str);
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))) {
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);
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);
463 for (i=start_byte;i<BITBUF_COLS;i++) {
465 unsigned char bit_val = ((bb[0][i] & (0x80 >> j)) >> (7-j));
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)));
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);
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);
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,
503 data_acquired_handler(data);
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);
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,
519 data_acquired_handler(data);
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);
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,
534 data_acquired_handler(data);
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);
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,
550 data_acquired_handler(data);
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);
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,
567 data_acquired_handler(data);
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;
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,
586 data_acquired_handler(data);
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);
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,
601 data_acquired_handler(data);
603 } else if (msg[0] == 0x26) { // Owl CM180 readings
604 msg[0]=msg[0] & 0x0f;
605 int valid = validate_os_checksum(msg, 23);
607 for (k=0; k<BITBUF_COLS;k++) { // Reverse nibbles
608 msg[k] = (msg[k] & 0xF0) >> 4 | (msg[k] & 0x0F) << 4;
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) {
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,
622 data_acquired_handler(data);
623 } else if (!itotal) {
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,
631 data_acquired_handler(data);
633 } else if ((msg[0] != 0) && (msg[1]!= 0)) { // sync nibble was found and some data is present...
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");
639 } else if (bb[0][3] != 0 ) {
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");
646 else { // Based on first couple of bytes, either corrupt message or something other than an Oregon Scientific v3 message
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"); }
654 static int oregon_scientific_callback(bitbuffer_t *bitbuffer) {
655 int ret = oregon_scientific_v2_1_parser(bitbuffer);
657 ret = oregon_scientific_v3_parser(bitbuffer);
661 static char *output_fields[] = {
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
680 .json_callback = &oregon_scientific_callback,
683 .fields = output_fields