Skip to content

Commit

Permalink
Sub-GHz: Add Bresser 3CH weather station protocol (#217)
Browse files Browse the repository at this point in the history
* feat(subghz): added Bresser 3CH weather station

Added the Bresser 3CH, a weather station supported by rtl_433 already.

* Update changelog

---------

Co-authored-by: Willy-JL <[email protected]>
  • Loading branch information
m7i-org and Willy-JL authored Sep 17, 2024
1 parent 2ed1366 commit 3351f9b
Show file tree
Hide file tree
Showing 8 changed files with 377 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
- NFC: Cyborg Detector (by @RocketGod-git)
- Sub-GHz: Radio Scanner (by @RocketGod-git)
- Sub-GHz:
- UL: Add Marantec24 (static 24 bit) with add manually (by @xMasterX)
- Add Bresser 3CH weather station protocol (#217 by @m7i-org)
- UL: Add Marantec24 protocol (static 24 bit) with add manually (by @xMasterX)
- UL: Add GangQi protocol (static 34 bit) with button parsing and add manually (by @xMasterX & @Skorpionm)
- UL: Add Hollarm protocol (static 42 bit) with button parsing and add manually (by @xMasterX & @Skorpionm)
- UL: Add Hay21 protocol (dynamic 21 bit) with button parsing (by @xMasterX)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Filetype: Flipper SubGhz Key File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok270Async
Latitute: nan
Longitude: nan
Protocol: Bresser-3CH
Id: 23
Bit: 40
Data: 00 00 00 17 26 0C 40 89
Batt: 0
Hum: 64
Ts: 1726436378
Ch: 2
Btn: 0
Temp: 18.222225
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok270Async
Protocol: RAW
RAW_Data: 268 -704 756 -708 760 -704 756 -716 260 -464 272 -464 268 -460 512 -224 268 -460 512 -224 508 -228 500 -228 264 -472 264 -468 504 -228 264 -472 256 -476 504 -232 496 -232 260 -480 256 -472 256 -480 256 -480 252 -472 508 -228 508 -216 268 -468 268 -468 256 -468 512 -224 268 -460 268 -468 264 -464 264 -472 264 -472 256 -476 504 -232 260 -468 260 -476 260 -476 500 -232 256 -480 256 -472 504 -232 256 -956 756 -704 760 -708 752 -712 752 -716 260 -472 256 -476 260 -476 500 -228 260 -480 500 -228 500 -236 500 -232 260 -472 260 -476 504 -220 264 -472 264 -472 504 -220 512 -224 268 -460 268 -464 268 -468 260 -468 264 -472 508 -224 504 -228 264 -472 256 -472 264 -476 496 -232 260 -476 260 -476 252 -480 260 -472 260 -468 264 -472 508 -212 272 -464 268 -468 268 -460 512 -224 268 -460 264 -472 508 -228 256 -964 752 -716 748 -716 752 -720 744 -716 264 -460 268 -464 272 -456 516 -220 268 -468 504 -224 512 -224 508 -220 264 -472 264 -472 500 -232 260 -476 256 -476 500 -232 504 -232 260 -472 256 -480 256 -468 264 -472 264 -468 504 -224 512 -220 268 -460 268 -468 268 -464 508 -224 264 -472 264 -464 260 -476 260 -476 252 -480 256 -480 500 -228 256 -480 260 -476 252 -476 504 -232 260 -464 264 -472 512 -224 256 -956 752 -712 760 -704 756 -708 752 -720 260 -472 256 -476 260 -472 504 -232 256 -480 496 -232 504 -232 500 -228 260 -476 260 -476 500 -224 264 -468 268 -460 516 -220 512 -224 268 -456 272 -464 272 -456 268 -468 268 -468 500 -228 508 -228 264 -464 264 -472 260 -476 500 -232 256 -480 256 -476 252 -480 260 -476 256 -472 260 -476 504 -224 260 -476 260 -472 260 -464 512 -224 268 -456 268 -468 516 -220 260 -960 756 -708 752 -716 748 -724 740 -724 256 -476 256 -476 260 -476 500 -228 260 -476 500 -224 512 -224 508 -228 256 -468 268 -468 512 -212 268 -468 268 -468 504 -224 508 -228 268 -460 264 -472 264 -472 256 -472 264 -476 504 -224 504 -232 256 -480 252 -476 260 -480 500 -224 260 -476 260 -476 252 -472 268 -464 268 -460 268 -464 516 -220 260 -468 264 -472 264 -468 504 -228 264 -476 252 -476 504 -232 260 -960 748 -720 744 -724 744 -720 744 -716 264 -472 256 -468 268 -464 508 -220 268 -468 516 -220 504 -224 512 -224 268 -460 264 -472 508 -228 256 -472 264 -472 504 -228 504 -232 256 -480 252 -476 256 -480 260 -468 260 -476 504 -232 496 -232 260 -472 264 -460 268 -468 512 -224 260 -464 268 -468 272 -456 268 -468 264 -472 256 -472 508 -228 264 -468 260 -476 260 -476 496 -232 260 -476 260 -472 504 -228 260 -960 752 -716 748 -712 752 -708 760 -704 268 -468 268 -460 264 -472 508 -228 256 -472 508 -228 508 -220 508 -228 260 -476 260 -472 500 -232 260 -472 256 -480 500 -232 496 -236 256 -480 256 -472 260 -476 256 -480 256 -464 512 -224 512 -224 260 -468 264 -468 268 -460 512 -224 268 -464 260 -472 264 -468 264 -468 260 -476 260 -472 504 -228 260 -476 260 -472 256 -480 500 -232 256 -476 260 -476 496 -228 264 -964 744 -712 752 -712 760 -700 760 -704 264 -472 264 -472 264 -468 504 -232 260 -468 504 -232 504 -228 500 -232 260 -476 260 -476 496 -232 260 -476 260 -472 500 -232 504 -232 252 -476 260 -476 260 -464 264 -468 268 -468 504 -220 516 -220 268 -460 268 -468 268 -468 500 -228 264 -472 264 -468 260 -472 264 -472 256 -476 260 -476 504 -224 260 -480 256 -480 248 -476 504 -232 260 -464 268 -468 512 -224 264 -948 760 -704 760 -704 756 -716 744 -720 260 -472 256 -480 256 -480 496 -232 256 -480 500 -228 504 -232 500 -232 260 -472 260 -476 500 -228 260 -476 256 -480 496 -228 508 -228 260 -476 256 -468 268 -464 272 -456 268 -468 516 -216 508 -220 272 -464 268 -460 268 -468 512 -224 256 -472 264 -472 264 -468 260 -472 264 -472 256 -476 504 -232 256 -476 256 -480 252 -480 496 -236 256 -476 260 -468 504 -232 260 -952 756 -716 752 -712 748 -712 760 -704 264 -468 268 -464 264 -468 512 -224 256 -476 504 -232 504 -224 504 -232 260 -472 264 -468 504 -232 256 -472 260 -476 504 -232 496 -232 260 -476 256 -480 252 -476 260 -476 260 -464 508 -228 508 -228 256 -472 264 -468 268 -460 512 -224 268 -464 264 -464 268 -468 268 -460 264 -472 264 -472 500 -228 264 -472 260 -472 256 -480 504 -228 252 -480 256 -480 500 -228 260 -968 740 -724 748 -712 752 -708 756 -716 260 -464 272 -464 268 -456 516 -220 272 -464 504 -224 516 -220 512 -216 268 -468 268 -468 500 -228 264 -472 264 -472 500 -228 504 -232 260 -468 260 -476 256 -480 252 -480 256 -476 504 -228 500 -232 260 -480 248 -480 256 -480 500 -224 260 -476 260 -472 260 -468 268 -464 268 -460 268 -468 516 -216 272 -456 268 -468 268 -460 512 -224 268 -468 264 -468 504 -228 264 -960 748 -716 748 -720 744 -720 748 -720 256 -476 260 -472 260 -476 504 -232 252 -472 508 -228 500 -228 504 -232 260 -472 256 -472 508 -228 264 -460 268 -468 512 -224 504 -224 268 -464 272 -456 268 -468 268 -468 268 -460 512 -220 516 -212 268 -468 268 -468 264 -468 504 -228 264 -472 256 -476 260 -476 260 -468 260 -476 260 -476 496 -232 260 -476 256 -476 256 -476 504 -232 252 -476 260 -476 504 -224 260 -964 744 -720 752 -708 756 -704 760 -712 260 -468 268 -464 272 -456 516 -220 268 -468 504 -224 512 -224 512 -216 268 -464 272 -464 504 -228 264 -468 268 -468 504 -224 508 -228 264 -468 260 -472 264 -472 256 -476 260 -472 508 -224 504 -228 260 -476 252 -480 260 -476 500 -228 256 -480 256 -480 256 -472 260 -476 260 -464 264 -472 508 -228 264 -460 268 -468 268 -456 516 -220 272 -464 268 -460 512 -224 268 -948 756 -708 756 -716 748 -716 752 -712 260 -476 260 -468 260 -476 504 -232 252 -476 504 -232 504 -224 504 -232 256 -480 252 -476 504 -232 256 -472 260 -476 504 -232 496 -232 260 -476 260 -476 252 -472 264 -472 264 -460 512 -228 508 -228 256 -468 268 -464 272 -456 516 -220 268 -468 260 -464 268 -472 264 -464 264 -468 264 -472 504 -228 260 -472 264 -468 260 -476 504 -232 260 -468 260 -476 504 -228 256 -1212 744 -720 752 -712 752 -708 756 -712 260 -468 268 -464 268 -460 512 -224 268 -468 504 -224 512 -224 512 -216 264 -468 268 -468 504 -228 260 -472 264 -468 504 -232 504 -228 264 -468 256 -480 260 -476 252 -476 260 -476 496 -232 504 -232 256 -480 252 -476 260 -476 504 -224 260 -476 260 -476 252 -472 264 -472 264 -460 268 -468 512 -224 268 -456 272 -464 268 -460 512 -224 268 -468 264 -464 508 -228 264 -10004
7 changes: 7 additions & 0 deletions applications/debug/unit_tests/tests/subghz/subghz_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,13 @@ MU_TEST(subghz_decoder_solight_te44_test) {
"Test decoder " WS_PROTOCOL_SOLIGHT_TE44_NAME " error\r\n");
}

MU_TEST(subghz_decoder_bresser_3ch_test) {
mu_assert(
subghz_decoder_test(
EXT_PATH("unit_tests/subghz/bresser_3ch_raw.sub"), WS_PROTOCOL_BRESSER_3CH_NAME),
"Test decoder " WS_PROTOCOL_BRESSER_3CH_NAME " error\r\n");
}

//test encoders
MU_TEST(subghz_encoder_princeton_test) {
mu_assert(
Expand Down
264 changes: 264 additions & 0 deletions lib/subghz/protocols/bresser_3ch.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
#include "bresser_3ch.h"
#include "furi/core/log.h"
#define TAG "WSProtocolBresser3ch"

/*
* Help:
* https://github.com/merbanan/rtl_433/blob/master/src/devices/bresser_3ch.c
*
* Bresser sensor protocol.
*
* The protocol is for the wireless Temperature/Humidity sensor
* - Bresser Thermo-/Hygro-Sensor 3CH
* - also works for Renkforce DM-7511
*
* The sensor sends 15 identical packages of 40 bits each ~60s.
* The bits are PWM modulated with On Off Keying.
*
* A short pulse of 250 us followed by a 500 us gap is a 0 bit,
* a long pulse of 500 us followed by a 250 us gap is a 1 bit,
* there is a sync preamble of pulse, gap, 750 us each, repeated 4 times.
* Actual received and demodulated timings might be 2% shorter.
*
* The data is grouped in 5 bytes / 10 nibbles
*
* [id] [id] [flags] [temp] [temp] [temp] [humi] [humi] [chk] [chk]
*
* - id is an 8 bit random id that is generated when the sensor starts
* - flags are 4 bits battery low indicator, test button press and channel
* - temp is 12 bit unsigned fahrenheit offset by 90 and scaled by 10
* - humi is 8 bit relative humidity percentage
* - chk is the sum of the four data bytes
*
* @m7i-org - because there's more stuff screaming in the ether than you might think
*
*/

static const SubGhzBlockConst ws_protocol_bresser_3ch_const = {
.te_short = 250,
.te_long = 500,
.te_delta = 150,
.min_count_bit_for_found = 40,
};

struct WSProtocolDecoderBresser3ch {
SubGhzProtocolDecoderBase base;

SubGhzBlockDecoder decoder;
WSBlockGeneric generic;
};

struct WSProtocolEncoderBresser3ch {
SubGhzProtocolEncoderBase base;

SubGhzProtocolBlockEncoder encoder;
WSBlockGeneric generic;
};

const SubGhzProtocolDecoder ws_protocol_bresser_3ch_decoder = {
.alloc = ws_protocol_decoder_bresser_3ch_alloc,
.free = ws_protocol_decoder_bresser_3ch_free,

.feed = ws_protocol_decoder_bresser_3ch_feed,
.reset = ws_protocol_decoder_bresser_3ch_reset,

.get_hash_data = NULL,
.get_hash_data_long = ws_protocol_decoder_bresser_3ch_get_hash_data,
.serialize = ws_protocol_decoder_bresser_3ch_serialize,
.deserialize = ws_protocol_decoder_bresser_3ch_deserialize,
.get_string = ws_protocol_decoder_bresser_3ch_get_string,
.get_string_brief = NULL,
};

const SubGhzProtocolEncoder ws_protocol_bresser_3ch_encoder = {
.alloc = NULL,
.free = NULL,

.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};

const SubGhzProtocol ws_protocol_bresser_3ch = {
.name = WS_PROTOCOL_BRESSER_3CH_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
.filter = SubGhzProtocolFilter_Weather,
.decoder = &ws_protocol_bresser_3ch_decoder,
.encoder = &ws_protocol_bresser_3ch_encoder,
};

typedef enum {
Bresser3chDecoderStepReset = 0,
Bresser3chDecoderStepPreambleDn,
Bresser3chDecoderStepPreambleUp,
Bresser3chDecoderStepSaveDuration,
Bresser3chDecoderStepCheckDuration,
} Bresser3chDecoderStep;

void* ws_protocol_decoder_bresser_3ch_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
WSProtocolDecoderBresser3ch* instance = malloc(sizeof(WSProtocolDecoderBresser3ch));
instance->base.protocol = &ws_protocol_bresser_3ch;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}

void ws_protocol_decoder_bresser_3ch_free(void* context) {
furi_assert(context);
WSProtocolDecoderBresser3ch* instance = context;
free(instance);
}

void ws_protocol_decoder_bresser_3ch_reset(void* context) {
furi_assert(context);
WSProtocolDecoderBresser3ch* instance = context;
instance->decoder.parser_step = Bresser3chDecoderStepReset;
}

static bool ws_protocol_bresser_3ch_check(WSProtocolDecoderBresser3ch* instance) {
if(!instance->decoder.decode_data) return false;

uint8_t sum = (((instance->decoder.decode_data >> 32) & 0xff) +
((instance->decoder.decode_data >> 24) & 0xff) +
((instance->decoder.decode_data >> 16) & 0xff) +
((instance->decoder.decode_data >> 8) & 0xff)) &
0xff;

return (instance->decoder.decode_data & 0xff) == sum;
}

/**
* Analysis of received data
* @param instance Pointer to a WSBlockGeneric* instance
*/
static void ws_protocol_bresser_3ch_extract_data(WSBlockGeneric* instance) {
instance->id = (instance->data >> 32) & 0xff;
instance->battery_low = ((instance->data >> 31) & 0x1);
instance->btn = (instance->data >> 30) & 0x1;
instance->channel = (instance->data >> 28) & 0x3;

int16_t temp = (instance->data >> 16) & 0xfff;
instance->temp = locale_fahrenheit_to_celsius((float)(temp - 900) / 10.0);

instance->humidity = (instance->data >> 8) & 0xff;
}

void ws_protocol_decoder_bresser_3ch_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
WSProtocolDecoderBresser3ch* instance = context;

switch(instance->decoder.parser_step) {
case Bresser3chDecoderStepReset:
if(level && DURATION_DIFF(duration, ws_protocol_bresser_3ch_const.te_short * 3) <
ws_protocol_bresser_3ch_const.te_delta) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = Bresser3chDecoderStepPreambleDn;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
}
break;

case Bresser3chDecoderStepPreambleDn:
if((!level) && DURATION_DIFF(duration, ws_protocol_bresser_3ch_const.te_short * 3) <
ws_protocol_bresser_3ch_const.te_delta) {
if(DURATION_DIFF(
instance->decoder.te_last, ws_protocol_bresser_3ch_const.te_short * 12) <
ws_protocol_bresser_3ch_const.te_delta * 2) {
// End of sync after 4*750 (12*250) high values, start reading the message
instance->decoder.parser_step = Bresser3chDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = Bresser3chDecoderStepPreambleUp;
}
} else {
instance->decoder.parser_step = Bresser3chDecoderStepReset;
}
break;

case Bresser3chDecoderStepPreambleUp:
if(level && DURATION_DIFF(duration, ws_protocol_bresser_3ch_const.te_short * 3) <
ws_protocol_bresser_3ch_const.te_delta) {
instance->decoder.te_last = instance->decoder.te_last + duration;
instance->decoder.parser_step = Bresser3chDecoderStepPreambleDn;
} else {
instance->decoder.parser_step = Bresser3chDecoderStepReset;
}
break;

case Bresser3chDecoderStepSaveDuration:
if(instance->decoder.decode_count_bit ==
ws_protocol_bresser_3ch_const.min_count_bit_for_found) {
if(ws_protocol_bresser_3ch_check(instance)) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
ws_protocol_bresser_3ch_extract_data(&instance->generic);

if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = Bresser3chDecoderStepReset;
} else if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = Bresser3chDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = Bresser3chDecoderStepReset;
}
break;

case Bresser3chDecoderStepCheckDuration:
if(!level) {
if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_bresser_3ch_const.te_short) <
ws_protocol_bresser_3ch_const.te_delta &&
DURATION_DIFF(duration, ws_protocol_bresser_3ch_const.te_long) <
ws_protocol_bresser_3ch_const.te_delta) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = Bresser3chDecoderStepSaveDuration;
} else if(
DURATION_DIFF(instance->decoder.te_last, ws_protocol_bresser_3ch_const.te_long) <
ws_protocol_bresser_3ch_const.te_delta &&
DURATION_DIFF(duration, ws_protocol_bresser_3ch_const.te_short) <
ws_protocol_bresser_3ch_const.te_delta) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = Bresser3chDecoderStepSaveDuration;
} else
instance->decoder.parser_step = Bresser3chDecoderStepReset;
} else
instance->decoder.parser_step = Bresser3chDecoderStepReset;
break;
}
}

uint32_t ws_protocol_decoder_bresser_3ch_get_hash_data(void* context) {
furi_assert(context);
WSProtocolDecoderBresser3ch* instance = context;
return subghz_protocol_blocks_get_hash_data_long(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}

SubGhzProtocolStatus ws_protocol_decoder_bresser_3ch_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
WSProtocolDecoderBresser3ch* instance = context;
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
}

SubGhzProtocolStatus
ws_protocol_decoder_bresser_3ch_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
WSProtocolDecoderBresser3ch* instance = context;
return ws_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, ws_protocol_bresser_3ch_const.min_count_bit_for_found);
}

void ws_protocol_decoder_bresser_3ch_get_string(void* context, FuriString* output) {
furi_assert(context);
WSProtocolDecoderBresser3ch* instance = context;
ws_block_generic_get_string(&instance->generic, output);
}
Loading

0 comments on commit 3351f9b

Please sign in to comment.