From 3c3d06bae093925a6158d1ce437f33ce06b97e4e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:02:05 +0300 Subject: [PATCH] Roger that --- .../main/subghz/helpers/subghz_custom_event.h | 3 +- .../subghz/scenes/subghz_scene_set_type.c | 11 + lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + lib/subghz/protocols/roger.c | 442 ++++++++++++++++++ lib/subghz/protocols/roger.h | 109 +++++ 6 files changed, 566 insertions(+), 1 deletion(-) create mode 100644 lib/subghz/protocols/roger.c create mode 100644 lib/subghz/protocols/roger.h diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index cffad2929..9fc607889 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -75,7 +75,6 @@ typedef enum { SetTypeANMotorsAT4, SetTypeAlutechAT4N, SetTypePhoenix_V2_433, - SetTypeMotorline433, SetTypeHCS101_433_92, SetTypeDoorHan_315_00, SetTypeDoorHan_433_92, @@ -88,6 +87,7 @@ typedef enum { SetTypeCenturion433, SetTypeMonarch433, SetTypeJollyMotors433, + SetTypeMotorline433, SetTypeSommer_FM_434, SetTypeSommer_FM_868, SetTypeSommer_FM238_434, @@ -130,6 +130,7 @@ typedef enum { SetTypeMarantec24_868, SetTypeMarantec_433, SetTypeMarantec_868, + SetTypeRoger_433, SetTypeLinear_300_00, // SetTypeNeroSketch, //Deleted in OFW // SetTypeNeroRadio, //Deleted in OFW diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index b6ff75768..b986c2c5d 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -20,6 +20,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeSomfyTelis] = "Somfy Telis 433MHz", [SetTypeANMotorsAT4] = "AN-Motors AT4 433MHz", [SetTypeAlutechAT4N] = "Alutech AT4N 433MHz", + [SetTypeRoger_433] = "Roger 433MHz", [SetTypePhoenix_V2_433] = "V2 Phoenix 433MHz", [SetTypeHCS101_433_92] = "KL: HCS101 433MHz", [SetTypeDoorHan_315_00] = "KL: DoorHan 315MHz", @@ -286,6 +287,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .data.bits = 24, .data.te = 0}; break; + case SetTypeRoger_433: + gen_info = (GenInfo){ + .type = GenData, + .mod = "AM650", + .freq = 433920000, + .data.name = SUBGHZ_PROTOCOL_ROGER_NAME, + .data.key = (key & 0xFFFF000) | 0x0000101, // button code 0x1 and (crc?) is 0x01 + .data.bits = 28, + .data.te = 0}; + break; case SetTypeLinear_300_00: gen_info = (GenInfo){ .type = GenData, diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index c73923c7a..465585d77 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -53,6 +53,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_hay21, &subghz_protocol_revers_rb2, &subghz_protocol_feron, + &subghz_protocol_roger, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 6165d748a..4f63b030e 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -54,3 +54,4 @@ #include "hay21.h" #include "revers_rb2.h" #include "feron.h" +#include "roger.h" diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c new file mode 100644 index 000000000..d14547876 --- /dev/null +++ b/lib/subghz/protocols/roger.c @@ -0,0 +1,442 @@ +#include "roger.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 "SubGhzProtocolRoger" + +static const SubGhzBlockConst subghz_protocol_roger_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 270, + .min_count_bit_for_found = 28, +}; + +struct SubGhzProtocolDecoderRoger { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderRoger { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + RogerDecoderStepReset = 0, + RogerDecoderStepSaveDuration, + RogerDecoderStepCheckDuration, +} RogerDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_roger_decoder = { + .alloc = subghz_protocol_decoder_roger_alloc, + .free = subghz_protocol_decoder_roger_free, + + .feed = subghz_protocol_decoder_roger_feed, + .reset = subghz_protocol_decoder_roger_reset, + + .get_hash_data = subghz_protocol_decoder_roger_get_hash_data, + .serialize = subghz_protocol_decoder_roger_serialize, + .deserialize = subghz_protocol_decoder_roger_deserialize, + .get_string = subghz_protocol_decoder_roger_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_roger_encoder = { + .alloc = subghz_protocol_encoder_roger_alloc, + .free = subghz_protocol_encoder_roger_free, + + .deserialize = subghz_protocol_encoder_roger_deserialize, + .stop = subghz_protocol_encoder_roger_stop, + .yield = subghz_protocol_encoder_roger_yield, +}; + +const SubGhzProtocol subghz_protocol_roger = { + .name = SUBGHZ_PROTOCOL_ROGER_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_roger_decoder, + .encoder = &subghz_protocol_roger_encoder, +}; + +void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderRoger* instance = malloc(sizeof(SubGhzProtocolEncoderRoger)); + + instance->base.protocol = &subghz_protocol_roger; + 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_roger_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderRoger* instance = context; + free(instance->encoder.upload); + free(instance); +} + +// Get custom button code +static uint8_t subghz_protocol_roger_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 0x1: + btn = 0x2; + break; + case 0x2: + btn = 0x1; + break; + case 0x4: + btn = 0x1; + break; + case 0x8: + btn = 0x1; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x1: + btn = 0x4; + break; + case 0x2: + btn = 0x4; + break; + case 0x4: + btn = 0x2; + break; + case 0x8: + btn = 0x4; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x1: + btn = 0x8; + break; + case 0x2: + btn = 0x8; + break; + case 0x4: + btn = 0x8; + break; + case 0x8: + btn = 0x2; + break; + + default: + break; + } + } + + return btn; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderRoger instance + */ +static void subghz_protocol_encoder_roger_get_upload(SubGhzProtocolEncoderRoger* instance) { + furi_assert(instance); + size_t index = 0; + + uint8_t btn = instance->generic.btn; + + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(btn); + } + + // Get custom button code + // This will override the btn variable if a custom button is set + btn = subghz_protocol_roger_get_btn_code(); + + // If CRC is not == button - transmit as is, no custom button allowed + if((instance->generic.data & 0xFF) == instance->generic.btn) { + instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) | + btn; + } + + // Send key and GAP + 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_roger_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_roger_const.te_short * 19); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_roger_const.te_short * 19); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_long); + } + } + } + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_roger_check_remote_controller(SubGhzBlockGeneric* instance) { + // Roger Decoder + // 2025.07 - @xMasterX (MMX) + + // Key samples + // 0010001111111001 0001 00100000 // S/N: 0x23F9 Btn: 0x1 CRC: 0x20 + // 0010001111111001 0010 00100011 // S/N: 0x23F9 Btn: 0x2 CRC: 0x23 + // 0101011001010110 0001 00000001 // S/N: 0x5656 Btn: 0x1 CRC: 0x01 + // 0101011001010110 0010 00000010 // S/N: 0x5656 Btn: 0x2 CRC: 0x02 + // 0000110111111110 0001 00000001 // S/N: 0x0DFE Btn: 0x1 CRC: 0x01 + // 0000110111111110 0100 00000100 // S/N: 0x0DFE Btn: 0x4 CRC: 0x04 + // 0000110111111110 0010 00000010 // S/N: 0x0DFE Btn: 0x2 CRC: 0x02 + // 0000110111111110 1000 00001000 // S/N: 0x0DFE Btn: 0x8 CRC: 0x08 + + instance->serial = instance->data >> 12; + instance->btn = (instance->data >> 8) & 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(3); +} + +SubGhzProtocolStatus + subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderRoger* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_roger_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_roger_check_remote_controller(&instance->generic); + subghz_protocol_encoder_roger_get_upload(instance); + + 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_roger_stop(void* context) { + SubGhzProtocolEncoderRoger* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_roger_yield(void* context) { + SubGhzProtocolEncoderRoger* 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_roger_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderRoger* instance = malloc(sizeof(SubGhzProtocolDecoderRoger)); + instance->base.protocol = &subghz_protocol_roger; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_roger_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + free(instance); +} + +void subghz_protocol_decoder_roger_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + instance->decoder.parser_step = RogerDecoderStepReset; +} + +void subghz_protocol_decoder_roger_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + + switch(instance->decoder.parser_step) { + case RogerDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < + subghz_protocol_roger_const.te_delta * 3)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + } + break; + case RogerDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = RogerDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + break; + case RogerDecoderStepCheckDuration: + if(!level) { + // Bit 1 is long and short timing = 1000us HIGH (te_last) and 500us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + // Bit 0 is short and long timing = 500us HIGH (te_last) and 1000us LOW + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < + subghz_protocol_roger_const.te_delta * 3) { + //Found next GAP and add bit 1 or 0 + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + // If got full 28 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_roger_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 = RogerDecoderStepReset; + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_roger_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + + subghz_protocol_roger_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%07lX\r\n" + "Serial: 0x%04lX\r\n" + "CRC: 0x%02lX\r\n" + "Btn: %01X", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFFF), + instance->generic.serial, + (uint32_t)(instance->generic.data & 0xFF), + instance->generic.btn); +} diff --git a/lib/subghz/protocols/roger.h b/lib/subghz/protocols/roger.h new file mode 100644 index 000000000..c279164f9 --- /dev/null +++ b/lib/subghz/protocols/roger.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_ROGER_NAME "Roger" + +typedef struct SubGhzProtocolDecoderRoger SubGhzProtocolDecoderRoger; +typedef struct SubGhzProtocolEncoderRoger SubGhzProtocolEncoderRoger; + +extern const SubGhzProtocolDecoder subghz_protocol_roger_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_roger_encoder; +extern const SubGhzProtocol subghz_protocol_roger; + +/** + * Allocate SubGhzProtocolEncoderRoger. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderRoger* pointer to a SubGhzProtocolEncoderRoger instance + */ +void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderRoger. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + */ +void subghz_protocol_encoder_roger_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + */ +void subghz_protocol_encoder_roger_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_roger_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderRoger. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderRoger* pointer to a SubGhzProtocolDecoderRoger instance + */ +void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + */ +void subghz_protocol_decoder_roger_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + */ +void subghz_protocol_decoder_roger_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_roger_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param output Resulting text + */ +void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output);