static int ht680_callback(bitbuffer_t *bitbuffer) { bitrow_t *bb = bitbuffer->bb; data_t *data; for (uint8_t row = 0;row < bitbuffer->num_rows;row++){ uint8_t *b = bb[row]; if(bitbuffer->bits_per_row[row] == 40 && //Length of packet is 40 (b[0] & 0x50) == 0x50 && //Sync mask 01010000 (b[1] & 0x0A) == 0x0A && //Address always mask 00001010 (b[3] & 0x82) == 0x82 && //Buttons(4,3) always mask 10000010 (b[4] & 0x0A) == 0x0A){ //Buttons(2,1) always mask 00001010 b[0] = b[0] & 0x0F; //Clear sync data = data_make("model", "", DATA_STRING, "HT680 Remote control", "addres", "Addres code", DATA_FORMAT, "%06X", DATA_INT, (b[0]<<16)+(b[1]<<8)+b[2], "button1", "Button 1", DATA_STRING, (((b[4]>>4) & 0x03) == 3) ? "PRESSED" : "", "button2", "Button 2", DATA_STRING, (((b[4]>>6) & 0x03) == 3) ? "PRESSED" : "", "button3", "Button 3", DATA_STRING, ((((b[3]&0x7D)>>2) & 0x03) == 3) ? "PRESSED" : "", "button4", "Button 4", DATA_STRING, ((((b[3]&0x7D)>>4) & 0x03) == 3) ? "PRESSED" : "", NULL); data_acquired_handler(data); return 1; } }
static int thermopro_tp12_sensor_callback(bitbuffer_t *bitbuffer) { int iTemp1, iTemp2, good = -1; float fTemp1, fTemp2; uint8_t *bytes; unsigned int device, value; char time_str[LOCAL_TIME_BUFLEN]; data_t *data; // The device transmits 16 rows, let's check for 3 matching. // (Really 17 rows, but the last one doesn't match because it's missing a trailing 1.) good = bitbuffer_find_repeated_row(bitbuffer, 5, 40); if (good < 0) { return 0; } bytes = bitbuffer->bb[good]; if (!bytes[0] && !bytes[1] && !bytes[2] && !bytes[3]) { return 0; // reduce false positives } // Note: the device ID changes randomly each time you replace the battery, so we can't early out based on it. // This is probably to allow multiple devices to be used at once. When you replace the receiver batteries // or long-press its power button, it pairs with the first device ID it hears. device = bytes[0]; if(debug_output) { // There is a mysterious checksum in bytes[4]. It may be the same as the checksum used by the TP-11, // which consisted of a lookup table containing, for each bit in the message, a byte to be xor-ed into // the checksum if the message bit was 1. It should be possible to solve for that table using Gaussian // elimination, so dump some data so we can try this. // This format is easily usable by bruteforce-crc, after piping through | grep raw_data | cut -d':' -f2 // bruteforce-crc didn't find anything, though - this may not be a CRC algorithm specifically. fprintf(stderr,"thermopro_tp12_raw_data:"); for(int bit_index = 0; bit_index < 40; ++bit_index){ fputc(bitrow_get_bit(bytes, bit_index) + '0', stderr); } fputc('\n', stderr); } iTemp1 = ((bytes[2] & 0xf0) << 4) | bytes[1]; iTemp2 = ((bytes[2] & 0x0f) << 8) | bytes[3]; fTemp1 = (iTemp1 - 200) / 10.; fTemp2 = (iTemp2 - 200) / 10.; local_time_str(0, time_str); data = data_make("time", "", DATA_STRING, time_str, "model", "", DATA_STRING, MODEL, "id", "Id", DATA_FORMAT, "\t %d", DATA_INT, device, "temperature_1_C", "Temperature 1 (Food)", DATA_FORMAT, "%.01f C", DATA_DOUBLE, fTemp1, "temperature_2_C", "Temperature 2 (Barbecue)", DATA_FORMAT, "%.01f C", DATA_DOUBLE, fTemp2, NULL); data_acquired_handler(data); return 1; }
static int current_cost_callback(bitbuffer_t *bitbuffer) { bitbuffer_invert(bitbuffer); bitrow_t *bb = bitbuffer->bb; uint8_t *b = bb[0]; char time_str[LOCAL_TIME_BUFLEN]; data_t *data; local_time_str(0, time_str); uint8_t init_pattern[] = { 0b11001100, //8 0b11001100, //16 0b11001100, //24 0b11001110, //32 0b10010001, //40 0b01011101, //45 (! last 3 bits is not init) }; unsigned int start_pos = bitbuffer_search(bitbuffer, 0, 0, init_pattern, 45); if(start_pos == bitbuffer->bits_per_row[0]){ return 0; } start_pos += 45; bitbuffer_t packet_bits = {0}; start_pos = bitbuffer_manchester_decode(bitbuffer, 0, start_pos, &packet_bits, 0); uint8_t *packet = packet_bits.bb[0]; // Read data if(packet_bits.bits_per_row[0] >= 56 && ((packet[0] & 0xf0) == 0) ){ uint16_t device_id = (packet[0] & 0x0f) << 8 | packet[1]; uint16_t watt0 = (packet[2] & 0x7F) << 8 | packet[3] ; uint16_t watt1 = (packet[4] & 0x7F) << 8 | packet[5] ; uint16_t watt2 = (packet[6] & 0x7F) << 8 | packet[7] ; data = data_make("time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "CurrentCost TX", //TODO: it may have different CC Model ? any ref ? //"rc", "Rolling Code", DATA_INT, rc, //TODO: add rolling code b[1] ? test needed "dev_id", "Device Id", DATA_FORMAT, "%d", DATA_INT, device_id, "power0", "Power 0", DATA_FORMAT, "%d W", DATA_INT, watt0, "power1", "Power 1", DATA_FORMAT, "%d W", DATA_INT, watt1, "power2", "Power 2", DATA_FORMAT, "%d W", DATA_INT, watt2, //"battery", "Battery", DATA_STRING, battery_low ? "LOW" : "OK", //TODO is there some low battery indicator ? NULL); data_acquired_handler(data); return 1; } return 0; }
static int waveman_callback(bitbuffer_t *bitbuffer) { uint8_t *b = bitbuffer->bb[0]; /* Two bits map to 2 states, 0 1 -> 0 and 1 1 -> 1 */ int i; uint8_t nb[3] = {0}; data_t *data; char id_str[2]; /* @todo iterate through all rows */ /* Reject codes of wrong length */ if ( 24 != bitbuffer->bits_per_row[0]) return 0; /* * Catch the case triggering false positive for other transmitters. * example: Brennstuhl RCS 2044SN * @todo is this message valid at all??? if not then put more validation below * instead of this special case */ if ( 0xFF == b[0] && 0xFF == b[1] && 0xFF == b[2] ) return 0; /* Test if the bit stream conforms to the rule of every odd bit being set to one */ if (((b[0]&0x55)==0x55) && ((b[1]&0x55)==0x55) && ((b[2]&0x55)==0x55) && ((b[3]&0x55)==0x00)) { /* Extract data from the bit stream */ for (i=0 ; i<3 ; i++) { nb[i] |= ((b[i]&0xC0)==0xC0) ? 0x00 : 0x01; nb[i] |= ((b[i]&0x30)==0x30) ? 0x00 : 0x02; nb[i] |= ((b[i]&0x0C)==0x0C) ? 0x00 : 0x04; nb[i] |= ((b[i]&0x03)==0x03) ? 0x00 : 0x08; } id_str[0] = 'A'+nb[0]; id_str[1] = 0; data = data_make("model", NULL, DATA_STRING, "Waveman Switch Transmitter", "id", NULL, DATA_STRING, id_str, "channel", NULL, DATA_INT, (nb[1]>>2)+1, "button", NULL, DATA_INT, (nb[1]&3)+1, "state", NULL, DATA_STRING, (nb[2]==0xe) ? "on" : "off", NULL); data_acquired_handler(data); return 1; } return 0; }
// Acurite 609 Temperature and Humidity Sensor // 5 byte messages // II ST TT HH CC // II - ID byte, changes at each power up // S - Status bitmask, normally 0x2, // 0xa - battery low (bit 0x80) // TTT - Temp in Celsius * 10, 12 bit with complement. // HH - Humidity // CC - Checksum // // @todo - see if the 3rd nybble is battery/status // static int acurite_th_callback(bitbuffer_t *bitbuf) { uint8_t *bb = NULL; int cksum, battery_low, valid = 0; float tempc; uint8_t humidity, id, status; data_t *data; local_time_str(0, time_str); for (uint16_t brow = 0; brow < bitbuf->num_rows; ++brow) { if (bitbuf->bits_per_row[brow] != 40) { continue; } bb = bitbuf->bb[brow]; cksum = (bb[0] + bb[1] + bb[2] + bb[3]); if (cksum == 0 || ((cksum & 0xff) != bb[4])) { continue; } tempc = acurite_th_temperature(bb); id = bb[0]; status = (bb[1] & 0xf0) >> 4; battery_low = status & 0x8; humidity = bb[3]; data = data_make( "time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "Acurite 609TXC Sensor", "id", "", DATA_INT, id, "battery", "", DATA_STRING, battery_low ? "LOW" : "OK", "status", "", DATA_INT, status, "temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, tempc, "humidity", "Humidity", DATA_INT, humidity, NULL); data_acquired_handler(data); valid++; } if (valid) return 1; return 0; }
static int ht680_callback(bitbuffer_t *bitbuffer) { bitrow_t *bb = bitbuffer->bb; char time_str[LOCAL_TIME_BUFLEN]; data_t *data; for (uint8_t row = 0;row < bitbuffer->num_rows;row++){ uint8_t *b = bb[row]; if(bitbuffer->bits_per_row[row] == 40 && //Length of packet is 40 (b[0] & 0x50) == 0x50 && //Sync mask 01010000 (b[1] & 0x0A) == 0x0A && //Address always mask 00001010 (b[3] & 0x82) == 0x82 && //Buttons(4,3) always mask 10000010 (b[4] & 0x0A) == 0x0A){ //Buttons(2,1) always mask 00001010 b[0] = b[0] & 0x0F; //Clear sync // Tristate coding char tristate[21]; char *p = tristate; for(uint8_t byte = 0; byte < 5; byte++){ for(int8_t bit = 7; bit > 0; bit -= 2){ switch ((b[byte] >> (bit-1)) & 0x03){ case 0x00: *p++ = '0'; break; case 0x01: *p++ = '?'; break; //Invalid code 01 case 0x02: *p++ = 'Z'; break; //Floating state Z is 10 case 0x03: *p++ = '1'; break; default: *p++ = '!'; break; //Unknown error } } } *p = '\0'; local_time_str(0, time_str); data = data_make( "time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "HT680 Remote control", "tristate","Tristate code",DATA_STRING, tristate, "address", "Address", DATA_FORMAT, "0x%06X", DATA_INT, (b[0]<<16)+(b[1]<<8)+b[2], "button1", "Button 1", DATA_STRING, (((b[4]>>4) & 0x03) == 3) ? "PRESSED" : "", "button2", "Button 2", DATA_STRING, (((b[4]>>6) & 0x03) == 3) ? "PRESSED" : "", "button3", "Button 3", DATA_STRING, ((((b[3]&0x7D)>>2) & 0x03) == 3) ? "PRESSED" : "", "button4", "Button 4", DATA_STRING, ((((b[3]&0x7D)>>4) & 0x03) == 3) ? "PRESSED" : "", NULL); data_acquired_handler(data); return 1; } }
static int pool_temperature_sensor_callback(bitbuffer_t *bitbuffer) { bitrow_t *bb = bitbuffer->bb; data_t *data; char time_str[LOCAL_TIME_BUFLEN]; local_time_str(0, time_str); int i,device,channel; float fTemp; for(i=1;i<8;i++){ if(bitbuffer->bits_per_row[i]!=28){ /*10 24 bits frame*/ return 0; } } /* AAAABBBB BBBBCCCC CCCCCCCC DDEE A: ? B: device id (changing only after reset) C: templerature D: channel number E: ? */ device=(((bb[1][0]&0xF)<<4)+((bb[1][1]&0xF0)>>4)); fTemp=((signed short)(((bb[1][1]&0xF)<<8)+bb[1][2])/10.0); channel=(signed short)((bb[1][3]&0xC0)>>6); data = data_make("time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "TFA pool temperature sensor", "id", "Id", DATA_FORMAT, "\t %d", DATA_INT, device, "channel", "Channel number", DATA_FORMAT, "\t %d", DATA_INT, channel, "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, fTemp, NULL); data_acquired_handler(data); return 1; }
static int generic_temperature_sensor_callback(bitbuffer_t *bitbuffer) { bitrow_t *bb = bitbuffer->bb; data_t *data; char time_str[LOCAL_TIME_BUFLEN]; local_time_str(0, time_str); int i,device,battery; float fTemp; for(i=1;i<10;i++){ if(bitbuffer->bits_per_row[i]!=24){ /*10 24 bits frame*/ return 0; } } //AAAAAAAA BBCCCCCC CCCCCCCC //AAAAAAAA : ID //BBBB : battery ? //CCCCCCCCCCCC : Temp device=(bb[1][0]); battery=(bb[1][1]&0xF0)>>4; fTemp=(float)((signed short)(((bb[1][1]&0x3f)*256+bb[1][2])<<2))/160.0; data = data_make("time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "Generic temperature sensor 1", "id", "Id", DATA_FORMAT, "\t %d", DATA_INT, device, "temperature_C", "Temperature", DATA_FORMAT, "%.02f C", DATA_DOUBLE, fTemp, "battery", "Battery?", DATA_INT, battery, NULL); data_acquired_handler(data); return 1; }
static int steelmate_callback(bitbuffer_t *bitbuffer) { //if (debug_output >= 1) { // fprintf(stdout, "Steelmate TPMS decoder\n"); // bitbuffer_print(bitbuffer); // fprintf(stdout, "\n"); //} char time_str[LOCAL_TIME_BUFLEN]; local_time_str(0, time_str); bitrow_t *bb = bitbuffer->bb; //Loop through each row of data for (int i = 0; i < bitbuffer->num_rows; i++) { //Payload is inverted Manchester encoded, and reversed MSB/LSB order uint8_t preAmble, ID1, ID2, p1, tempFahrenheit, tmpbattery_mV, payload_checksum, calculated_checksum; uint16_t sensorID, battery_mV; float pressurePSI; char sensorIDhex[7]; data_t *data; //Length must be 72 bits to be considered a valid packet if (bitbuffer->bits_per_row[i] != 72) continue; //Valid preamble? (Note, the data is still wrong order at this point. Correct pre-amble: 0x00 0x00 0x01) if (bb[i][0] != 0x00 || bb[i][1] != 0x00 || bb[i][2] != 0x7f) continue; //Preamble preAmble = ~reverse8(bb[i][2]); //Sensor ID ID1 = ~reverse8(bb[i][3]); ID2 = ~reverse8(bb[i][4]); //Pressure is stored as twice the PSI p1 = ~reverse8(bb[i][5]); //Temperature is stored in Fahrenheit. Note that the datasheet claims operational to -40'C, but can only express values from -17.8'C tempFahrenheit = ~reverse8(bb[i][6]); //Battery voltage is stored as half the mV tmpbattery_mV = ~reverse8(bb[i][7]); //Checksum is a sum of all the other values payload_checksum = ~reverse8(bb[i][8]); calculated_checksum = preAmble + ID1 + ID2 + p1 + tempFahrenheit + tmpbattery_mV; if (payload_checksum != calculated_checksum) continue; sensorID = (ID1 << 8) + ID2; sprintf(sensorIDhex, "0x%04x", sensorID); pressurePSI = (float)p1 / 2; battery_mV = tmpbattery_mV * 2; data = data_make("time", "", DATA_STRING, time_str, "type", "", DATA_STRING, "TPMS", "model", "", DATA_STRING, "Steelmate", "id", "", DATA_STRING, sensorIDhex, "pressure_PSI", "", DATA_DOUBLE, pressurePSI, "temperature_F", "", DATA_DOUBLE, (float)tempFahrenheit, "battery_mV", "", DATA_INT, battery_mV, "mic", "Integrity", DATA_STRING, "CHECKSUM", NULL); data_acquired_handler(data); return 1; } //Was not a Steelmate TPMS after all return 0; }
static int oil_watchman_callback(bitbuffer_t *bitbuffer) { uint8_t *b; uint32_t unit_id; uint16_t depth = 0; uint16_t binding_countdown = 0; uint8_t flags; uint8_t maybetemp; double temperature; char time_str[LOCAL_TIME_BUFLEN]; data_t *data; unsigned bitpos = 0; bitbuffer_t databits = {0}; int events = 0; local_time_str(0, time_str); // Find a preamble with enough bits after it that it could be a complete packet while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, &preamble_pattern, 6)) + 136 <= bitbuffer->bits_per_row[0]) { // Skip the matched preamble bits to point to the data bitpos += 6; bitpos = bitbuffer_manchester_decode(bitbuffer, 0, bitpos, &databits, 64); if (databits.bits_per_row[0] != 64) continue; b = databits.bb[0]; // Check for postamble, depending on last data bit if (bitbuffer_search(bitbuffer, 0, bitpos, &postamble_pattern[b[7] & 1], 2) != bitpos) continue; if (b[7] != crc8le(b, 7, 0x31, 0)) continue; // The unit ID changes when you rebind by holding a magnet to the // sensor for long enough; it seems to be time-based. unit_id = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; // 0x01: Rebinding (magnet held to sensor) // 0x08: Leak/theft alarm // top three bits seem also to vary with temperature (independently of maybetemp) flags = b[4]; // Not entirely sure what this is but it might be inversely // proportional to temperature. maybetemp = b[5] >> 2; temperature = (double)(145.0 - 5.0 * maybetemp) / 3.0; if (flags & 1) // When binding, the countdown counts up from 0x51 to 0x5a // (as long as you hold the magnet to it for long enough) // before the device ID changes. The receiver unit needs // to receive this *strongly* in order to change its // allegiance. binding_countdown = b[6]; else // A depth reading of zero indicates no reading. Even with // the sensor flat down on a table, it still reads about 13. depth = ((b[5] & 3) << 8) | b[6]; data = data_make("time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "Oil Watchman", "id", "", DATA_FORMAT, "%06x", DATA_INT, unit_id, "flags", "", DATA_FORMAT, "%02x", DATA_INT, flags, "maybetemp", "", DATA_INT, maybetemp, "temperature_C", "", DATA_DOUBLE, temperature, "binding_countdown", "", DATA_INT, binding_countdown, "depth", "", DATA_INT, depth, NULL); data_acquired_handler(data); events++; } return events; }
static int efergy_optical_callback(bitbuffer_t *bitbuffer) { unsigned num_bits = bitbuffer->bits_per_row[0]; uint8_t *bytes = bitbuffer->bb[0]; double power, n_imp; double pulsecount; double seconds; data_t *data; char time_str[LOCAL_TIME_BUFLEN]; uint16_t crc; uint16_t csum1; if (num_bits < 64 || num_bits > 100){ return 0; } // The bit buffer isn't always aligned to the transmitted data, so // search for data start and shift out the bits which aren't part // of the data. The data always starts with 0000 (or 1111 if // gaps/pulses are mixed up). while ((bytes[0] & 0xf0) != 0xf0 && (bytes[0] & 0xf0) != 0x00) { num_bits -= 1; if (num_bits < 64) { return 0; } for (unsigned i = 0; i < (num_bits + 7) / 8; ++i) { bytes[i] <<= 1; bytes[i] |= (bytes[i + 1] & 0x80) >> 7; } } // Sometimes pulses and gaps are mixed up. If this happens, invert // all bytes to get correct interpretation. if (bytes[0] & 0xf0){ for (unsigned i = 0; i < 12; ++i) { bytes[i] = ~bytes[i]; } } if (debug_output){ fprintf(stdout,"Possible Efergy Optical: "); bitbuffer_print(bitbuffer); } // Calculate checksum for bytes[0..10] // crc16 xmodem with start value of 0x00 and polynomic of 0x1021 is same as CRC-CCITT (0x0000) // start of data, length of data=10, polynomic=0x1021, init=0x0000 csum1 = ((bytes[10]<<8)|(bytes[11])); crc = crc16_ccitt(bytes, 10, 0x1021, 0x0); if (crc == csum1) { if (debug_output) { fprintf (stdout, "Checksum OK :) :)\n"); fprintf (stdout, "Calculated crc is 0x%02X\n", crc); fprintf (stdout, "Received csum1 is 0x%02X\n", csum1); } // this setting depends on your electricity meter's optical output n_imp = 3200; pulsecount = bytes[8]; seconds = bytes[10]; //some logic for low pulse count not sure how I reached this formula if (pulsecount < 3) { power = ((pulsecount/n_imp) * (3600/seconds)); } else { power = ((pulsecount/n_imp) * (3600/30)); } /* Get time now */ local_time_str(0, time_str); data = data_make("time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "Efergy Optical", "power", "Power KWh", DATA_FORMAT,"%.03f KWh", DATA_DOUBLE, power, NULL); data_acquired_handler(data); return 0; } else { if (debug_output) { fprintf (stdout, "Checksum not OK !!!\n"); fprintf(stdout, "Calculated crc is 0x%02X\n", crc); fprintf(stdout, "Received csum1 is 0x%02X\n", csum1); } } return 0; }
static int tpms_citroen_decode(bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos) { char time_str[LOCAL_TIME_BUFLEN]; data_t *data; unsigned int start_pos; bitbuffer_t packet_bits = {0}; uint8_t *b; int state; char state_str[3]; unsigned id; char id_str[9]; int flags; int repeat; int pressure; int temperature; int battery; char code_str[7]; int crc; bitbuffer_invert(bitbuffer); start_pos = bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 88); b = packet_bits.bb[0]; if (b[6] == 0 || b[7] == 0) { return 0; // sanity check failed } crc = b[1]^b[2]^b[3]^b[4]^b[5]^b[6]^b[7]^b[8]^b[9]; if (crc != 0) { return 0; // bad checksum } state = b[0]; // not covered by CRC sprintf(state_str, "%02x", state); id = b[1]<<24 | b[2]<<16 | b[3]<<8 | b[4]; sprintf(id_str, "%08x", id); flags = b[5]>>4; repeat = b[5]&0x0f; pressure = b[6]; temperature = b[7]; battery = b[8]; sprintf(code_str, "%02x%02x%02x", pressure, temperature, battery); local_time_str(0, time_str); data = data_make( "time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "Citroen", "type", "", DATA_STRING, "TPMS", "state", "", DATA_STRING, state_str, "id", "", DATA_STRING, id_str, "flags", "", DATA_INT, flags, "repeat", "", DATA_INT, repeat, // "pressure_bar", "Pressure", DATA_FORMAT, "%.03f bar", DATA_DOUBLE, (double)pressure*0.0125, // "temperature_C", "Temperature", DATA_FORMAT, "%.0f C", DATA_DOUBLE, (double)temperature-50.0, // "battery_mV", "Battery", DATA_INT, battery_mV, "code", "", DATA_STRING, code_str, "mic", "", DATA_STRING, "CHECKSUM", NULL); data_acquired_handler(data); return 1; }
/* * This callback handles several Acurite devices that use a very * similar RF encoding and data format: *: * - 592TXR temperature and humidity sensor * - 5-n-1 weather station * - 6045M Lightning Detectur with Temperature and Humidity */ static int acurite_txr_callback(bitbuffer_t *bitbuf) { int browlen, valid = 0; uint8_t *bb; float tempc, tempf, wind_dird, rainfall = 0.0, wind_speed, wind_speedmph; uint8_t humidity, sensor_status, sequence_num, message_type; char channel, *wind_dirstr = ""; char channel_str[2]; uint16_t sensor_id; int raincounter, temp, battery_low; uint8_t strike_count, strike_distance; data_t *data; local_time_str(0, time_str); if (debug_output > 1) { fprintf(stderr,"acurite_txr\n"); bitbuffer_print(bitbuf); } for (uint16_t brow = 0; brow < bitbuf->num_rows; ++brow) { browlen = (bitbuf->bits_per_row[brow] + 7)/8; bb = bitbuf->bb[brow]; if (debug_output > 1) fprintf(stderr,"acurite_txr: row %d bits %d, bytes %d \n", brow, bitbuf->bits_per_row[brow], browlen); if ((bitbuf->bits_per_row[brow] < ACURITE_TXR_BITLEN || bitbuf->bits_per_row[brow] > ACURITE_5N1_BITLEN + 1) && bitbuf->bits_per_row[brow] != ACURITE_6045_BITLEN) { if (debug_output > 1 && bitbuf->bits_per_row[brow] > 16) fprintf(stderr,"acurite_txr: skipping wrong len\n"); continue; } // There will be 1 extra false zero bit added by the demod. // this forces an extra zero byte to be added if (bb[browlen - 1] == 0) browlen--; if (!acurite_checksum(bb,browlen - 1)) { if (debug_output) { fprintf(stderr, "%s Acurite bad checksum:", time_str); for (uint8_t i = 0; i < browlen; i++) fprintf(stderr," 0x%02x",bb[i]); fprintf(stderr,"\n"); } continue; } if (debug_output) { fprintf(stderr, "acurite_txr Parity: "); for (uint8_t i = 0; i < browlen; i++) { fprintf(stderr,"%d",byteParity(bb[i])); } fprintf(stderr,"\n"); } // tower sensor messages are 7 bytes. // @todo - see if there is a type in the message that // can be used instead of length to determine type if (browlen == ACURITE_TXR_BITLEN / 8) { channel = acurite_getChannel(bb[0]); sensor_id = acurite_txr_getSensorId(bb[0],bb[1]); sensor_status = bb[2]; // @todo, uses parity? & 0x07f humidity = acurite_getHumidity(bb[3]); tempc = acurite_txr_getTemp(bb[4], bb[5]); sprintf(channel_str, "%c", channel); battery_low = sensor_status >>7; data = data_make( "time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "Acurite tower sensor", "id", "", DATA_INT, sensor_id, "channel", "", DATA_STRING, &channel_str, "temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, tempc, "humidity", "Humidity", DATA_INT, humidity, "battery", "Battery", DATA_INT, battery_low, "status", "", DATA_INT, sensor_status, NULL); data_acquired_handler(data); valid++; } // The 5-n-1 weather sensor messages are 8 bytes. if (browlen == ACURITE_5N1_BITLEN / 8) { if (debug_output) { fprintf(stderr, "Acurite 5n1 raw msg: %02X %02X %02X %02X %02X %02X %02X %02X\n", bb[0], bb[1], bb[2], bb[3], bb[4], bb[5], bb[6], bb[7]); } channel = acurite_getChannel(bb[0]); sprintf(channel_str, "%c", channel); sensor_id = acurite_5n1_getSensorId(bb[0],bb[1]); sequence_num = acurite_5n1_getMessageCaught(bb[0]); message_type = bb[2] & 0x3f; battery_low = (bb[2] & 0x40) >> 6; if (message_type == ACURITE_MSGTYPE_WINDSPEED_WINDDIR_RAINFALL) { // Wind speed, wind direction, and rain fall wind_speed = acurite_getWindSpeed_kph(bb[3], bb[4]); wind_speedmph = kmph2mph(wind_speed); wind_dird = acurite_5n1_winddirections[bb[4] & 0x0f]; wind_dirstr = acurite_5n1_winddirection_str[bb[4] & 0x0f]; raincounter = acurite_getRainfallCounter(bb[5], bb[6]); if (acurite_5n1t_raincounter > 0) { // track rainfall difference after first run // FIXME when converting to structured output, just output // the reading, let consumer track state/wrap around, etc. rainfall = ( raincounter - acurite_5n1t_raincounter ) * 0.01; if (raincounter < acurite_5n1t_raincounter) { fprintf(stderr, "%s Acurite 5n1 sensor 0x%04X Ch %c, rain counter reset or wrapped around (old %d, new %d)\n", time_str, sensor_id, channel, acurite_5n1t_raincounter, raincounter); acurite_5n1t_raincounter = raincounter; } } else { // capture starting counter acurite_5n1t_raincounter = raincounter; fprintf(stderr, "%s Acurite 5n1 sensor 0x%04X Ch %c, Total rain fall since last reset: %0.2f\n", time_str, sensor_id, channel, raincounter * 0.01); } data = data_make( "time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "Acurite 5n1 sensor", "sensor_id", NULL, DATA_FORMAT, "0x%02X", DATA_INT, sensor_id, "channel", NULL, DATA_STRING, &channel_str, "sequence_num", NULL, DATA_INT, sequence_num, "battery", NULL, DATA_STRING, battery_low ? "OK" : "LOW", "message_type", NULL, DATA_INT, message_type, "wind_speed", NULL, DATA_FORMAT, "%.1f mph", DATA_DOUBLE, wind_speedmph, "wind_dir_deg", NULL, DATA_FORMAT, "%.1f", DATA_DOUBLE, wind_dird, "wind_dir", NULL, DATA_STRING, wind_dirstr, "rainfall_accumulation", NULL, DATA_FORMAT, "%.2f in", DATA_DOUBLE, rainfall, "raincounter_raw", NULL, DATA_INT, raincounter, NULL); data_acquired_handler(data); } else if (message_type == ACURITE_MSGTYPE_WINDSPEED_TEMP_HUMIDITY) { // Wind speed, temperature and humidity wind_speed = acurite_getWindSpeed_kph(bb[3], bb[4]); wind_speedmph = kmph2mph(wind_speed); tempf = acurite_getTemp(bb[4], bb[5]); tempc = fahrenheit2celsius(tempf); humidity = acurite_getHumidity(bb[6]); data = data_make( "time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "Acurite 5n1 sensor", "sensor_id", NULL, DATA_FORMAT, "0x%02X", DATA_INT, sensor_id, "channel", NULL, DATA_STRING, &channel_str, "sequence_num", NULL, DATA_INT, sequence_num, "battery", NULL, DATA_STRING, battery_low ? "OK" : "LOW", "message_type", NULL, DATA_INT, message_type, "wind_speed", NULL, DATA_FORMAT, "%.1f mph", DATA_DOUBLE, wind_speedmph, "temperature_F", "temperature", DATA_FORMAT, "%.1f F", DATA_DOUBLE, tempf, "humidity", NULL, DATA_FORMAT, "%d", DATA_INT, humidity, NULL); data_acquired_handler(data); } else { fprintf(stderr, "%s Acurite 5n1 sensor 0x%04X Ch %c, Status %02X, Unknown message type 0x%02x\n", time_str, sensor_id, channel, bb[3], message_type); } } if (browlen == ACURITE_6045_BITLEN / 8) { channel = acurite_getChannel(bb[0]); // same as TXR sensor_id = (bb[1] << 8) | bb[2]; // TBD 16 bits or 20? humidity = acurite_getHumidity(bb[3]); // same as TXR message_type = bb[4] & 0x7f; temp = bb[5] & 0x7f; // TBD Not sure if this is the temp. strike_count = bb[6] & 0x7f; strike_distance = bb[7] & 0x7f; printf("%s Acurite lightning 0x%04X Ch %c Msg Type 0x%02x: %d C %d %% RH Strikes %d Distance %d -", time_str, sensor_id, channel, message_type, temp, humidity, strike_count, strike_distance); // FIXME Temporarily dump message data until the decoding improves. // Include parity indicator. for (int i=0; i < browlen; i++) { char pc; pc = byteParity(bb[i]) == 0 ? ' ' : '*'; fprintf(stdout, " %02x%c", bb[i], pc); } printf("\n"); } }
static int nexus_callback(bitbuffer_t *bitbuffer) { bitrow_t *bb = bitbuffer->bb; data_t *data; char time_str[LOCAL_TIME_BUFLEN]; if (debug_output > 1) { fprintf(stderr,"Possible Nexus: "); bitbuffer_print(bitbuffer); } uint8_t id; uint8_t channel; int16_t temp; uint8_t humidity; int r = bitbuffer_find_repeated_row(bitbuffer, 3, 36); /** The nexus protocol will trigger on rubicson data, so calculate the rubicson crc and make sure * it doesn't match. By guesstimate it should generate a correct crc 1/255% of the times. * So less then 0.5% which should be acceptable. */ if (!rubicson_crc_check(bb) && r >= 0 && bitbuffer->bits_per_row[r] <= 37 && // we expect 36 bits but there might be a trailing 0 bit bb[r][0] != 0 && bb[r][1] != 0 && bb[r][2] != 0 && bb[r][3] != 0) { /* Get time now */ local_time_str(0, time_str); /* Nibble 0,1 contains id */ id = bb[r][0]; channel = (bb[r][1]&0x03) + 1; /* Nible 3,4,5 contains 12 bits of temperature * The temerature is signed and scaled by 10 */ temp = (int16_t)((uint16_t)(bb[r][1] << 12) | (bb[r][2] << 4)); temp = temp >> 4; humidity = (uint8_t)(((bb[r][3]&0x0F)<<4)|(bb[r][4]>>4)); // Thermo if (bb[r][3] == 0xF0) { data = data_make("time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "Nexus Temperature", "id", "House Code", DATA_INT, id, "channel", "Channel", DATA_INT, channel, "temperature_C", "Temperature", DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp/10.0, NULL); data_acquired_handler(data); } // Thermo/Hygro else { data = data_make("time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "Nexus Temperature/Humidity", "id", "House Code", DATA_INT, id, "channel", "Channel", DATA_INT, channel, "temperature_C", "Temperature", DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp/10.0, "humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity, NULL); data_acquired_handler(data); } return 1; }
static int template_callback(bitbuffer_t *bitbuffer) { char time_str[LOCAL_TIME_BUFLEN]; uint8_t *bb; uint16_t brow, row_nbytes; uint16_t sensor_id = 0; uint8_t msg_type, r_crc, c_crc; int16_t value; data_t *data; int valid = 0; /* * Early debugging aid to see demodulated bits in buffer and * to determine if your limit settings are matched and firing * this callback. * * 1. Enable with -D -D (debug level of 2) * 2. Delete this block when your decoder is working */ // if (debug_output > 1) { // fprintf(stderr,"new_tmplate callback:\n"); // bitbuffer_print(bitbuffer); // } local_time_str(0, time_str); /* * bit buffer will contain multiple rows, many of them empty. * Typically a complete message will be contained in a single * row if long and reset limits are set correctly. * May contain multiple message repeats. * Message might not appear in row 0, if protocol uses * start/preamble periods of different lengths. */ for (brow = 0; brow < bitbuffer->num_rows; ++brow) { bb = bitbuffer->bb[brow]; /* * Validate message and reject invalid messages as * early as possible before attempting to parse data.. * * Check "message envelope" * - valid message length * - valid preamble/device type/fixed bits if any * - Data integrity checks (CRC/Checksum/Parity) */ if (bitbuffer->bits_per_row[brow] != 68) continue; /* * number of bytes in row. * * Number of decoded bits may not be a multiple of 8. * bitbuffer row will have enough bytes to contain * all bytes, so round up. */ row_nbytes = (bitbuffer->bits_per_row[brow] + 7)/8; /* * Reject rows that don't start with the correct start byte * Example message should start with 0xAA */ if (bb[0] != MYDEVICE_STARTBYTE) continue; /* * Check message integrity (CRC/Checksum/parity) * * Example device uses CRC-8 */ r_crc = bb[row_nbytes - 1]; c_crc = crc8(bb, row_nbytes - 1, MYDEVICE_CRC_POLY, MYDEVICE_CRC_INIT); if (r_crc != c_crc) { // example debugging output if (debug_output >= 1) fprintf(stderr, "%s new_tamplate bad CRC: calculated %02x, received %02x\n", time_str, c_crc, r_crc); // reject row continue; } /* * Now that message "envelope" has been validated, * start parsing data. */ msg_type = bb[1]; sensor_id = bb[2] << 8 | bb[3]; value = bb[4] << 8 | bb[5]; if (msg_type != MYDEVICE_MSG_TYPE) { /* * received an unexpected message type * could be a bad message or a new message not * previously seen. Optionally log debug putput. */ continue; } data = data_make("time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "New Template", "id", "", DATA_INT, sensor_id, "data","", DATA_INT, value, NULL); data_acquired_handler(data); valid++; } // Return 1 if message successfully decoded if (valid) return 1; return 0; }
static int newkaku_callback(bitbuffer_t *bitbuffer) { /* Two bits map to 2 states, 0 1 -> 0 and 1 1 -> 1 */ /* Status bit can be 1 1 -> 1 which indicates DIM value. 4 extra bits are present with value */ /*start pulse: 1T high, 10.44T low */ /*- 26 bit: Address */ /*- 1 bit: group bit*/ /*- 1 bit: Status bit on/off/[dim]*/ /*- 4 bit: unit*/ /*- [4 bit: dim level. Present if [dim] is used, but might be present anyway...]*/ /*- stop pulse: 1T high, 40T low */ data_t *data; bitrow_t *bb = bitbuffer->bb; int i; uint8_t tmp = 0; uint8_t unit = 0; uint8_t packet = 0; uint8_t bitcount = 0; uint32_t kakuid = 0; uint8_t dv = 0; char *group_call, *command, *dim; char time_str[LOCAL_TIME_BUFLEN]; local_time_str(0, time_str); if (bb[0][0] == 0xac || bb[0][0] == 0xb2) {//always starts with ac or b2 // first bit is from startbit sequence, not part of payload! // check protocol if value is 10 or 01, else stop processing as it is no valid KAKU packet! //get id=24bits, remember 1st 1 bit = startbit, no payload! for (packet = 0; packet < 6; packet++) {//get first part kakuid tmp = bb[0][packet] << 1; if ((bb[0][packet + 1]&(1 << 7)) != 0) {// if set add bit to current tmp++; } for (bitcount = 0; bitcount < 8; bitcount += 2) {//process bitstream, check protocol! if (((tmp << bitcount & (0x80)) == 0x80)&((tmp << bitcount & (0x40)) == 0)) { //add 1 kakuid = kakuid << 1; kakuid++; } else if (((tmp << bitcount & (0x80)) == 0)&((tmp << bitcount & (0x40)) == 0x40)) { kakuid = kakuid << 1; //add 0 } else { return 0; //00 and 11 indicates packet error. Do exit, no valid packet } } } tmp = bb[0][6] << 1; //Get last part ID for (bitcount = 0; bitcount < 4; bitcount += 2) { if (((tmp << bitcount & (0x80)) == 0x80)&((tmp << bitcount & (0x40)) == 0)) { //add 1 kakuid = kakuid << 1; kakuid++; } else if (((tmp << bitcount & (0x80)) == 0)&((tmp << bitcount & (0x40)) == 0x40)) { //= add bit on kakuid kakuid = kakuid << 1; //add 0 } else { return 0; //00 and 11 indicates packet error. no valid packet! do exit } } //Get unit ID tmp = bb[0][7] << 1; if ((bb[0][8]&(1 << 7)) != 0) {// if set add bit to current tmp++; } for (bitcount = 0; bitcount < 8; bitcount += 2) {//process bitstream, check protocol! if (((tmp << bitcount & (0x80)) == 0x80)&((tmp << bitcount & (0x40)) == 0)) { //add 1 unit = unit << 1; unit++; } else if (((tmp << bitcount & (0x80)) == 0)&((tmp << bitcount & (0x40)) == 0x40)) { unit = unit << 1; //add 0 } else { return 0; //00 and 11 indicates packet error. Do exit, no valid packet } } group_call = (((bb[0][6] & (0x04)) == 0x04)&((bb[0][6] & (0x02)) == 0)) ? "Yes" : "No"; command = (((bb[0][6] & (0x01)) == 0x01)&((bb[0][7] & (0x80)) == 0)) ? "On" : "Off"; if (((bb[0][6] & (0x01)) == 0x01)&((bb[0][7] & (0x80)) == 0x80)) {//11 indicates DIM command, 4 extra bits indicate DIM value dim = "Yes"; tmp = bb[0][8] << 1; // get packet, loose first bit if ((bb[0][9]&(1 << 7)) != 0) {// if bit is set Add to current packet tmp++; for (bitcount = 0; bitcount < 8; bitcount += 2) {//process last bit outside if (((tmp << bitcount & (0x80)) == 0x80)&((tmp << bitcount & (0x40)) == 0)) { //add 1 dv = dv << 1; dv++; } else if (((tmp << bitcount & (0x80)) == 0)&((tmp << bitcount & (0x40)) == 0x40)) { dv = dv << 1; //add 0 } else { return 0; //00 and 11 indicates packet error. Do exit, no valid packet } } } } else { dim = "No"; } data = data_make("time", "", DATA_STRING, time_str, "model", "", DATA_STRING, "KlikAanKlikUit Wireless Switch", "id", "", DATA_INT, kakuid, "unit", "Unit", DATA_INT, unit, "group_call", "Group Call", DATA_STRING, group_call, "command", "Command", DATA_STRING, command, "dim", "Dim", DATA_STRING, dim, "dim_value", "Dim Value", DATA_INT, dv, NULL); data_acquired_handler(data); return 1; } return 0; }