Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Bresser Lightning Sensor and Bresser Air Quality Sensor #2698

Merged
merged 16 commits into from
Oct 30, 2023
Merged
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 startup = (msg[6] & 0x08) ? 0 : 1;
int chan = msg[6] & 0x07;

// data whitening
for (unsigned i = 0; i < sizeof (msg); ++i) {
msg[i] ^= 0xaa;
Expand All @@ -90,53 +107,86 @@ 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,
"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",
matthias-bs marked this conversation as resolved.
Show resolved Hide resolved
"startup", "Startup", DATA_COND, startup, DATA_INT, startup,
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,
"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,
"battery_ok", "Battery", DATA_INT, !battery_low,
"mic", "Integrity", DATA_STRING, "CRC",
"startup", "Startup", DATA_COND, startup, DATA_INT, startup,
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",
"temperature_C",
"humidity",
"wind_max_m_s",
Expand All @@ -146,13 +196,16 @@ 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",
"startup",
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
133 changes: 133 additions & 0 deletions src/devices/bresser_lightning.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/** @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_ok = (msg[5] & 0x08) ? 0 : 1;
matthias-bs marked this conversation as resolved.
Show resolved Hide resolved
int startup = (msg[6] & 0x08) ? 0 : 1;

// 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,
"battery_ok", "Battery", DATA_INT, battery_ok,
"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,
"startup", "Startup", DATA_COND, startup, DATA_INT, startup,
matthias-bs marked this conversation as resolved.
Show resolved Hide resolved
NULL);
/* clang-format on */

decoder_output_data(decoder, data);
return 1;
}

static char const *const output_fields[] = {
"model",
"id",
"battery_ok",
"distance_km",
"strike_count",
"unknown1",
"unknown2",
"startup",
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,
};
Loading