mirror of https://github.com/merbanan/rtl_433.git
155 lines
4.7 KiB
C
155 lines
4.7 KiB
C
/** @file
|
|
Philips AJ3650 outdoor temperature sensor.
|
|
|
|
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.
|
|
*/
|
|
/**
|
|
Philips outdoor temperature sensor -- used with various Philips clock
|
|
radios (tested on AJ3650).
|
|
|
|
Not tested, but these should also work: 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, poly 0x9, init 0x1
|
|
|
|
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.
|
|
*/
|
|
|
|
#include "decoder.h"
|
|
|
|
#define PHILIPS_BITLEN 112
|
|
#define PHILIPS_PACKETLEN 4
|
|
#define PHILIPS_STARTNIBBLE 0x0
|
|
|
|
static int philips_aj3650_decode(r_device *decoder, bitbuffer_t *bitbuffer)
|
|
{
|
|
/* Map channel values to their real-world counterparts */
|
|
uint8_t const channel_map[] = {2, 0, 1, 0, 3};
|
|
|
|
uint8_t *bb;
|
|
unsigned int i;
|
|
uint8_t a, b, c;
|
|
uint8_t packet[PHILIPS_PACKETLEN];
|
|
uint8_t c_crc;
|
|
uint8_t channel, battery_low;
|
|
int temp_raw;
|
|
float temperature;
|
|
data_t *data;
|
|
|
|
/* Invert the data bits */
|
|
bitbuffer_invert(bitbuffer);
|
|
|
|
/* Correct number of rows? */
|
|
if (bitbuffer->num_rows != 1) {
|
|
decoder_logf(decoder, 2, __func__, "wrong number of rows (%d)", bitbuffer->num_rows);
|
|
return DECODE_ABORT_EARLY;
|
|
}
|
|
|
|
/* Correct bit length? */
|
|
if (bitbuffer->bits_per_row[0] != PHILIPS_BITLEN) {
|
|
decoder_logf(decoder, 2, __func__, "wrong number of bits (%d)", bitbuffer->bits_per_row[0]);
|
|
return DECODE_ABORT_LENGTH;
|
|
}
|
|
|
|
bb = bitbuffer->bb[0];
|
|
|
|
/* Correct start sequence? */
|
|
if ((bb[0] >> 4) != PHILIPS_STARTNIBBLE) {
|
|
decoder_log(decoder, 2, __func__, "wrong start nibble");
|
|
return DECODE_ABORT_EARLY;
|
|
}
|
|
|
|
/* 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 */
|
|
decoder_logf_bitrow(decoder, 2, __func__, packet, PHILIPS_PACKETLEN * 8, "combined packet");
|
|
|
|
/* Correct CRC? */
|
|
c_crc = crc4(packet, PHILIPS_PACKETLEN, 0x9, 1); /* Including the CRC nibble */
|
|
if (0 != c_crc) {
|
|
decoder_logf(decoder, 1, __func__, "CRC failed, calculated %x", c_crc);
|
|
return DECODE_FAIL_MIC;
|
|
}
|
|
|
|
/* 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 */
|
|
temp_raw = (packet[1] << 2) | (packet[2] >> 6);
|
|
temperature = (temp_raw - 500) * 0.1f;
|
|
|
|
/* Battery status */
|
|
battery_low = packet[PHILIPS_PACKETLEN - 1] & 0x40;
|
|
|
|
/* clang-format off */
|
|
data = data_make(
|
|
"model", "", DATA_STRING, "Philips-Temperature",
|
|
"channel", "Channel", DATA_INT, channel,
|
|
"battery_ok", "Battery", DATA_INT, !battery_low,
|
|
"temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temperature,
|
|
NULL);
|
|
/* clang-format on */
|
|
|
|
decoder_output_data(decoder, data);
|
|
return 1;
|
|
}
|
|
|
|
static char const *const output_fields[] = {
|
|
"model",
|
|
"channel",
|
|
"battery_ok",
|
|
"temperature_C",
|
|
NULL,
|
|
};
|
|
|
|
r_device const philips_aj3650 = {
|
|
.name = "Philips outdoor temperature sensor (type AJ3650)",
|
|
.modulation = OOK_PULSE_PWM,
|
|
.short_width = 2000,
|
|
.long_width = 6000,
|
|
// .gap_limit = 8000,
|
|
.reset_limit = 30000,
|
|
.decode_fn = &philips_aj3650_decode,
|
|
.fields = output_fields,
|
|
};
|