Files
MMX fad487df0e SubGHz: Added 9 new protocols, fixes to existing protocols (#4255)
* 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>
2025-10-01 18:05:50 +04:00

472 lines
16 KiB
C

#include "hay21.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include "../blocks/custom_btn_i.h"
#define TAG "SubGhzProtocolHay21"
static const SubGhzBlockConst subghz_protocol_hay21_const = {
.te_short = 300,
.te_long = 700,
.te_delta = 150,
.min_count_bit_for_found = 21,
};
struct SubGhzProtocolDecoderHay21 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderHay21 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
Hay21DecoderStepReset = 0,
Hay21DecoderStepSaveDuration,
Hay21DecoderStepCheckDuration,
} Hay21DecoderStep;
const SubGhzProtocolDecoder subghz_protocol_hay21_decoder = {
.alloc = subghz_protocol_decoder_hay21_alloc,
.free = subghz_protocol_decoder_hay21_free,
.feed = subghz_protocol_decoder_hay21_feed,
.reset = subghz_protocol_decoder_hay21_reset,
.get_hash_data = subghz_protocol_decoder_hay21_get_hash_data,
.serialize = subghz_protocol_decoder_hay21_serialize,
.deserialize = subghz_protocol_decoder_hay21_deserialize,
.get_string = subghz_protocol_decoder_hay21_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_hay21_encoder = {
.alloc = subghz_protocol_encoder_hay21_alloc,
.free = subghz_protocol_encoder_hay21_free,
.deserialize = subghz_protocol_encoder_hay21_deserialize,
.stop = subghz_protocol_encoder_hay21_stop,
.yield = subghz_protocol_encoder_hay21_yield,
};
const SubGhzProtocol subghz_protocol_hay21 = {
.name = SUBGHZ_PROTOCOL_HAY21_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_hay21_decoder,
.encoder = &subghz_protocol_hay21_encoder,
};
void* subghz_protocol_encoder_hay21_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderHay21* instance = malloc(sizeof(SubGhzProtocolEncoderHay21));
instance->base.protocol = &subghz_protocol_hay21;
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_hay21_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderHay21* instance = context;
free(instance->encoder.upload);
free(instance);
}
// Get custom button code
static uint8_t subghz_protocol_hay21_get_btn_code(void) {
uint8_t custom_btn_id = subghz_custom_btn_get();
uint8_t original_btn_code = subghz_custom_btn_get_original();
uint8_t btn = original_btn_code;
// Set custom button
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) {
// Restore original button code
btn = original_btn_code;
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) {
switch(original_btn_code) {
case 0x5A:
btn = 0xC3;
break;
case 0xC3:
btn = 0x5A;
break;
case 0x88:
btn = 0x5A;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) {
switch(original_btn_code) {
case 0x5A:
btn = 0x88;
break;
case 0xC3:
btn = 0x88;
break;
case 0x88:
btn = 0xC3;
break;
default:
break;
}
}
return btn;
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderHay21 instance
*/
static void subghz_protocol_encoder_hay21_get_upload(SubGhzProtocolEncoderHay21* instance) {
furi_assert(instance);
// Generate new key using custom or default button
instance->generic.btn = subghz_protocol_hay21_get_btn_code();
// Counter increment
if(instance->generic.cnt < 0xF) {
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xF) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
}
if(furi_hal_subghz_get_rolling_counter_mult() >= 0xF) {
instance->generic.cnt = 0xF;
}
} else if(instance->generic.cnt >= 0xF) {
instance->generic.cnt = 0;
}
// Reconstruction of the data
instance->generic.data =
((uint64_t)instance->generic.btn << 13 | (uint64_t)instance->generic.serial << 5 |
instance->generic.cnt << 1) |
0b1;
size_t index = 0;
// Send key and GAP between parcels
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_hay21_const.te_long);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_hay21_const.te_long * 6);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_hay21_const.te_short);
}
} else {
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_hay21_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_hay21_const.te_long * 6);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_hay21_const.te_long);
}
}
}
instance->encoder.size_upload = index;
return;
}
/**
* Analysis of received data and parsing serial number
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_hay21_remote_controller(SubGhzBlockGeneric* instance) {
instance->btn = (instance->data >> 13) & 0xFF;
instance->serial = (instance->data >> 5) & 0xFF;
instance->cnt = (instance->data >> 1) & 0xF;
// Save original button for later use
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->btn);
}
subghz_custom_btn_set_max(2);
// Hay21 Decoder
// 09.2024 - @xMasterX (MMX)
// Key samples (inverted)
// button serial CNT (goes lower since 0/1 are inverted)
//14A84A = 000 10100101 01000010 0101 0 (cnt 5)
//14A848 = 000 10100101 01000010 0100 0 (cnt 4)
//14A846 = 000 10100101 01000010 0011 0 (cnt 3)
//14A844 = 000 10100101 01000010 0010 0 (cnt 2)
//14A842 = 000 10100101 01000010 0001 0 (cnt 1)
//14A840 = 000 10100101 01000010 0000 0 (cnt 0)
//14A85E = 000 10100101 01000010 1111 0 (cnt F)
//14A85C = 000 10100101 01000010 1110 0 (cnt E)
//14A85A = 000 10100101 01000010 1101 0 (cnt D)
//14A858 = 000 10100101 01000010 1100 0 (cnt C)
//14A856 = 000 10100101 01000010 1011 0 (cnt B)
// 0xA5 (Labeled as On/Off on the remote board)
// 0x3C (Labeled as Mode on the remote board)
// 0x42 (Serial)
// BTN Serial CNT
//078854 = 000 00111100 01000010 1010 0 (cnt A)
//078852 = 000 00111100 01000010 1001 0 (cnt 9)
//078850 = 000 00111100 01000010 1000 0 (cnt 8)
//07884E = 000 00111100 01000010 0111 0 (cnt 7)
// Inverted back
//1877B9 = 000 11000011 10111101 1100 1
//1877BB = 000 11000011 10111101 1101 1
//1877BD = 000 11000011 10111101 1110 1
//0B57BF = 000 01011010 10111101 1111 1
}
SubGhzProtocolStatus
subghz_protocol_encoder_hay21_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderHay21* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_hay21_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);
subghz_protocol_hay21_remote_controller(&instance->generic);
subghz_protocol_encoder_hay21_get_upload(instance);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Key");
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_hay21_stop(void* context) {
SubGhzProtocolEncoderHay21* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_hay21_yield(void* context) {
SubGhzProtocolEncoderHay21* 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_hay21_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderHay21* instance = malloc(sizeof(SubGhzProtocolDecoderHay21));
instance->base.protocol = &subghz_protocol_hay21;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_hay21_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
free(instance);
}
void subghz_protocol_decoder_hay21_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
instance->decoder.parser_step = Hay21DecoderStepReset;
}
void subghz_protocol_decoder_hay21_feed(void* context, bool level, volatile uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
switch(instance->decoder.parser_step) {
case Hay21DecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long * 6) <
subghz_protocol_hay21_const.te_delta * 3)) {
//Found GAP
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
}
break;
case Hay21DecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = Hay21DecoderStepCheckDuration;
} else {
instance->decoder.parser_step = Hay21DecoderStepReset;
}
break;
case Hay21DecoderStepCheckDuration:
if(!level) {
// Bit 1 is long + short timing
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_long) <
subghz_protocol_hay21_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_hay21_const.te_short) <
subghz_protocol_hay21_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
// Bit 0 is short + long timing
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_short) <
subghz_protocol_hay21_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long) <
subghz_protocol_hay21_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
} else if(
// End of the key
DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long * 6) <
subghz_protocol_hay21_const.te_delta * 2) {
//Found next GAP and add bit 0 or 1
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_long) <
subghz_protocol_hay21_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
}
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_short) <
subghz_protocol_hay21_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
}
// If got 21 bits key reading is finished
if(instance->decoder.decode_count_bit ==
subghz_protocol_hay21_const.min_count_bit_for_found) {
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 = Hay21DecoderStepReset;
} else {
instance->decoder.parser_step = Hay21DecoderStepReset;
}
} else {
instance->decoder.parser_step = Hay21DecoderStepReset;
}
break;
}
}
/**
* Get button name.
* @param btn Button number, 4 bit
*/
static const char* subghz_protocol_hay21_get_button_name(uint8_t btn) {
const char* btn_name;
switch(btn) {
case 0x5A:
btn_name = "On/Off";
break;
case 0xC3:
btn_name = "Mode";
break;
case 0x88:
btn_name = "Hold";
break;
default:
btn_name = "Unknown";
break;
}
return btn_name;
}
uint8_t subghz_protocol_decoder_hay21_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_hay21_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_hay21_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_hay21_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_hay21_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
// Parse serial, button, counter
subghz_protocol_hay21_remote_controller(&instance->generic);
furi_string_cat_printf(
output,
"%s - %dbit\r\n"
"Key: 0x%06lX\r\n"
"Serial: 0x%02X\r\n"
"Btn: 0x%01X - %s\r\n"
"Cnt: 0x%01X\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
(uint8_t)(instance->generic.serial & 0xFF),
instance->generic.btn,
subghz_protocol_hay21_get_button_name(instance->generic.btn),
(uint8_t)(instance->generic.cnt & 0xF));
}