diff --git a/README.md b/README.md index 6f39b4c1..e1861709 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,7 @@ Supported device protocols: [91] Infactory [92] Ft004b [93] Ford car remote + [94] Philips outdoor temperature sensor * Disabled by default, use -R n or -G diff --git a/include/rtl_433.h b/include/rtl_433.h index 30f05d06..e9e425c5 100755 --- a/include/rtl_433.h +++ b/include/rtl_433.h @@ -42,7 +42,7 @@ #define MINIMAL_BUF_LENGTH 512 #define MAXIMAL_BUF_LENGTH (256 * 16384) -#define MAX_PROTOCOLS 93 +#define MAX_PROTOCOLS 94 #define SIGNAL_GRABBER_BUFFER (12 * DEFAULT_BUF_LENGTH) /* Supported modulation types */ diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index 27e9e93b..7a64fd57 100755 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -96,7 +96,8 @@ DECL(tpms_renault) \ DECL(infactory) \ DECL(ft004b) \ - DECL(fordremote) + DECL(fordremote) \ + DECL(philips) typedef struct { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d6114551..87d5d1db 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -105,6 +105,7 @@ add_executable(rtl_433 devices/tpms_renault.c devices/infactory.c devices/fordremote.c + devices/philips.c ) diff --git a/src/Makefile.am b/src/Makefile.am index 1b584757..99090918 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -90,6 +90,7 @@ rtl_433_SOURCES = baseband.c \ devices/tpms_ford.c \ devices/tpms_renault.c \ devices/infactory.c \ - devices/fordremote.c + devices/fordremote.c \ + devices/philips.c rtl_433_LDADD = $(LIBRTLSDR) $(LIBM) diff --git a/src/devices/philips.c b/src/devices/philips.c new file mode 100644 index 00000000..457f3faa --- /dev/null +++ b/src/devices/philips.c @@ -0,0 +1,231 @@ +/* + * Philips outdoor temperature sensor -- used with various Philips clock + * radios (tested on AJ3650) + * + * Not tested, but these should also work: AJ7010, AJ260 ... maybe others? + * + * A complete message is 112 bits: + * 4-bit initial preamble, always 0 + * 4-bit packet separator, always 0, followed by 32-bit data packet. + * Packets are repeated 3 times for 108 bits total. + * + * 32-bit data packet format: + * + * 0001cccc tttttttt tt000000 0b0?ssss + * + * c - channel: 0=channel 2, 2=channel 1, 4=channel 3 (4 bits) + * t - temperature in Celsius: subtract 500 and divide by 10 (10 bits) + * b - battery status: 0 = OK, 1 = LOW (1 bit) + * ? - unknown: always 1 in every packet I've seen (1 bit) + * s - CRC: non-standard CRC-4, reverse-engineered using RevEng (4 bits) + * http://reveng.sourceforge.net + * width=4 poly=0x9 init=0x1 refin=false refout=false xorout=0x0 check=0x5 residue=0x0 name=(none) + * + * Pulse width: + * Short: 2000 us = 0 + * Long: 6000 us = 1 + * Gap width: + * Short: 6000 us + * Long: 2000 us + * Gap width between packets: 29000 us + * + * Presumably the 4-bit preamble is meant to be a sync of some sort, + * but it has the exact same pulse/gap width as a short pulse, and + * gets processed as data. + * + * Copyright (C) 2017 Chris Coffey (kpuc@sdf.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include "rtl_433.h" +#include "util.h" + +#define PHILIPS_BITLEN 112 +#define PHILIPS_PACKETLEN 4 +#define PHILIPS_STARTNIBBLE 0x0 + +/* Map channel values to their real-world counterparts */ +static const uint8_t channel_map[] = { 2, 0, 1, 0, 3 }; + + +/* philips_crc4_bit(): + * Compute CRC-4 for a byte sequence, one bit at a time. + * + * Note that Philips uses a custom CRC implementation for this device; see above + * for details. + * + * This function was generated using crcany (https://github.com/madler/crcany) + * License information: "This code is under the zlib license, permitting free + * commercial use." + */ +static unsigned philips_crc4_bit(unsigned crc, void const *mem, size_t len) +{ + unsigned char const *data = mem; + if (data == NULL) + return 0x1; + crc <<= 4; + while (len--) { + crc ^= *data++; + for (unsigned k = 0; k < 8; k++) + crc = crc & 0x80 ? (crc << 1) ^ 0x90 : crc << 1; + } + crc >>= 4; + crc &= 0xf; + return crc; +} + + +/* philips_crc4_rem(): + * Compute CRC-4 of the remaining high bits in the low byte of val. + * + * Note that Philips uses a custom CRC implementation for this device; see above + * for details. + * + * This function was generated using crcany (https://github.com/madler/crcany) + * License information: "This code is under the zlib license, permitting free + * commercial use." + */ +static unsigned philips_crc4_rem(unsigned crc, unsigned val, unsigned bits) +{ + crc <<= 4; + val &= ((1U << bits) - 1) << (8 - bits); + crc ^= val; + while (bits--) + crc = crc & 0x80 ? (crc << 1) ^ 0x90 : crc << 1; + crc >>= 4; + crc &= 0xf; + return crc; +} + + +static int philips_callback(bitbuffer_t *bitbuffer) +{ + char time_str[LOCAL_TIME_BUFLEN]; + uint8_t *bb; + unsigned int i; + uint8_t a, b, c; + uint8_t packet[PHILIPS_PACKETLEN]; + uint8_t r_crc, c_crc; + uint8_t channel, battery_status; + int tmp; + float temperature; + data_t *data; + + /* Get the time */ + local_time_str(0, time_str); + + /* Correct number of rows? */ + if (bitbuffer->num_rows != 1) { + if (debug_output) { + fprintf(stderr, "%s %s: wrong number of rows (%d)\n", + time_str, __func__, bitbuffer->num_rows); + } + return 0; + } + + /* Correct bit length? */ + if (bitbuffer->bits_per_row[0] != PHILIPS_BITLEN) { + if (debug_output) { + fprintf(stderr, "%s %s: wrong number of bits (%d)\n", + time_str, __func__, bitbuffer->bits_per_row[0]); + } + return 0; + } + + bb = bitbuffer->bb[0]; + + /* Correct start sequence? */ + if ((bb[0] >> 4) != PHILIPS_STARTNIBBLE) { + if (debug_output) { + fprintf(stderr, "%s %s: wrong start nibble\n", time_str, __func__); + } + return 0; + } + + /* Compare and combine the 3 repeated packets, with majority wins */ + for (i = 0; i < PHILIPS_PACKETLEN; i++) { + a = bb[i+1]; /* First packet - on byte boundary */ + b = (bb[i+5] << 4) | (bb[i+6] >> 4 & 0xf); /* Second packet - not on byte boundary */ + c = bb[i+10]; /* Third packet - on byte boundary */ + + packet[i] = (a & b) | (b & c) | (a & c); + } + + /* If debug enabled, print the combined majority-wins packet */ + if (debug_output) { + fprintf(stderr, "%s %s: combined packet = ", time_str, __func__); + for (i = 0; i < PHILIPS_PACKETLEN; i++) { + fprintf(stderr, "%02x ",packet[i]); + } + fprintf(stderr, "\n"); + } + + /* Correct CRC? */ + r_crc = packet[PHILIPS_PACKETLEN - 1] & 0x0f; /* Last nibble in the packet */ + c_crc = philips_crc4_bit(1, packet, PHILIPS_PACKETLEN - 1); /* First three bytes */ + c_crc = philips_crc4_rem(c_crc, packet[PHILIPS_PACKETLEN - 1], 4); /* Remaining high nibble */ + + if (r_crc != c_crc) { + if (debug_output) { + fprintf(stderr, "%s %s: CRC failed, calculated %x, received %x\n", + time_str, __func__, c_crc, r_crc); + } + return 0; + } + + /* Message validated, now parse the data */ + + /* Channel */ + channel = packet[0] & 0x0f; + if (channel > (sizeof(channel_map) / sizeof(channel_map[0]))) + channel = 0; + else + channel = channel_map[channel]; + + /* Temperature */ + tmp = packet[1]; + tmp <<= 2; + tmp |= ((packet[2] & 0xc0) >> 6); + tmp -= 500; + temperature = tmp / 10.0f; + + /* Battery status */ + battery_status = packet[PHILIPS_PACKETLEN - 1] & 0x40; + + data = data_make("time", "", DATA_STRING, time_str, + "model", "", DATA_STRING, "Philips outdoor temperature sensor", + "channel", "Channel", DATA_INT, channel, + "temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temperature, + "battery", "Battery", DATA_STRING, battery_status ? "LOW" : "OK", + NULL); + + data_acquired_handler(data); + + return 1; +} + +static char *philips_output_fields[] = { + "time", + "model", + "channel", + "temperature_C", + "battery", + NULL +}; + +r_device philips = { + .name = "Philips outdoor temperature sensor", + .modulation = OOK_PULSE_PWM_TERNARY, + .short_limit = 500, + .long_limit = 4000, + .reset_limit = 30000, + .json_callback = &philips_callback, + .disabled = 0, + .demod_arg = 0, + .fields = philips_output_fields, +}; +