Skip to content

Commit

Permalink
Add support for Bresser Lightning and Bresser Air Quality (#2698)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthias-bs committed Oct 30, 2023
1 parent daf054f commit 3a9c61b
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 46 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
[170] LaCrosse Technology View LTV-WR1 Multi Sensor
[171] LaCrosse Technology View LTV-TH Thermo/Hygro Sensor
[172] Bresser Weather Center 6-in-1, 7-in-1 indoor, soil, new 5-in-1, 3-in-1 wind gauge, Froggit WH6000, Ventus C8488A
[173] Bresser Weather Center 7-in-1
[173] Bresser Weather Center 7-in-1, Air Quality PM2.5 / PM10
[174] EcoDHOME Smart Socket and MCEE Solar monitor
[175] LaCrosse Technology View LTV-R1, LTV-R3 Rainfall Gauge, LTV-W1/W2 Wind Sensor
[176] BlueLine Innovations Power Cost Monitor
Expand Down Expand Up @@ -334,6 +334,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
[246] TFA 30.3151 Weather Station
[247] Bresser water leakage
[248]* Nissan TPMS
[249] Bresser lightning
* Disabled by default, use -R n or a conf file to enable
Expand Down
1 change: 1 addition & 0 deletions conf/rtl_433.example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ convert si
protocol 246 # TFA 30.3151 Weather Station
protocol 247 # Bresser water leakage
# protocol 248 # Nissan TPMS
protocol 249 # Bresser lightning

## Flex devices (command line option "-X")

Expand Down
1 change: 1 addition & 0 deletions include/rtl_433_devices.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@
DECL(tfa_303151) \
DECL(bresser_leakage) \
DECL(tpms_nissan) \
DECL(bresser_lightning) \

/* Add new decoders here. */

Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ add_library(r_433 STATIC
devices/bresser_6in1.c
devices/bresser_7in1.c
devices/bresser_leakage.c
devices/bresser_lightning.c
devices/bt_rain.c
devices/burnhardbbq.c
devices/calibeur.c
Expand Down
143 changes: 98 additions & 45 deletions src/devices/bresser_7in1.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,25 @@

#include "decoder.h"

#define SENSOR_TYPE_WEATHER 1
#define SENSOR_TYPE_AIR_PM 8

/**
Decoder for Bresser Weather Center 7-in-1, outdoor sensor.
Decoder for Bresser Weather Center 7-in-1 and Air Quality PM2.5 / PM10, outdoor sensors.
See https://github.com/merbanan/rtl_433/issues/1492
See
https://github.com/merbanan/rtl_433/issues/1492
and
https://github.com/merbanan/rtl_433/issues/2693
Preamble:
aa aa aa aa aa 2d d4
Observed length depends on reset_limit.
The data has a whitening of 0xaa.
The data (not including STYPE, STARTUP, CH and maybe ID) has a whitening of 0xaa.
Weather Center
Data layout:
{271}631d05c09e9a18abaabaaaaaaaaa8adacbacff9cafcaaaaaaa000000000000000000
Expand All @@ -41,6 +48,11 @@ Data layout:
Unit of light is kLux (not W/m²).
Air Quality Sensor PM2.5 / PM10 Sensor (PN 7009970)
Data layout:
DIGEST:8h8h ID?8h8h ?8h8h STYPE:4h STARTUP:1b CH:3b ?8h 4h ?4h8h4h PM_2_5:4h8h4h PM10:4h8h4h ?4h ?8h4h BATT:1b ?3b ?8h8h8h8h8h8h TRAILER:8h8h8h
First two bytes are an LFSR-16 digest, generator 0x8810 key 0xba95 with a final xor 0x6df1, which likely means we got that wrong.
*/

Expand Down Expand Up @@ -76,6 +88,11 @@ static int bresser_7in1_decode(r_device *decoder, bitbuffer_t *bitbuffer)
if (msg[21] == 0x00) {
return DECODE_FAIL_SANITY;
}

int s_type = msg[6] >> 4;
int nstartup = (msg[6] & 0x08) >> 3;
int chan = msg[6] & 0x07;

// data whitening
for (unsigned i = 0; i < sizeof (msg); ++i) {
msg[i] ^= 0xaa;
Expand All @@ -90,53 +107,87 @@ static int bresser_7in1_decode(r_device *decoder, bitbuffer_t *bitbuffer)
return DECODE_FAIL_MIC;
}

int id = (msg[2] << 8) | (msg[3]);
int wdir = (msg[4] >> 4) * 100 + (msg[4] & 0x0f) * 10 + (msg[5] >> 4);
int wgst_raw = (msg[7] >> 4) * 100 + (msg[7] & 0x0f) * 10 + (msg[8] >> 4);
int wavg_raw = (msg[8] & 0x0f) * 100 + (msg[9] >> 4) * 10 + (msg[9] & 0x0f);
int rain_raw = (msg[10] >> 4) * 100000 + (msg[10] & 0x0f) * 10000 + (msg[11] >> 4) * 1000
+ (msg[11] & 0x0f) * 100 + (msg[12] >> 4) * 10 + (msg[12] & 0x0f) * 1; // 6 digits
float rain_mm = rain_raw * 0.1f;
int temp_raw = (msg[14] >> 4) * 100 + (msg[14] & 0x0f) * 10 + (msg[15] >> 4);
float temp_c = temp_raw * 0.1f;
int flags = (msg[15] & 0x0f);
int id = (msg[2] << 8) | (msg[3]);
int flags = (msg[15] & 0x0f);
int battery_low = (flags & 0x06) == 0x06;
if (temp_raw > 600)
temp_c = (temp_raw - 1000) * 0.1f;
int humidity = (msg[16] >> 4) * 10 + (msg[16] & 0x0f);
int lght_raw = (msg[17] >> 4) * 100000 + (msg[17] & 0x0f) * 10000 + (msg[18] >> 4) * 1000
+ (msg[18] & 0x0f) * 100 + (msg[19] >> 4) * 10 + (msg[19] & 0x0f);
int uv_raw = (msg[20] >> 4) * 100 + (msg[20] & 0x0f) * 10 + (msg[21] >> 4);

float light_klx = lght_raw * 0.001f; // TODO: remove this
float light_lux = lght_raw;
float uv_index = uv_raw * 0.1f;

/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "Bresser-7in1",
"id", "", DATA_INT, id,
"temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_c,
"humidity", "Humidity", DATA_INT, humidity,
"wind_max_m_s", "Wind Gust", DATA_FORMAT, "%.1f m/s", DATA_DOUBLE, wgst_raw * 0.1f,
"wind_avg_m_s", "Wind Speed", DATA_FORMAT, "%.1f m/s", DATA_DOUBLE, wavg_raw * 0.1f,
"wind_dir_deg", "Direction", DATA_INT, wdir,
"rain_mm", "Rain", DATA_FORMAT, "%.1f mm", DATA_DOUBLE, rain_mm,
"light_klx", "Light", DATA_FORMAT, "%.3f klx", DATA_DOUBLE, light_klx, // TODO: remove this
"light_lux", "Light", DATA_FORMAT, "%.3f lux", DATA_DOUBLE, light_lux,
"uv", "UV Index", DATA_FORMAT, "%.1f", DATA_DOUBLE, uv_index,
"battery_ok", "Battery", DATA_INT, !battery_low,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */

decoder_output_data(decoder, data);
return 1;

if (s_type == SENSOR_TYPE_WEATHER) {
int wdir = (msg[4] >> 4) * 100 + (msg[4] & 0x0f) * 10 + (msg[5] >> 4);
int wgst_raw = (msg[7] >> 4) * 100 + (msg[7] & 0x0f) * 10 + (msg[8] >> 4);
int wavg_raw = (msg[8] & 0x0f) * 100 + (msg[9] >> 4) * 10 + (msg[9] & 0x0f);
int rain_raw = (msg[10] >> 4) * 100000 + (msg[10] & 0x0f) * 10000 + (msg[11] >> 4) * 1000
+ (msg[11] & 0x0f) * 100 + (msg[12] >> 4) * 10 + (msg[12] & 0x0f) * 1; // 6 digits
float rain_mm = rain_raw * 0.1f;
int temp_raw = (msg[14] >> 4) * 100 + (msg[14] & 0x0f) * 10 + (msg[15] >> 4);
float temp_c = temp_raw * 0.1f;

if (temp_raw > 600)
temp_c = (temp_raw - 1000) * 0.1f;
int humidity = (msg[16] >> 4) * 10 + (msg[16] & 0x0f);
int lght_raw = (msg[17] >> 4) * 100000 + (msg[17] & 0x0f) * 10000 + (msg[18] >> 4) * 1000
+ (msg[18] & 0x0f) * 100 + (msg[19] >> 4) * 10 + (msg[19] & 0x0f);
int uv_raw = (msg[20] >> 4) * 100 + (msg[20] & 0x0f) * 10 + (msg[21] >> 4);

float light_klx = lght_raw * 0.001f; // TODO: remove this
float light_lux = lght_raw;
float uv_index = uv_raw * 0.1f;

/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "Bresser-7in1",
"id", "", DATA_INT, id,
"startup", "Startup", DATA_COND, !nstartup, DATA_INT, !nstartup,
"temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_c,
"humidity", "Humidity", DATA_INT, humidity,
"wind_max_m_s", "Wind Gust", DATA_FORMAT, "%.1f m/s", DATA_DOUBLE, wgst_raw * 0.1f,
"wind_avg_m_s", "Wind Speed", DATA_FORMAT, "%.1f m/s", DATA_DOUBLE, wavg_raw * 0.1f,
"wind_dir_deg", "Direction", DATA_INT, wdir,
"rain_mm", "Rain", DATA_FORMAT, "%.1f mm", DATA_DOUBLE, rain_mm,
"light_klx", "Light", DATA_FORMAT, "%.3f klx", DATA_DOUBLE, light_klx, // TODO: remove this
"light_lux", "Light", DATA_FORMAT, "%.3f lux", DATA_DOUBLE, light_lux,
"uv", "UV Index", DATA_FORMAT, "%.1f", DATA_DOUBLE, uv_index,
"battery_ok", "Battery", DATA_INT, !battery_low,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */

decoder_output_data(decoder, data);
return 1;

} else if (s_type == SENSOR_TYPE_AIR_PM) {
int pm_2_5 = (msg[10] & 0x0f) * 1000 + (msg[11] >> 4) * 100 + (msg[11] & 0x0f) * 10 + (msg[12] >> 4);
int pm_10 = (msg[12] & 0x0f) * 1000 + (msg[13] >> 4) * 100 + (msg[13] & 0x0f) * 10 + (msg[14] >> 4);

// To Do: identify further data

/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "Bresser-7in1",
"id", "", DATA_INT, id,
"channel", "", DATA_INT, chan,
"startup", "Startup", DATA_COND, !nstartup, DATA_INT, !nstartup,
"battery_ok", "Battery", DATA_INT, !battery_low,
"pm_2_5_ug_m3", "PM2.5 Mass Concentration", DATA_INT, pm_2_5,
"pm_10_ug_m3", "PM10 Mass Concentraton", DATA_INT, pm_10,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */

decoder_output_data(decoder, data);
return 1;

} else {
decoder_logf(decoder, 2, __func__, "DECODE_FAIL_SANITY, s_type=%d not implemented", s_type);
return DECODE_FAIL_SANITY;

}
}

static char const *const output_fields[] = {
"model",
"id",
"channel",
"startup",
"temperature_C",
"humidity",
"wind_max_m_s",
Expand All @@ -146,13 +197,15 @@ static char const *const output_fields[] = {
"light_klx", // TODO: remove this
"light_lux",
"uv",
"pm_2_5_ug_m3",
"pm_10_ug_m3",
"battery_ok",
"mic",
NULL,
};

r_device const bresser_7in1 = {
.name = "Bresser Weather Center 7-in-1",
.name = "Bresser Weather Center 7-in-1, Air Quality PM2.5 / PM10",
.modulation = FSK_PULSE_PCM,
.short_width = 124,
.long_width = 124,
Expand Down
134 changes: 134 additions & 0 deletions src/devices/bresser_lightning.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/** @file
Bresser Lightning Sensor.
Copyright (C) 2023 The rtl_433 Project
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 "decoder.h"

#define SENSOR_TYPE_LIGHTNING 9

/**
Bresser Lightning Sensor.
Decoder for Bresser lightning outdoor sensor, PN 7009976
see https://github.com/merbanan/rtl_433/issues/2140
Preamble: aa aa 2d d4
Data layout:
DIGEST:8h8h ID:8h8h CTR:12h BATT:1b ?3b STYPE:4h STARTUP:1b CH:3d KM:8d ?8h8h
Based on bresser_7in1.c
The data (not including STYPE, STARTUP, CH and maybe ID) has a whitening of 0xaa.
CH is always 0.
First two bytes are an LFSR-16 digest, generator 0x8810 key 0xabf9 with a final xor 0x899e
*/

static int bresser_lightning_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0x2d, 0xd4};
uint8_t msg[25];

if (bitbuffer->num_rows != 1
|| bitbuffer->bits_per_row[0] < 160
|| bitbuffer->bits_per_row[0] > 440) {
decoder_logf(decoder, 2, __func__, "bit_per_row %u out of range", bitbuffer->bits_per_row[0]);
return DECODE_ABORT_EARLY; // Unrecognized data
}

unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0,
preamble_pattern, sizeof (preamble_pattern) * 8);

if (start_pos >= bitbuffer->bits_per_row[0]) {
return DECODE_ABORT_LENGTH;
}
start_pos += sizeof (preamble_pattern) * 8;

unsigned len = bitbuffer->bits_per_row[0] - start_pos;
if (len < sizeof(msg) * 8) {
decoder_logf(decoder, 2, __func__, "%u too short", len);
return DECODE_ABORT_LENGTH; // message too short
}

bitbuffer_extract_bytes(bitbuffer, 0, start_pos, msg, sizeof(msg) * 8);

decoder_log_bitrow(decoder, 2, __func__, msg, sizeof(msg) * 8, "MSG");

int s_type = msg[6] >> 4;
int chan = msg[6] & 0x07;
int battery_low = (msg[5] & 0x08) >> 3;
int nstartup = (msg[6] & 0x08) >> 3;

// data de-whitening
for (unsigned i = 0; i < sizeof (msg); ++i) {
msg[i] ^= 0xaa;
}
decoder_log_bitrow(decoder, 2, __func__, msg, sizeof(msg) * 8, "XOR");

// LFSR-16 digest, generator 0x8810 key 0xba95 final xor 0x6df1
int chk = (msg[0] << 8) | msg[1];
int digest = lfsr_digest16(&msg[2], 23, 0x8810, 0xba95);
if ((chk ^ digest) != 0x6df1) {
decoder_logf(decoder, 2, __func__, "Digest check failed %04x vs %04x (%04x)", chk, digest, chk ^ digest);
return DECODE_FAIL_MIC;
}

int sensor_id = (msg[2] << 8) | (msg[3]);
int distance_km = msg[7];
int count = (msg[4] << 4) | (msg[5] & 0xf0) >> 4;
int unknown1 = ((msg[5] & 0x0f) << 8) | msg[6];
int unknown2 = (msg[8] << 8) | msg[9];

// Sanity checks
if ((s_type != SENSOR_TYPE_LIGHTNING) || (chan != 0)) {
return DECODE_FAIL_SANITY;
}

/* clang-format off */
data_t *data = data_make(
"model", "", DATA_STRING, "Bresser-Lightning",
"id", "", DATA_FORMAT, "%08x", DATA_INT, sensor_id,
"startup", "Startup", DATA_COND, !nstartup, DATA_INT, !nstartup,
"battery_ok", "Battery", DATA_INT, !battery_low,
"distance_km", "storm_distance_km", DATA_INT, distance_km,
"strike_count", "strike_count", DATA_INT, count,
"unknown1", "Unknown1", DATA_FORMAT, "%08x", DATA_INT, unknown1,
"unknown2", "Unknown2", DATA_FORMAT, "%08x", DATA_INT, unknown2,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */

decoder_output_data(decoder, data);
return 1;
}

static char const *const output_fields[] = {
"model",
"id",
"startup",
"battery_ok",
"distance_km",
"strike_count",
"unknown1",
"unknown2",
NULL,
};

r_device const bresser_lightning = {
.name = "Bresser lightning",
.modulation = FSK_PULSE_PCM,
.short_width = 124,
.long_width = 124,
.reset_limit = 25000,
.decode_fn = &bresser_lightning_decode,
.fields = output_fields,
};

0 comments on commit 3a9c61b

Please sign in to comment.