mirror of
https://github.com/flipperdevices/flipperzero-firmware.git
synced 2025-12-12 20:59:50 +04:00
* Fix Typos * Tune decoders * Better parsing, show more data in existing protocols * Add new protocols * Update keeloqs * Add unit tests & raws * Add honeywell unittest * Comment until better solution is found Adding GAPs to be sent first to make signal better suitable for decoder (decoding from only one signal sample) does nothing, needs something else TODO: Fix encoders? * suppressed missing issue warning * subghz: re-enabled failing encoder tests * Fix two? 3 left * properly do gangqi and marantec for unit test and real use * fix unit tests now * fix possible memory leak * reset decoder step too * subghz: extra encoder safety; report random signal test results on failure * unit_tests: subghz: renamed test file for consistency * subghz: more explicit buffer position resets * Fix gangqi samples --------- Co-authored-by: hedger <hedger@users.noreply.github.com> Co-authored-by: hedger <hedger@nanode.su>
516 lines
18 KiB
C
516 lines
18 KiB
C
#include "magellan.h"
|
|
|
|
#include "../blocks/const.h"
|
|
#include "../blocks/decoder.h"
|
|
#include "../blocks/encoder.h"
|
|
#include "../blocks/generic.h"
|
|
#include "../blocks/math.h"
|
|
|
|
#define TAG "SubGhzProtocolMagellan"
|
|
|
|
static const SubGhzBlockConst subghz_protocol_magellan_const = {
|
|
.te_short = 200,
|
|
.te_long = 400,
|
|
.te_delta = 100,
|
|
.min_count_bit_for_found = 32,
|
|
};
|
|
|
|
struct SubGhzProtocolDecoderMagellan {
|
|
SubGhzProtocolDecoderBase base;
|
|
|
|
SubGhzBlockDecoder decoder;
|
|
SubGhzBlockGeneric generic;
|
|
uint16_t header_count;
|
|
};
|
|
|
|
struct SubGhzProtocolEncoderMagellan {
|
|
SubGhzProtocolEncoderBase base;
|
|
|
|
SubGhzProtocolBlockEncoder encoder;
|
|
SubGhzBlockGeneric generic;
|
|
};
|
|
|
|
typedef enum {
|
|
MagellanDecoderStepReset = 0,
|
|
MagellanDecoderStepCheckPreambula,
|
|
MagellanDecoderStepFoundPreambula,
|
|
MagellanDecoderStepSaveDuration,
|
|
MagellanDecoderStepCheckDuration,
|
|
} MagellanDecoderStep;
|
|
|
|
const SubGhzProtocolDecoder subghz_protocol_magellan_decoder = {
|
|
.alloc = subghz_protocol_decoder_magellan_alloc,
|
|
.free = subghz_protocol_decoder_magellan_free,
|
|
|
|
.feed = subghz_protocol_decoder_magellan_feed,
|
|
.reset = subghz_protocol_decoder_magellan_reset,
|
|
|
|
.get_hash_data = subghz_protocol_decoder_magellan_get_hash_data,
|
|
.serialize = subghz_protocol_decoder_magellan_serialize,
|
|
.deserialize = subghz_protocol_decoder_magellan_deserialize,
|
|
.get_string = subghz_protocol_decoder_magellan_get_string,
|
|
};
|
|
|
|
const SubGhzProtocolEncoder subghz_protocol_magellan_encoder = {
|
|
.alloc = subghz_protocol_encoder_magellan_alloc,
|
|
.free = subghz_protocol_encoder_magellan_free,
|
|
|
|
.deserialize = subghz_protocol_encoder_magellan_deserialize,
|
|
.stop = subghz_protocol_encoder_magellan_stop,
|
|
.yield = subghz_protocol_encoder_magellan_yield,
|
|
};
|
|
|
|
const SubGhzProtocol subghz_protocol_magellan = {
|
|
.name = SUBGHZ_PROTOCOL_MAGELLAN_NAME,
|
|
.type = SubGhzProtocolTypeStatic,
|
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
|
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
|
|
|
.decoder = &subghz_protocol_magellan_decoder,
|
|
.encoder = &subghz_protocol_magellan_encoder,
|
|
};
|
|
|
|
void* subghz_protocol_encoder_magellan_alloc(SubGhzEnvironment* environment) {
|
|
UNUSED(environment);
|
|
SubGhzProtocolEncoderMagellan* instance = malloc(sizeof(SubGhzProtocolEncoderMagellan));
|
|
|
|
instance->base.protocol = &subghz_protocol_magellan;
|
|
instance->generic.protocol_name = instance->base.protocol->name;
|
|
|
|
instance->encoder.repeat = 10;
|
|
instance->encoder.size_upload = 256;
|
|
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
|
instance->encoder.is_running = false;
|
|
return instance;
|
|
}
|
|
|
|
void subghz_protocol_encoder_magellan_free(void* context) {
|
|
furi_assert(context);
|
|
SubGhzProtocolEncoderMagellan* instance = context;
|
|
free(instance->encoder.upload);
|
|
free(instance);
|
|
}
|
|
|
|
/**
|
|
* Generating an upload from data.
|
|
* @param instance Pointer to a SubGhzProtocolEncoderMagellan instance
|
|
* @return true On success
|
|
*/
|
|
static bool subghz_protocol_encoder_magellan_get_upload(SubGhzProtocolEncoderMagellan* instance) {
|
|
furi_assert(instance);
|
|
|
|
size_t index = 0;
|
|
|
|
//Send header
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short * 4);
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_short);
|
|
for(uint8_t i = 0; i < 12; i++) {
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short);
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_short);
|
|
}
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short);
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long);
|
|
|
|
//Send start bit
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_long * 3);
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long);
|
|
|
|
//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(true, (uint32_t)subghz_protocol_magellan_const.te_short);
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long);
|
|
} else {
|
|
//send bit 0
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_long);
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_short);
|
|
}
|
|
}
|
|
|
|
//Send stop bit
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short);
|
|
instance->encoder.upload[index++] =
|
|
level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long * 100);
|
|
|
|
instance->encoder.size_upload = index;
|
|
return true;
|
|
}
|
|
|
|
SubGhzProtocolStatus
|
|
subghz_protocol_encoder_magellan_deserialize(void* context, FlipperFormat* flipper_format) {
|
|
furi_assert(context);
|
|
SubGhzProtocolEncoderMagellan* instance = context;
|
|
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
|
do {
|
|
ret = subghz_block_generic_deserialize_check_count_bit(
|
|
&instance->generic,
|
|
flipper_format,
|
|
subghz_protocol_magellan_const.min_count_bit_for_found);
|
|
if(ret != SubGhzProtocolStatusOk) {
|
|
break;
|
|
}
|
|
//optional parameter parameter
|
|
flipper_format_read_uint32(
|
|
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
|
|
|
if(!subghz_protocol_encoder_magellan_get_upload(instance)) {
|
|
instance->encoder.front = 0; // reset before start
|
|
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
|
break;
|
|
}
|
|
instance->encoder.is_running = true;
|
|
} while(false);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void subghz_protocol_encoder_magellan_stop(void* context) {
|
|
SubGhzProtocolEncoderMagellan* instance = context;
|
|
instance->encoder.is_running = false;
|
|
instance->encoder.front = 0; // reset position
|
|
}
|
|
|
|
LevelDuration subghz_protocol_encoder_magellan_yield(void* context) {
|
|
SubGhzProtocolEncoderMagellan* 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_magellan_alloc(SubGhzEnvironment* environment) {
|
|
UNUSED(environment);
|
|
SubGhzProtocolDecoderMagellan* instance = malloc(sizeof(SubGhzProtocolDecoderMagellan));
|
|
instance->base.protocol = &subghz_protocol_magellan;
|
|
instance->generic.protocol_name = instance->base.protocol->name;
|
|
return instance;
|
|
}
|
|
|
|
void subghz_protocol_decoder_magellan_free(void* context) {
|
|
furi_assert(context);
|
|
SubGhzProtocolDecoderMagellan* instance = context;
|
|
free(instance);
|
|
}
|
|
|
|
void subghz_protocol_decoder_magellan_reset(void* context) {
|
|
furi_assert(context);
|
|
SubGhzProtocolDecoderMagellan* instance = context;
|
|
instance->decoder.parser_step = MagellanDecoderStepReset;
|
|
}
|
|
|
|
uint8_t subghz_protocol_magellan_crc8(uint8_t* data, size_t len) {
|
|
uint8_t crc = 0x00;
|
|
size_t i, j;
|
|
for(i = 0; i < len; i++) {
|
|
crc ^= data[i];
|
|
for(j = 0; j < 8; j++) {
|
|
if((crc & 0x80) != 0)
|
|
crc = (uint8_t)((crc << 1) ^ 0x31);
|
|
else
|
|
crc <<= 1;
|
|
}
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
static bool subghz_protocol_magellan_check_crc(SubGhzProtocolDecoderMagellan* instance) {
|
|
uint8_t data[3] = {
|
|
instance->decoder.decode_data >> 24,
|
|
instance->decoder.decode_data >> 16,
|
|
instance->decoder.decode_data >> 8};
|
|
return (instance->decoder.decode_data & 0xFF) ==
|
|
subghz_protocol_magellan_crc8(data, sizeof(data));
|
|
}
|
|
|
|
void subghz_protocol_decoder_magellan_feed(void* context, bool level, uint32_t duration) {
|
|
furi_assert(context);
|
|
SubGhzProtocolDecoderMagellan* instance = context;
|
|
|
|
switch(instance->decoder.parser_step) {
|
|
case MagellanDecoderStepReset:
|
|
if((level) && (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_short) <
|
|
subghz_protocol_magellan_const.te_delta)) {
|
|
instance->decoder.parser_step = MagellanDecoderStepCheckPreambula;
|
|
instance->decoder.te_last = duration;
|
|
instance->header_count = 0;
|
|
}
|
|
break;
|
|
|
|
case MagellanDecoderStepCheckPreambula:
|
|
if(level) {
|
|
instance->decoder.te_last = duration;
|
|
} else {
|
|
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_short) <
|
|
subghz_protocol_magellan_const.te_delta) &&
|
|
(DURATION_DIFF(duration, subghz_protocol_magellan_const.te_short) <
|
|
subghz_protocol_magellan_const.te_delta)) {
|
|
// Found header
|
|
instance->header_count++;
|
|
} else if(
|
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_short) <
|
|
subghz_protocol_magellan_const.te_delta) &&
|
|
(DURATION_DIFF(duration, subghz_protocol_magellan_const.te_long) <
|
|
subghz_protocol_magellan_const.te_delta * 2) &&
|
|
(instance->header_count > 10)) {
|
|
instance->decoder.parser_step = MagellanDecoderStepFoundPreambula;
|
|
} else {
|
|
instance->decoder.parser_step = MagellanDecoderStepReset;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MagellanDecoderStepFoundPreambula:
|
|
if(level) {
|
|
instance->decoder.te_last = duration;
|
|
} else {
|
|
if((DURATION_DIFF(
|
|
instance->decoder.te_last, subghz_protocol_magellan_const.te_short * 6) <
|
|
subghz_protocol_magellan_const.te_delta * 3) &&
|
|
(DURATION_DIFF(duration, subghz_protocol_magellan_const.te_long) <
|
|
subghz_protocol_magellan_const.te_delta * 2)) {
|
|
instance->decoder.parser_step = MagellanDecoderStepSaveDuration;
|
|
instance->decoder.decode_data = 0;
|
|
instance->decoder.decode_count_bit = 0;
|
|
} else {
|
|
instance->decoder.parser_step = MagellanDecoderStepReset;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MagellanDecoderStepSaveDuration:
|
|
if(level) {
|
|
instance->decoder.te_last = duration;
|
|
instance->decoder.parser_step = MagellanDecoderStepCheckDuration;
|
|
} else {
|
|
instance->decoder.parser_step = MagellanDecoderStepReset;
|
|
}
|
|
break;
|
|
|
|
case MagellanDecoderStepCheckDuration:
|
|
if(!level) {
|
|
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_short) <
|
|
subghz_protocol_magellan_const.te_delta) &&
|
|
(DURATION_DIFF(duration, subghz_protocol_magellan_const.te_long) <
|
|
subghz_protocol_magellan_const.te_delta)) {
|
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
|
instance->decoder.parser_step = MagellanDecoderStepSaveDuration;
|
|
} else if(
|
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_long) <
|
|
subghz_protocol_magellan_const.te_delta) &&
|
|
(DURATION_DIFF(duration, subghz_protocol_magellan_const.te_short) <
|
|
subghz_protocol_magellan_const.te_delta)) {
|
|
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
|
instance->decoder.parser_step = MagellanDecoderStepSaveDuration;
|
|
} else if(duration >= (subghz_protocol_magellan_const.te_long * 3)) {
|
|
//Found stop bit
|
|
if((instance->decoder.decode_count_bit ==
|
|
subghz_protocol_magellan_const.min_count_bit_for_found) &&
|
|
subghz_protocol_magellan_check_crc(instance)) {
|
|
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.decode_data = 0;
|
|
instance->decoder.decode_count_bit = 0;
|
|
instance->decoder.parser_step = MagellanDecoderStepReset;
|
|
} else {
|
|
instance->decoder.parser_step = MagellanDecoderStepReset;
|
|
}
|
|
} else {
|
|
instance->decoder.parser_step = MagellanDecoderStepReset;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Analysis of received data
|
|
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
|
*/
|
|
static void subghz_protocol_magellan_check_remote_controller(SubGhzBlockGeneric* instance) {
|
|
/*
|
|
* package 32b data 24b CRC8
|
|
* 0x037AE4828 => 001101111010111001001000 00101000
|
|
*
|
|
* 0x037AE48 (flipped in reverse bit sequence) => 0x1275EC
|
|
*
|
|
* 0x1275EC => 0x12-event codes, 0x75EC-serial (dec 117236)
|
|
*
|
|
* Event codes consist of two parts:
|
|
* - The upper nibble (bits 7-4) represents the event type:
|
|
* - 0x00: Nothing
|
|
* - 0x01: Door
|
|
* - 0x02: Motion
|
|
* - 0x03: Smoke Alarm
|
|
* - 0x04: REM1
|
|
* - 0x05: REM1 with subtype Off1
|
|
* - 0x06: REM2
|
|
* - 0x07: REM2 with subtype Off1
|
|
* - Others: Unknown
|
|
* - The lower nibble (bits 3-0) represents the event subtype, which varies based on the model type:
|
|
* - If the model type is greater than 0x03 (e.g., REM1 or REM2):
|
|
* - 0x00: Arm1
|
|
* - 0x01: Btn1
|
|
* - 0x02: Btn2
|
|
* - 0x03: Btn3
|
|
* - 0x08: Reset
|
|
* - 0x09: LowBatt
|
|
* - 0x0A: BattOk
|
|
* - 0x0B: Learn
|
|
* - Others: Unknown
|
|
* - Otherwise:
|
|
* - 0x00: Sealed
|
|
* - 0x01: Alarm
|
|
* - 0x02: Tamper
|
|
* - 0x03: Alarm + Tamper
|
|
* - 0x08: Reset
|
|
* - 0x09: LowBatt
|
|
* - 0x0A: BattOk
|
|
* - 0x0B: Learn
|
|
* - Others: Unknown
|
|
*
|
|
*/
|
|
uint64_t data_rev = subghz_protocol_blocks_reverse_key(instance->data >> 8, 24);
|
|
instance->serial = data_rev & 0xFFFF;
|
|
instance->btn = (data_rev >> 16) & 0xFF;
|
|
}
|
|
|
|
static void subghz_protocol_magellan_get_event_serialize(uint8_t event, FuriString* output) {
|
|
const char* event_type;
|
|
const char* event_subtype;
|
|
|
|
switch((event >> 4) & 0x0F) {
|
|
case 0x00:
|
|
event_type = "Nothing";
|
|
break;
|
|
case 0x01:
|
|
event_type = "Door";
|
|
break;
|
|
case 0x02:
|
|
event_type = "Motion";
|
|
break;
|
|
case 0x03:
|
|
event_type = "Smoke Alarm";
|
|
break;
|
|
case 0x04:
|
|
event_type = "REM1";
|
|
break;
|
|
case 0x05:
|
|
event_type = "REM1";
|
|
event_subtype = "Off1";
|
|
furi_string_cat_printf(output, "%s - %s", event_type, event_subtype);
|
|
return;
|
|
case 0x06:
|
|
event_type = "REM2";
|
|
event_subtype = "Off1";
|
|
furi_string_cat_printf(output, "%s - %s", event_type, event_subtype);
|
|
return;
|
|
default:
|
|
event_type = "Unknown";
|
|
break;
|
|
}
|
|
|
|
switch(event & 0x0F) {
|
|
case 0x00:
|
|
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Arm1" : "Sealed";
|
|
break;
|
|
case 0x01:
|
|
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn1" : "Alarm";
|
|
break;
|
|
case 0x02:
|
|
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn2" : "Tamper";
|
|
break;
|
|
case 0x03:
|
|
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn3" : "Alarm + Tamper";
|
|
break;
|
|
case 0x08:
|
|
event_subtype = "Reset";
|
|
break;
|
|
case 0x09:
|
|
event_subtype = "LowBatt";
|
|
break;
|
|
case 0x0A:
|
|
event_subtype = "BattOk";
|
|
break;
|
|
case 0x0B:
|
|
event_subtype = "Learn";
|
|
break;
|
|
default:
|
|
event_subtype = "Unknown";
|
|
break;
|
|
}
|
|
|
|
furi_string_cat_printf(output, "%s - %s", event_type, event_subtype);
|
|
}
|
|
|
|
uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context) {
|
|
furi_assert(context);
|
|
SubGhzProtocolDecoderMagellan* instance = context;
|
|
return subghz_protocol_blocks_get_hash_data(
|
|
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
|
}
|
|
|
|
SubGhzProtocolStatus subghz_protocol_decoder_magellan_serialize(
|
|
void* context,
|
|
FlipperFormat* flipper_format,
|
|
SubGhzRadioPreset* preset) {
|
|
furi_assert(context);
|
|
SubGhzProtocolDecoderMagellan* instance = context;
|
|
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
|
}
|
|
|
|
SubGhzProtocolStatus
|
|
subghz_protocol_decoder_magellan_deserialize(void* context, FlipperFormat* flipper_format) {
|
|
furi_assert(context);
|
|
SubGhzProtocolDecoderMagellan* instance = context;
|
|
return subghz_block_generic_deserialize_check_count_bit(
|
|
&instance->generic,
|
|
flipper_format,
|
|
subghz_protocol_magellan_const.min_count_bit_for_found);
|
|
}
|
|
|
|
void subghz_protocol_decoder_magellan_get_string(void* context, FuriString* output) {
|
|
furi_assert(context);
|
|
SubGhzProtocolDecoderMagellan* instance = context;
|
|
subghz_protocol_magellan_check_remote_controller(&instance->generic);
|
|
furi_string_cat_printf(
|
|
output,
|
|
"%s %dbit\r\n"
|
|
"Key:0x%08lX\r\n"
|
|
"Sn:%03ld%03ld, Event:0x%02X\r\n"
|
|
"Stat:",
|
|
instance->generic.protocol_name,
|
|
instance->generic.data_count_bit,
|
|
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
|
(instance->generic.serial >> 8) & 0xFF,
|
|
instance->generic.serial & 0xFF,
|
|
instance->generic.btn);
|
|
|
|
subghz_protocol_magellan_get_event_serialize(instance->generic.btn, output);
|
|
}
|