rtl_433/src/devices/ikea_sparsnas.c
2021-06-12 12:05:07 +02:00

311 lines
12 KiB
C

/** @file
IKEA Sparsnäs Energy Meter Monitor.
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.
*/
/**
IKEA Sparsnäs Energy Meter Monitor.
The IKEA Sparsnäs consists of a display unit, and a sender unit. The display unit
displays and stores the values sent by the sender unit. It is not needed for this
decoder. The sender unit is placed by the energy meter. The sender unit has an
IR photo sensor which is placed over the energy meter impulse diode. The sender
also has an external antenna, which should be placed where it can provide non-
interfered transmissions.
The energy meter sends a fixed number of pulses per kWh. This is different per
unit, but usual values are 500, 1000 and 2000. This is usually indicated like
1000 imp/kWh
on the front of the meter. This value goes into ikea_sparsnas_pulses_per_kwh
in this file. The sender also has a unique ID which is used in the encryption
key, hence it is needed here to decrypt the data. The sender ID is on a sticker
in the battery compartment. There are three groups of three digits there. The
last six digits are your sender ID. Eg "400 617 633" gives you the sender id
617633. This number goes into IKEA_SPARSNAS_SENSOR_ID in this file.
The data is sent using CPFSK modulation. It requires PD_MIN_PULSE_SAMPLES in
pulse_detect.h to be lowered to 5 to be able to demodulate at 250kS/s. The
preamble is optimally 4 bytes of 0XAA. Then the sync word 0xD201. Here only
the last 2 bytes of the 0xAA preamble is checked, as the first ones seems
to be corrupted quite often. There are plenty of integrity checks made on
the demodulated package which makes this compromise OK.
Packet structure according to: https://github.com/strigeus/sparsnas_decoder
(with some changes by myself)
0: uint8_t length; // Always 0x11
1: uint8_t sender_id_lo; // Lowest byte of sender ID
2: uint8_t unknown; // Not sure
3: uint8_t major_version; // Always 0x07 - the major version number of the sender.
4: uint8_t minor_version; // Always 0x0E - the minor version number of the sender.
5: uint32_t sender_id; // ID of sender
9: uint16_t sequence; // Sequence number of current packet
11: uint16_t effect; // Current effect usage
13: uint32_t pulses; // Total number of pulses
17: uint8_t battery; // Battery level, 0-100%
18: uint16_t CRC; // 16 bit CRC of bytes 0-17
Example packet: 0x11a15f070ea2dfefe6d5fdd20547e6340ae7be61
The packet's integrity can be checked with the 16b CRC at the end of the packet.
There are also several other ways to check the integrity of the package.
- (preamble)
- CRC
- The decrypted sensor ID
- the constant bytes at 0, 3 and 4
The decryption, CRC is calculation, value extraction and interpretation is
taken from https://github.com/strigeus/sparsnas_decoder and adapted to
this application. Many thanks to strigeus!
Most other things are from https://github.com/kodarn/Sparsnas which is an
amazing repository of the IKEA Sparsnäs. Everything is studied with greay
detail. Many thanks to kodarn!
*/
#include "decoder.h"
#define IKEA_SPARSNAS_MESSAGE_BITLEN 160 // 20 bytes incl 8 bit length, 8 bit address, 128 bits data, and 16 bits of CRC. Excluding preamble and sync word
#define IKEA_SPARSNAS_MESSAGE_BYTELEN ((IKEA_SPARSNAS_MESSAGE_BITLEN + 7) / 8)
#define IKEA_SPARSNAS_MESSAGE_BITLEN_MAX 260 // Just for early sanity checks
#define IKEA_SPARSNAS_PREAMBLE_BITLEN 32
static const uint8_t preamble_pattern[4] = {0xAA, 0xAA, 0xD2, 0x01};
#define IKEA_SPARSNAS_CRC_INIT 0xffff
#define IKEA_SPARSNAS_CRC_POLY 0x8005
#define IKEA_SPARSNAS_ID_KEY_SUB 0x5D38E8CB
static uint16_t ikea_sparsnas_pulses_per_kwh = 1000;
static uint32_t ikea_sparsnas_sensor_id = 0;
static uint32_t ikea_sparsnas_brute_force_encryption(uint8_t buffer[18])
{
const uint8_t b5 = buffer[5 + 0];
const uint8_t b6 = buffer[5 + 1];
const uint8_t b7 = buffer[5 + 2];
const uint8_t b8 = buffer[5 + 3];
const uint8_t battery_enc = buffer[17];
uint8_t d0, d1, d2;
const uint8_t d3 = b8 ^ 0x47;
uint32_t dec_sensor_id, key_sensor_id;
uint8_t battery_dec, k0, k1, k2, k4;
for (k0=0;k0<0xFF;++k0) {
d0 = b5 ^ k0;
if (d0 > 0x0F) { //will result in sensor_id > 999999
continue; // DECODE_FAIL_SANITY
}
for (k1=0;k1<0xFF;++k1) {
d1 = b6 ^ k1;
for (k2=0;k2<0xFF;++k2) {
d2 = b7 ^ k2;
battery_dec = battery_enc ^ k2;
dec_sensor_id = (unsigned)d0 << 24 | d1 << 16 | d2 << 8 | d3;
if (dec_sensor_id > 999999) { //sensor id is at most 6 digits
continue; // DECODE_FAIL_SANITY
}
for (k4=0;k4<0xFF;++k4) {
key_sensor_id = ((unsigned)k0 << 24 | k4 << 16 | k2 << 8 | k1) + IKEA_SPARSNAS_ID_KEY_SUB;
if ((dec_sensor_id == key_sensor_id) && (battery_dec <= 100)) {
return dec_sensor_id;
}
}
}
}
}
return 0;
}
static int ikea_sparsnas_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
if ((bitbuffer->bits_per_row[0] < IKEA_SPARSNAS_MESSAGE_BITLEN) || (bitbuffer->bits_per_row[0] > IKEA_SPARSNAS_MESSAGE_BITLEN_MAX)) {
if (decoder->verbose > 1) {
decoder_output_bitbufferf(decoder, bitbuffer, "%s: ", __func__);
fprintf(stderr, "%s: Too short or too long packet received. Expected %d, received %d\n", __func__, IKEA_SPARSNAS_MESSAGE_BITLEN, bitbuffer->bits_per_row[0]);
}
return DECODE_ABORT_LENGTH;
}
// Look for preamble
uint16_t bitpos = bitbuffer_search(bitbuffer, 0, 0, (const uint8_t *)&preamble_pattern, IKEA_SPARSNAS_PREAMBLE_BITLEN);
if ((bitbuffer->bits_per_row[0] == bitpos) || (bitpos + IKEA_SPARSNAS_MESSAGE_BITLEN > bitbuffer->bits_per_row[0])) {
if (decoder->verbose > 1) {
decoder_output_bitbufferf(decoder, bitbuffer, "%s: ", __func__);
fprintf(stderr, "%s: malformed package, preamble not found. (Expected 0xAAAAD201)\n", __func__);
}
return DECODE_ABORT_EARLY;
}
// extract message, discarding preamble
uint8_t buffer[IKEA_SPARSNAS_MESSAGE_BYTELEN];
bitbuffer_extract_bytes(bitbuffer, 0, bitpos + IKEA_SPARSNAS_PREAMBLE_BITLEN, buffer, IKEA_SPARSNAS_MESSAGE_BITLEN);
if (decoder->verbose > 1) {
decoder_output_bitbufferf(decoder, bitbuffer, "%s: ", __func__);
decoder_output_bitrowf(decoder, buffer, IKEA_SPARSNAS_MESSAGE_BITLEN, "Encrypted message");
}
// CRC check
uint16_t crc_calculated = crc16(buffer, IKEA_SPARSNAS_MESSAGE_BYTELEN - 2, IKEA_SPARSNAS_CRC_POLY, IKEA_SPARSNAS_CRC_INIT);
uint16_t crc_received = buffer[18] << 8 | buffer[19];
if (crc_received != crc_calculated) {
if (decoder->verbose > 1) {
fprintf(stderr, "%s: CRC check failed (0x%X != 0x%X)\n", __func__, crc_calculated, crc_received);
}
return DECODE_FAIL_MIC;
}
//Decryption
if (!ikea_sparsnas_sensor_id) {
if (decoder->verbose > 1) {
fprintf(stderr, "%s: No sensor ID configured. Brute forcing encryption.\n", __func__);
}
ikea_sparsnas_sensor_id = ikea_sparsnas_brute_force_encryption(buffer);
if (decoder->verbose > 1) {
if (ikea_sparsnas_sensor_id) {
fprintf(stderr, "%s: Found valid sensor ID %06u. If reported values does not make sense, this might be incorrect.\n", __func__, ikea_sparsnas_sensor_id);
} else {
fprintf(stderr, "%s: No valid sensor ID found.\n", __func__);
}
}
}
uint8_t decrypted[18];
uint8_t key[5];
const uint32_t sensor_id_sub = ikea_sparsnas_sensor_id - IKEA_SPARSNAS_ID_KEY_SUB;
key[0] = (uint8_t)(sensor_id_sub >> 24);
key[1] = (uint8_t)(sensor_id_sub);
key[2] = (uint8_t)(sensor_id_sub >> 8);
key[3] = 0x47;
key[4] = (uint8_t)(sensor_id_sub >> 16);
for (size_t i = 0; i < 5; i++)
decrypted[i] = buffer[i];
for (size_t i = 0; i < 13; i++)
decrypted[5 + i] = buffer[5 + i] ^ key[i % 5];
// Additional integrity checks
uint32_t rcv_sensor_id = (unsigned)decrypted[5] << 24 | decrypted[6] << 16 | decrypted[7] << 8 | decrypted[8];
if (decoder->verbose > 1) {
fprintf(stderr, "%s: CRC OK (%X == %X)\n", __func__, crc_calculated, crc_received);
fprintf(stderr, "%s: Encryption key: 0x%X%X%X%X%X\n", __func__, key[0], key[1], key[2], key[3], key[4]);
decoder_output_bitrowf(decoder, decrypted, 18 * 8, "Decrypted");
fprintf(stderr, "%s: Received sensor id: %06u\n", __func__, rcv_sensor_id);
}
if (rcv_sensor_id != ikea_sparsnas_sensor_id) {
if (decoder->verbose > 1) {
fprintf(stderr, "%s: Malformed package, or wrong sensor id. Received sensor id (%06u) not the same as sender (%d)\n", __func__, rcv_sensor_id, ikea_sparsnas_sensor_id);
}
}
if ((!ikea_sparsnas_sensor_id) || (rcv_sensor_id != ikea_sparsnas_sensor_id)) {
data_t *data;
data = data_make(
"model", "Model", DATA_STRING, "Ikea-Sparsnas",
"id", "Sensor ID", DATA_INT, ikea_sparsnas_sensor_id,
"mic", "Integrity", DATA_STRING, "CRC",
NULL
);
decoder_output_data(decoder, data);
return 1;
}
if (decrypted[0] != 0x11) {
decoder_output_bitrowf(decoder, decrypted + 5, 13 * 8, "Message malformed");
if (decoder->verbose > 1) {
fprintf(stderr, "%s: Message malformed (byte0=%X expected %X)\n", __func__, decrypted[0], 0x11);
}
return DECODE_FAIL_SANITY;
}
if (decrypted[3] != 0x07) {
decoder_output_bitrowf(decoder, decrypted + 5, 13 * 8, "Message malformed");
if (decoder->verbose > 1) {
fprintf(stderr, "%s: Message malformed (byte3=%X expected %X)\n", __func__, decrypted[0], 0x07);
}
return DECODE_FAIL_SANITY;
}
//Value extraction and interpretation
uint16_t sequence_number = (decrypted[9] << 8 | decrypted[10]);
uint16_t effect = (decrypted[11] << 8 | decrypted[12]);
uint32_t pulses = ((unsigned)decrypted[13] << 24 | decrypted[14] << 16 | decrypted[15] << 8 | decrypted[16]);
uint8_t battery = decrypted[17];
//float watt = effect * 24.;
uint8_t mode = decrypted[4]^0x0f;
//if (mode == 1) { //Note that mode cycles between 0-3 when you first put in the batteries in
// watt = ((3600000.0 / ikea_sparsnas_pulses_per_kwh) * 1024.0) / effect;
//} else if (mode == 0 ) { // special mode for low power usage
// watt = effect * 0.24 / ikea_sparsnas_pulses_per_kwh;
//}
float cumulative_kWh = ((float)pulses) / ((float)ikea_sparsnas_pulses_per_kwh);
/* clang-format off */
data_t *data = data_make(
"model", "Model", DATA_STRING, "Ikea-Sparsnas",
"id", "Sensor ID", DATA_INT, rcv_sensor_id,
"sequence", "Sequence Number", DATA_INT, sequence_number,
"battery_ok", "Battery level", DATA_INT, battery * 0.01f, // 0-100
"pulses_per_kWh", "Pulses per kWh", DATA_INT, ikea_sparsnas_pulses_per_kwh,
"cumulative_kWh", "Cumulative kWh", DATA_FORMAT, "%7.3fkWh", DATA_DOUBLE, cumulative_kWh,
"effect", "Effect", DATA_FORMAT, "%dW", DATA_INT, effect,
"pulses", "Pulses", DATA_INT, pulses,
"mode", "Mode", DATA_INT, mode,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
return 1;
}
static char *output_fields[] = {
"model",
"id",
"sequence",
"battery_ok",
"pulses_per_kwh",
"cumulative_kWh",
"effect",
"pulses",
"mode",
"mic",
NULL,
};
r_device ikea_sparsnas = {
.name = "IKEA Sparsnas Energy Meter Monitor",
.modulation = FSK_PULSE_PCM,
.short_width = 27,
.long_width = 27,
.gap_limit = 1000,
.reset_limit = 3000,
.decode_fn = &ikea_sparsnas_decode,
.disabled = 0,
.fields = output_fields,
};