-
-
Notifications
You must be signed in to change notification settings - Fork 229
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New SubGHz protocol: Legrand doorbell
- Loading branch information
1 parent
d119416
commit 0769861
Showing
6 changed files
with
485 additions
and
0 deletions.
There are no files selected for viewing
10 changes: 10 additions & 0 deletions
10
applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F.sub
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
Filetype: Flipper SubGhz Key File | ||
Version: 1 | ||
Frequency: 433920000 | ||
Preset: FuriHalSubGhzPresetOok650Async | ||
Latitute: 0.000000 | ||
Longitude: 0.000000 | ||
Protocol: Legrand | ||
Bit: 18 | ||
Key: 00 00 00 00 00 02 E3 7F | ||
TE: 358 |
22 changes: 22 additions & 0 deletions
22
applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F_raw.sub
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,334 @@ | ||
#include "legrand.h" | ||
|
||
#include "../blocks/const.h" | ||
#include "../blocks/decoder.h" | ||
#include "../blocks/encoder.h" | ||
#include "../blocks/generic.h" | ||
#include "../blocks/math.h" | ||
|
||
#define TAG "SubGhzProtocolLegrand" | ||
|
||
static const SubGhzBlockConst subghz_protocol_legrand_const = { | ||
.te_short = 375, | ||
.te_long = 1125, | ||
.te_delta = 300, | ||
.min_count_bit_for_found = 18, | ||
}; | ||
|
||
struct SubGhzProtocolDecoderLegrand { | ||
SubGhzProtocolDecoderBase base; | ||
|
||
SubGhzBlockDecoder decoder; | ||
SubGhzBlockGeneric generic; | ||
|
||
uint32_t te; | ||
}; | ||
|
||
struct SubGhzProtocolEncoderLegrand { | ||
SubGhzProtocolEncoderBase base; | ||
|
||
SubGhzProtocolBlockEncoder encoder; | ||
SubGhzBlockGeneric generic; | ||
|
||
uint32_t te; | ||
}; | ||
|
||
typedef enum { | ||
LegrandDecoderStepReset = 0, | ||
LegrandDecoderStepSaveData, | ||
} LegrandDecoderStep; | ||
|
||
const SubGhzProtocolDecoder subghz_protocol_legrand_decoder = { | ||
.alloc = subghz_protocol_decoder_legrand_alloc, | ||
.free = subghz_protocol_decoder_legrand_free, | ||
|
||
.feed = subghz_protocol_decoder_legrand_feed, | ||
.reset = subghz_protocol_decoder_legrand_reset, | ||
|
||
.get_hash_data = NULL, | ||
.get_hash_data_long = subghz_protocol_decoder_legrand_get_hash_data, | ||
.serialize = subghz_protocol_decoder_legrand_serialize, | ||
.deserialize = subghz_protocol_decoder_legrand_deserialize, | ||
.get_string = subghz_protocol_decoder_legrand_get_string, | ||
}; | ||
|
||
const SubGhzProtocolEncoder subghz_protocol_legrand_encoder = { | ||
.alloc = subghz_protocol_encoder_legrand_alloc, | ||
.free = subghz_protocol_encoder_legrand_free, | ||
|
||
.deserialize = subghz_protocol_encoder_legrand_deserialize, | ||
.stop = subghz_protocol_encoder_legrand_stop, | ||
.yield = subghz_protocol_encoder_legrand_yield, | ||
}; | ||
|
||
const SubGhzProtocol subghz_protocol_legrand = { | ||
.name = SUBGHZ_PROTOCOL_LEGRAND_NAME, | ||
.type = SubGhzProtocolTypeStatic, | ||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | | ||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, | ||
|
||
.decoder = &subghz_protocol_legrand_decoder, | ||
.encoder = &subghz_protocol_legrand_encoder, | ||
}; | ||
|
||
void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment) { | ||
UNUSED(environment); | ||
SubGhzProtocolEncoderLegrand* instance = malloc(sizeof(SubGhzProtocolEncoderLegrand)); | ||
|
||
instance->base.protocol = &subghz_protocol_legrand; | ||
instance->generic.protocol_name = instance->base.protocol->name; | ||
|
||
instance->encoder.repeat = 10; | ||
instance->encoder.size_upload = 18 * 2 + 2; // max 18bit*2 + 2 (sync) | ||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); | ||
instance->encoder.is_running = false; | ||
return instance; | ||
} | ||
|
||
void subghz_protocol_encoder_legrand_free(void* context) { | ||
furi_assert(context); | ||
SubGhzProtocolEncoderLegrand* instance = context; | ||
free(instance->encoder.upload); | ||
free(instance); | ||
} | ||
|
||
/** | ||
* Generating an upload from data. | ||
* @param instance Pointer to a SubGhzProtocolEncoderLegrand instance | ||
* @return true On success | ||
*/ | ||
static bool subghz_protocol_encoder_legrand_get_upload(SubGhzProtocolEncoderLegrand* instance) { | ||
furi_assert(instance); | ||
|
||
size_t index = 0; | ||
size_t size_upload = (instance->generic.data_count_bit * 2) + 2; | ||
if(size_upload > instance->encoder.size_upload) { | ||
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); | ||
return false; | ||
} else { | ||
instance->encoder.size_upload = size_upload; | ||
} | ||
|
||
// Send sync | ||
instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 16); | ||
|
||
// Send key data | ||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { | ||
if(bit_read(instance->generic.data, i - 1)) { | ||
// send bit 1 | ||
instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te); | ||
instance->encoder.upload[index++] = | ||
level_duration_make(true, (uint32_t)instance->te * 3); | ||
} else { | ||
// send bit 0 | ||
instance->encoder.upload[index++] = | ||
level_duration_make(false, (uint32_t)instance->te * 3); | ||
instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
SubGhzProtocolStatus | ||
subghz_protocol_encoder_legrand_deserialize(void* context, FlipperFormat* flipper_format) { | ||
furi_assert(context); | ||
SubGhzProtocolEncoderLegrand* instance = context; | ||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError; | ||
do { | ||
ret = subghz_block_generic_deserialize_check_count_bit( | ||
&instance->generic, | ||
flipper_format, | ||
subghz_protocol_legrand_const.min_count_bit_for_found); | ||
if(ret != SubGhzProtocolStatusOk) { | ||
break; | ||
} | ||
if(!flipper_format_rewind(flipper_format)) { | ||
FURI_LOG_E(TAG, "Rewind error"); | ||
ret = SubGhzProtocolStatusErrorParserOthers; | ||
break; | ||
} | ||
if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { | ||
FURI_LOG_E(TAG, "Missing TE"); | ||
ret = SubGhzProtocolStatusErrorParserTe; | ||
break; | ||
} | ||
// optional parameter parameter | ||
flipper_format_read_uint32( | ||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); | ||
|
||
if(!subghz_protocol_encoder_legrand_get_upload(instance)) { | ||
ret = SubGhzProtocolStatusErrorEncoderGetUpload; | ||
break; | ||
} | ||
instance->encoder.is_running = true; | ||
} while(false); | ||
|
||
return ret; | ||
} | ||
|
||
void subghz_protocol_encoder_legrand_stop(void* context) { | ||
SubGhzProtocolEncoderLegrand* instance = context; | ||
instance->encoder.is_running = false; | ||
} | ||
|
||
LevelDuration subghz_protocol_encoder_legrand_yield(void* context) { | ||
SubGhzProtocolEncoderLegrand* instance = context; | ||
|
||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { | ||
instance->encoder.is_running = false; | ||
return level_duration_reset(); | ||
} | ||
|
||
LevelDuration ret = instance->encoder.upload[instance->encoder.front]; | ||
|
||
if(++instance->encoder.front == instance->encoder.size_upload) { | ||
instance->encoder.repeat--; | ||
instance->encoder.front = 0; | ||
} | ||
|
||
return ret; | ||
} | ||
|
||
void* subghz_protocol_decoder_legrand_alloc(SubGhzEnvironment* environment) { | ||
UNUSED(environment); | ||
SubGhzProtocolDecoderLegrand* instance = malloc(sizeof(SubGhzProtocolDecoderLegrand)); | ||
instance->base.protocol = &subghz_protocol_legrand; | ||
instance->generic.protocol_name = instance->base.protocol->name; | ||
return instance; | ||
} | ||
|
||
void subghz_protocol_decoder_legrand_free(void* context) { | ||
furi_assert(context); | ||
SubGhzProtocolDecoderLegrand* instance = context; | ||
free(instance); | ||
} | ||
|
||
void subghz_protocol_decoder_legrand_reset(void* context) { | ||
furi_assert(context); | ||
SubGhzProtocolDecoderLegrand* instance = context; | ||
instance->decoder.parser_step = LegrandDecoderStepReset; | ||
} | ||
|
||
void subghz_protocol_decoder_legrand_feed(void* context, bool level, uint32_t duration) { | ||
furi_assert(context); | ||
SubGhzProtocolDecoderLegrand* instance = context; | ||
|
||
switch(instance->decoder.parser_step) { | ||
case LegrandDecoderStepReset: | ||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short * 16) < | ||
subghz_protocol_legrand_const.te_delta * 4)) { | ||
// Found sync | ||
instance->decoder.parser_step = LegrandDecoderStepSaveData; | ||
instance->decoder.decode_data = 0; | ||
instance->decoder.decode_count_bit = 0; | ||
instance->te = 0; | ||
} | ||
break; | ||
case LegrandDecoderStepSaveData: | ||
// the first bit is not used in the calculation because its space is transmitted together | ||
// with the sync pulse as a single space | ||
if(instance->decoder.decode_count_bit > 0) { | ||
instance->te += duration; | ||
} | ||
|
||
if(!level) { | ||
break; | ||
} | ||
|
||
if(DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short) < | ||
subghz_protocol_legrand_const.te_delta) { | ||
subghz_protocol_blocks_add_bit(&instance->decoder, 0); | ||
} else if( | ||
DURATION_DIFF(duration, subghz_protocol_legrand_const.te_long) < | ||
subghz_protocol_legrand_const.te_delta) { | ||
subghz_protocol_blocks_add_bit(&instance->decoder, 1); | ||
} else { | ||
instance->decoder.parser_step = LegrandDecoderStepReset; | ||
break; | ||
} | ||
|
||
if(instance->decoder.decode_count_bit == | ||
subghz_protocol_legrand_const.min_count_bit_for_found) { | ||
instance->te /= (instance->decoder.decode_count_bit - 1) * 4; | ||
|
||
instance->generic.data = instance->decoder.decode_data; | ||
instance->generic.data_count_bit = instance->decoder.decode_count_bit; | ||
|
||
if(instance->base.callback) | ||
instance->base.callback(&instance->base, instance->base.context); | ||
|
||
instance->decoder.parser_step = LegrandDecoderStepReset; | ||
instance->decoder.decode_data = 0; | ||
instance->decoder.decode_count_bit = 0; | ||
instance->te = 0; | ||
} | ||
break; | ||
} | ||
} | ||
|
||
uint32_t subghz_protocol_decoder_legrand_get_hash_data(void* context) { | ||
furi_assert(context); | ||
SubGhzProtocolDecoderLegrand* instance = context; | ||
return subghz_protocol_blocks_get_hash_data_long( | ||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); | ||
} | ||
|
||
SubGhzProtocolStatus subghz_protocol_decoder_legrand_serialize( | ||
void* context, | ||
FlipperFormat* flipper_format, | ||
SubGhzRadioPreset* preset) { | ||
furi_assert(context); | ||
SubGhzProtocolDecoderLegrand* instance = context; | ||
SubGhzProtocolStatus ret = | ||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset); | ||
if((ret == SubGhzProtocolStatusOk) && | ||
!flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { | ||
FURI_LOG_E(TAG, "Unable to add TE"); | ||
ret = SubGhzProtocolStatusErrorParserTe; | ||
} | ||
return ret; | ||
} | ||
|
||
SubGhzProtocolStatus | ||
subghz_protocol_decoder_legrand_deserialize(void* context, FlipperFormat* flipper_format) { | ||
furi_assert(context); | ||
SubGhzProtocolDecoderLegrand* instance = context; | ||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError; | ||
do { | ||
ret = subghz_block_generic_deserialize_check_count_bit( | ||
&instance->generic, | ||
flipper_format, | ||
subghz_protocol_legrand_const.min_count_bit_for_found); | ||
if(ret != SubGhzProtocolStatusOk) { | ||
break; | ||
} | ||
if(!flipper_format_rewind(flipper_format)) { | ||
FURI_LOG_E(TAG, "Rewind error"); | ||
ret = SubGhzProtocolStatusErrorParserOthers; | ||
break; | ||
} | ||
if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { | ||
FURI_LOG_E(TAG, "Missing TE"); | ||
ret = SubGhzProtocolStatusErrorParserTe; | ||
break; | ||
} | ||
} while(false); | ||
|
||
return ret; | ||
} | ||
|
||
void subghz_protocol_decoder_legrand_get_string(void* context, FuriString* output) { | ||
furi_assert(context); | ||
SubGhzProtocolDecoderLegrand* instance = context; | ||
|
||
furi_string_cat_printf( | ||
output, | ||
"%s %dbit\r\n" | ||
"Key:0x%05lX\r\n" | ||
"Te:%luus\r\n", | ||
instance->generic.protocol_name, | ||
instance->generic.data_count_bit, | ||
(uint32_t)(instance->generic.data & 0xFFFFFF), | ||
instance->te); | ||
} |
Oops, something went wrong.