static int elro_db286a_callback(r_device *decoder, bitbuffer_t *bitbuffer) { data_t *data; uint8_t *b; char id_str[4*2+1]; // 33 bits expected, 5 minimum packet repetitions (14 expected) int row = bitbuffer_find_repeated_row(bitbuffer, 5, 33); if (row < 0 || bitbuffer->bits_per_row[row] != 33) return 0; b = bitbuffer->bb[row]; // 32 bits, trailing bit is dropped sprintf(id_str, "%02x%02x%02x%02x", b[0], b[1], b[2], b[3]); data = data_make( "model", "", DATA_STRING, "Elro-DB286A", "id", "ID", DATA_STRING, id_str, NULL); decoder_output_data(decoder, data); return 1; }
static int silvercrest_callback(r_device *decoder, bitbuffer_t *bitbuffer) { uint8_t *b; // bits of a row uint8_t cmd; data_t *data; if (bitbuffer->bits_per_row[1] !=33) return 0; /* select second row, first might be bad */ b = bitbuffer->bb[1]; if ((b[0] == 0x7c) && (b[1] == 0x26)) { cmd = b[2] & 0xF; // Validate button if ((b[3]&0xF) != cmd_lu_tab[cmd]) return 0; data = data_make( "model", "", DATA_STRING, "Silvercrest Remote Control", "button", "", DATA_INT, cmd, NULL); decoder_output_data(decoder, data); return 1; } return 0; }
static int thermopro_tp11_sensor_callback(r_device *decoder, bitbuffer_t *bitbuffer) { int temp_raw, row; float temp_c; bitrow_t *bb = bitbuffer->bb; unsigned int device, value; data_t *data; // Compare first four bytes of rows that have 32 or 33 bits. row = bitbuffer_find_repeated_row(bitbuffer, 2, 32); if (row < 0) return 0; if (bitbuffer->bits_per_row[row] > 33) return 0; value = (bb[row][0] << 16) + (bb[row][1] << 8) + bb[row][2]; device = value >> 12; // Validate code for known devices. if ((device == 0xb34 || device == 0xdb4 ) && !valid(value, bb[row][3])) return 0; temp_raw = value & 0xfff; temp_c = (temp_raw - 200) / 10.; data = data_make( "model", "", DATA_STRING, _X("Thermopro-TP11","Thermopro TP11 Thermometer"), "id", "Id", DATA_INT, device, "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temp_c, NULL); decoder_output_data(decoder, data); return 1; }
/** The sensor sends a single packet once every hour or twice a second for 11 minutes when in pairing/test mode (pairing needs 35 sec). depth reading is in cm, lowest reading is ~3, highest is ~305, 0 is invalid IIII IIII IIII IIII 0FFF L0OP DDDD DDDD The TEK377E might send an additional 8 zero bits. example packets are: 010101 01010101 01010111 01101001 10011010 10101001 10100101 10011010 01101010 10011001 10011010 0000 010101 01010101 01011000 10011010 01010110 01101010 10101010 10100101 01101010 10100110 10101001 1111 Start of frame full preamble is depending on first data bit either 01 0101 0101 0101 0101 0111 01 01 0101 0101 0101 0101 1000 10 */ static int oil_standard_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos) { data_t *data; uint8_t *b; uint16_t unit_id; uint16_t depth = 0; uint16_t binding_countdown = 0; uint8_t flags; uint8_t alarm; bitbuffer_t databits = {0}; bitpos = bitbuffer_manchester_decode(bitbuffer, row, bitpos, &databits, 41); if (databits.bits_per_row[0] < 32 || databits.bits_per_row[0] > 40 || (databits.bb[0][4] & 0xfe) != 0) return 0; b = databits.bb[0]; // The unit ID changes when you rebind by holding a magnet to the // sensor for long enough. unit_id = (b[0] << 8) | b[1]; // 0x01: Rebinding (magnet held to sensor) // 0x02: High-bit for depth // 0x04: (always zero?) // 0x08: Leak/theft alarm // 0x10: (unknown toggle) // 0x20: (unknown toggle) // 0x40: (unknown toggle) // 0x80: (always zero?) flags = b[2] & ~0x0A; alarm = (b[2] & 0x08) >> 3; if (flags & 1) // When binding, the countdown counts up from 0x40 to 0x4a // (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[3]; else // A depth reading of zero indicates no reading. depth = ((b[2] & 0x02) << 7) | b[3]; data = data_make( "model", "", DATA_STRING, _X("Oil-SonicStd","Oil Ultrasonic STANDARD"), "id", "", DATA_FORMAT, "%04x", DATA_INT, unit_id, "flags", "", DATA_FORMAT, "%02x", DATA_INT, flags, "alarm", "", DATA_INT, alarm, "binding_countdown", "", DATA_INT, binding_countdown, "depth_cm", "", DATA_INT, depth, NULL); decoder_output_data(decoder, 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 ge_coloreffects_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned start_pos) { data_t *data; bitbuffer_t packet_bits = {0}; uint8_t device_id; uint8_t command; ge_decode(decoder, bitbuffer, row, start_pos, &packet_bits); //bitbuffer_print(&packet_bits); /* From http://www.deepdarc.com/2010/11/27/hacking-christmas-lights/ * Decoded frame format is: * Preamble * Two zero bits * 6-bit Device ID (Can be modified by adding R15-R20 on the large PCB) * 8-bit Command * One zero bit */ // Frame should be 17 decoded bits (not including preamble) if (packet_bits.bits_per_row[0] != 17) return 0; // First two bits must be 0 if (*packet_bits.bb[0] & 0xc0) return 0; // Last bit must be 0 if (bit(packet_bits.bb[0], 16) != 0) return 0; // Extract device ID // We want bits [2..8]. Since the first two bits are zero, we'll just take the entire first byte device_id = *packet_bits.bb[0]; // Extract command from the second byte bitbuffer_extract_bytes(&packet_bits, 0, 8, &command, 8); // Format data data = data_make( "model", "", DATA_STRING, "GE Color Effects Remote", "id", "", DATA_FORMAT, "0x%x", DATA_INT, device_id, "command", "", DATA_STRING, ge_command_name(command), NULL); decoder_output_data(decoder, data); return 1; }
// 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 maverick_et73_sensor_callback(r_device *decoder, bitbuffer_t *bitbuffer) { int temp1_raw, temp2_raw, row; float temp1_c, temp2_c; uint8_t *bytes; unsigned int device; data_t *data; // The device transmits many rows, let's check for 3 matching. row = bitbuffer_find_repeated_row(bitbuffer, 3, 48); if (row < 0) { return 0; } bytes = bitbuffer->bb[row]; if (!bytes[0] && !bytes[1] && !bytes[2] && !bytes[3]) { return 0; // reduce false positives } if (bitbuffer->bits_per_row[row] != 48) return 0; device = bytes[0]; if (decoder->verbose) { fprintf(stderr,"maverick_et73_raw_data:"); bitrow_print(bytes, 48); } temp1_raw = (bytes[1] << 4) | ((bytes[2] & 0xf0) ); temp2_raw = ((bytes[2] & 0x0f) << 8) | bytes[3]; temp1_c = temp1_raw * 0.1; temp2_c = temp2_raw * 0.1; data = data_make( "model", "", DATA_STRING, "Maverick ET73", "rid", "Random Id", DATA_INT, device, "temperature_1_C", "Temperature 1", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temp1_c, "temperature_2_C", "Temperature 2", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temp2_c, NULL); decoder_output_data(decoder, 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 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; }
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 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 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 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 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"); } }
/// @param *data : returns the decoded information as a data_t * static int decode_xc0324_message(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, uint16_t bitpos, const int latest_event, data_t **data) { uint8_t b[XC0324_MESSAGE_BYTELEN]; char id [4] = {0}; double temperature; uint8_t flags; uint8_t chksum; // == 0x00 for a good message // Extract the message bitbuffer_extract_bytes(bitbuffer, row, bitpos, b, XC0324_MESSAGE_BITLEN); // Examine the chksum and bail out now if not OK to save time // b[5] is a check byte, the XOR of bytes 0-4. // ie a checksum where the sum is "binary add no carry" // Effectively, each bit of b[5] is the parity of the bits in the // corresponding position of b[0] to b[4] // NB : b[0] ^ b[1] ^ b[2] ^ b[3] ^ b[4] ^ b[5] == 0x00 for a clean message chksum = xor_bytes(b, 6); if (chksum != 0x00) { if (decoder->verbose == 1) { // Output the "bad" message (only for message level deciphering!) decoder_output_bitrowf(decoder, b, XC0324_MESSAGE_BITLEN, "chksum = 0x%02X not 0x00 <- XC0324:vv row %d bit %d", chksum, row, bitpos); } return 0; // No message was able to be decoded } // Extract the id as hex string snprintf(id, 3, "%02X", b[1]); // Decode temperature (b[2]), plus 1st 4 bits b[3], LSB first order! // Tenths of degrees C, offset from the minimum possible (-40.0 degrees) uint16_t temp = ((uint16_t)(reverse8(b[3]) & 0x0f) << 8) | reverse8(b[2]) ; temperature = (temp / 10.0) - 40.0 ; //Unknown byte, constant as 0x80 in all my data // ??maybe battery status?? flags = b[4]; // Create the data structure, ready for the decoder_output_data function. // Separate production output (decoder->verbose == 0) // from (simulated) deciphering stage output (decoder->verbose > 0) if (!decoder->verbose) { // production output *data = data_make( "model", "Device Type", DATA_STRING, "Digitech XC0324", "id", "ID", DATA_STRING, id, "temperature_C", "Temperature C", DATA_FORMAT, "%.1f", DATA_DOUBLE, temperature, "flags", "Constant ?", DATA_INT, flags, "mic", "Integrity", DATA_STRING, "CHECKSUM", NULL); } // Output (simulated) message level deciphering information.. if (decoder->verbose == 1) { decoder_output_bitrowf(decoder, b, XC0324_MESSAGE_BITLEN, "Temp was %4.1f <- XC0324:vv row %03d bit %03d", temperature, row, bitpos); } // Output "finished deciphering" reference values for future regression tests. if ((decoder->verbose == 3) & (latest_event == 0)) { //info from this first successful message is enough decoder_output_messagef(decoder, "XC0324:vvvv Reference -> Temperature %4.1f C; sensor id %s", temperature, id); } return 1; // Message successfully decoded }