1
mirror of https://github.com/flipperdevices/flipperzero-firmware.git synced 2025-12-12 04:41:26 +04:00
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

387 lines
14 KiB
C

#include "phoenix_v2.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#define TAG "SubGhzProtocolPhoenixV2"
//transmission only static mode
static const SubGhzBlockConst subghz_protocol_phoenix_v2_const = {
.te_short = 427,
.te_long = 853,
.te_delta = 100,
.min_count_bit_for_found = 52,
};
struct SubGhzProtocolDecoderPhoenix_V2 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderPhoenix_V2 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
Phoenix_V2DecoderStepReset = 0,
Phoenix_V2DecoderStepFoundStartBit,
Phoenix_V2DecoderStepSaveDuration,
Phoenix_V2DecoderStepCheckDuration,
} Phoenix_V2DecoderStep;
const SubGhzProtocolDecoder subghz_protocol_phoenix_v2_decoder = {
.alloc = subghz_protocol_decoder_phoenix_v2_alloc,
.free = subghz_protocol_decoder_phoenix_v2_free,
.feed = subghz_protocol_decoder_phoenix_v2_feed,
.reset = subghz_protocol_decoder_phoenix_v2_reset,
.get_hash_data = subghz_protocol_decoder_phoenix_v2_get_hash_data,
.serialize = subghz_protocol_decoder_phoenix_v2_serialize,
.deserialize = subghz_protocol_decoder_phoenix_v2_deserialize,
.get_string = subghz_protocol_decoder_phoenix_v2_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_phoenix_v2_encoder = {
.alloc = subghz_protocol_encoder_phoenix_v2_alloc,
.free = subghz_protocol_encoder_phoenix_v2_free,
.deserialize = subghz_protocol_encoder_phoenix_v2_deserialize,
.stop = subghz_protocol_encoder_phoenix_v2_stop,
.yield = subghz_protocol_encoder_phoenix_v2_yield,
};
const SubGhzProtocol subghz_protocol_phoenix_v2 = {
.name = SUBGHZ_PROTOCOL_PHOENIX_V2_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_phoenix_v2_decoder,
.encoder = &subghz_protocol_phoenix_v2_encoder,
};
void* subghz_protocol_encoder_phoenix_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderPhoenix_V2* instance = malloc(sizeof(SubGhzProtocolEncoderPhoenix_V2));
instance->base.protocol = &subghz_protocol_phoenix_v2;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 10;
instance->encoder.size_upload = 128;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_phoenix_v2_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderPhoenix_V2* instance = context;
free(instance->encoder.upload);
free(instance);
}
// Pre define functions
static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance);
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance
* @return true On success
*/
static bool
subghz_protocol_encoder_phoenix_v2_get_upload(SubGhzProtocolEncoderPhoenix_V2* 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 header
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_phoenix_v2_const.te_short * 60);
//Send start bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_phoenix_v2_const.te_short * 6);
//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)subghz_protocol_phoenix_v2_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_phoenix_v2_const.te_short);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_phoenix_v2_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_phoenix_v2_const.te_long);
}
}
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderPhoenix_V2* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_phoenix_v2_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_phoenix_v2_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_phoenix_v2_stop(void* context) {
SubGhzProtocolEncoderPhoenix_V2* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_phoenix_v2_yield(void* context) {
SubGhzProtocolEncoderPhoenix_V2* 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_phoenix_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderPhoenix_V2* instance = malloc(sizeof(SubGhzProtocolDecoderPhoenix_V2));
instance->base.protocol = &subghz_protocol_phoenix_v2;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_phoenix_v2_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderPhoenix_V2* instance = context;
free(instance);
}
void subghz_protocol_decoder_phoenix_v2_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderPhoenix_V2* instance = context;
instance->decoder.parser_step = Phoenix_V2DecoderStepReset;
}
void subghz_protocol_decoder_phoenix_v2_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderPhoenix_V2* instance = context;
switch(instance->decoder.parser_step) {
case Phoenix_V2DecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_phoenix_v2_const.te_short * 60) <
subghz_protocol_phoenix_v2_const.te_delta * 30)) {
//Found Preambula
instance->decoder.parser_step = Phoenix_V2DecoderStepFoundStartBit;
}
break;
case Phoenix_V2DecoderStepFoundStartBit:
if(level && (DURATION_DIFF(duration, (subghz_protocol_phoenix_v2_const.te_short * 6)) <
subghz_protocol_phoenix_v2_const.te_delta * 4)) {
//Found start bit
instance->decoder.parser_step = Phoenix_V2DecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = Phoenix_V2DecoderStepReset;
}
break;
case Phoenix_V2DecoderStepSaveDuration:
if(!level) {
if(duration >= ((uint32_t)subghz_protocol_phoenix_v2_const.te_short * 10 +
subghz_protocol_phoenix_v2_const.te_delta)) {
instance->decoder.parser_step = Phoenix_V2DecoderStepFoundStartBit;
if(instance->decoder.decode_count_bit ==
subghz_protocol_phoenix_v2_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;
break;
} else {
instance->decoder.te_last = duration;
instance->decoder.parser_step = Phoenix_V2DecoderStepCheckDuration;
}
}
break;
case Phoenix_V2DecoderStepCheckDuration:
if(level) {
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_phoenix_v2_const.te_short) <
subghz_protocol_phoenix_v2_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_phoenix_v2_const.te_long) <
subghz_protocol_phoenix_v2_const.te_delta * 3)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = Phoenix_V2DecoderStepSaveDuration;
} else if(
(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_phoenix_v2_const.te_long) <
subghz_protocol_phoenix_v2_const.te_delta * 3) &&
(DURATION_DIFF(duration, subghz_protocol_phoenix_v2_const.te_short) <
subghz_protocol_phoenix_v2_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = Phoenix_V2DecoderStepSaveDuration;
} else {
instance->decoder.parser_step = Phoenix_V2DecoderStepReset;
}
} else {
instance->decoder.parser_step = Phoenix_V2DecoderStepReset;
}
break;
}
}
static uint16_t subghz_protocol_phoenix_v2_decrypt_counter(uint64_t full_key) {
uint16_t encrypted_value = (uint16_t)((full_key >> 40) & 0xFFFF);
uint8_t byte1 = (uint8_t)(encrypted_value >> 8); // First encrypted counter byte
uint8_t byte2 = (uint8_t)(encrypted_value & 0xFF); // Second encrypted counter byte
uint8_t xor_key1 = (uint8_t)(full_key >> 24); // First byte of serial
uint8_t xor_key2 = (uint8_t)((full_key >> 16) & 0xFF); // Second byte of serial
for(int i = 0; i < 16; i++) {
// Store the most significant bit (MSB) of byte1.
// The check `(msb_of_byte1 == 0)` will determine if we apply the XOR keys.
uint8_t msb_of_byte1 = byte1 & 0x80;
// Store the least significant bit (LSB) of byte2.
uint8_t lsb_of_byte2 = byte2 & 1;
// Perform a bit shuffle between the two bytes
byte2 = (byte2 >> 1) | msb_of_byte1;
byte1 = (byte1 << 1) | lsb_of_byte2;
// Conditionally apply the XOR keys based on the original MSB of byte1.
if(msb_of_byte1 == 0) {
byte1 ^= xor_key1;
// The mask `& 0x7F` clears the MSB of byte2 after the XOR.
byte2 = (byte2 ^ xor_key2) & 0x7F;
}
}
return (uint16_t)byte2 << 8 | byte1;
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance) {
// 2022.08 - @Skorpionm
// 2025.07 - @xMasterX & @RocketGod-git
// Fully supported now, with button switch and add manually
//
// Key samples
// Full key example: 0xC63E01B9615720 - after subghz_protocol_blocks_reverse_key was applied
// Serial - B9615720
// Button - 01
// Encrypted -> Decrypted counters
// C63E - 025C
// BCC1 - 025D
// 3341 - 025E
// 49BE - 025F
// 99D3 - 0260
// E32C - 0261
uint64_t data_rev =
subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit + 4);
instance->serial = data_rev & 0xFFFFFFFF;
instance->cnt = subghz_protocol_phoenix_v2_decrypt_counter(data_rev);
instance->btn = (data_rev >> 32) & 0xF;
// encrypted cnt is (data_rev >> 40) & 0xFFFF
}
uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderPhoenix_V2* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_phoenix_v2_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderPhoenix_V2* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderPhoenix_V2* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_phoenix_v2_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderPhoenix_V2* instance = context;
subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic);
furi_string_cat_printf(
output,
"V2 Phoenix %dbit\r\n"
"Key:%05lX%08lX\r\n"
"Sn:0x%07lX \r\n"
"Cnt: 0x%04lX\r\n"
"Btn: %X\r\n",
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF,
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
instance->generic.serial,
instance->generic.cnt,
instance->generic.btn);
}