From 15c557c5f44be842205d8e122dc1294616402662 Mon Sep 17 00:00:00 2001 From: trivera90 <7055868+trivera90@users.noreply.github.com> Date: Mon, 4 Dec 2023 09:37:25 -0500 Subject: [PATCH] Added genie protocol. -Garage door genie protocal added. -Credits to Derek Jamison aka codeallnight. --- lib/subghz/protocols/genie.c | 743 ++++++++++++++++++++++++++ lib/subghz/protocols/genie.h | 109 ++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 4 files changed, 854 insertions(+) create mode 100644 lib/subghz/protocols/genie.c create mode 100644 lib/subghz/protocols/genie.h diff --git a/lib/subghz/protocols/genie.c b/lib/subghz/protocols/genie.c new file mode 100644 index 0000000000..191170e4bd --- /dev/null +++ b/lib/subghz/protocols/genie.c @@ -0,0 +1,743 @@ +/** + * The majority of this code is from the keeloq.c file. The Genie garage door openers + * use a modified version of the Keeloq algorithm. The speed of the transmission is + * different (twice as fast). We don't have the manufacturer's code, so instead we + * use the button+serial number to look through a file of previously captured codes. + * + * You can use the Genie Recorder app to capture codes and save them to a .GNE file. + * It uses the GPIO pin on the Flipper Zero to click the remote automatically and + * extract the codes. A full capture is 65536 codes, so it takes about 2 days. Your + * receiver may become out-of-sync with your remote & you may need to pair it again. + * + * @CodeAllNight +*/ + +#include "genie.h" +#include "keeloq_common.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolGenie" + +// Should be major version of supported Genie Recorder (.gne) files +#define GENIE_MAJOR_VERSION 2 + +#define GENIE_APPS_DATA_FOLDER EXT_PATH("apps_data") +#define GENIE_SAVE_FOLDER \ + GENIE_APPS_DATA_FOLDER "/" \ + "genie" +#define GENIE_FILE_EXT ".gne" + +enum { + GENIE_MAGIC = 0, // 2 bytes + GENIE_VERSION = 2, // 2 bytes + GENIE_SN = 4, // 4 bytes + GENIE_LAST_SENT = 8, // 2 bytes + GENIE_REC_COUNT = 10, // 2 bytes + GENIE_RESERVED = 12, // 4 bytes + GENIE_DATA = 16, // 64K bytes +} genie_file; + +static const SubGhzBlockConst subghz_protocol_genie_const = { + .te_short = 200, + .te_long = 400, + .te_delta = 70, + .min_count_bit_for_found = 64, +}; + +struct SubGhzProtocolDecoderGenie { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderGenie { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + GenieDecoderStepReset = 0, + GenieDecoderStepCheckPreambula, + GenieDecoderStepSaveDuration, + GenieDecoderStepCheckDuration, +} GenieDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_genie_decoder = { + .alloc = subghz_protocol_decoder_genie_alloc, + .free = subghz_protocol_decoder_genie_free, + + .feed = subghz_protocol_decoder_genie_feed, + .reset = subghz_protocol_decoder_genie_reset, + + .get_hash_data = subghz_protocol_decoder_genie_get_hash_data, + .serialize = subghz_protocol_decoder_genie_serialize, + .deserialize = subghz_protocol_decoder_genie_deserialize, + .get_string = subghz_protocol_decoder_genie_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_genie_encoder = { + .alloc = subghz_protocol_encoder_genie_alloc, + .free = subghz_protocol_encoder_genie_free, + + .deserialize = subghz_protocol_encoder_genie_deserialize, + .stop = subghz_protocol_encoder_genie_stop, + .yield = subghz_protocol_encoder_genie_yield, +}; + +const SubGhzProtocol subghz_protocol_genie = { + .name = SUBGHZ_PROTOCOL_GENIE_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_genie_decoder, + .encoder = &subghz_protocol_genie_encoder, +}; + +void* subghz_protocol_encoder_genie_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderGenie* instance = malloc(sizeof(SubGhzProtocolEncoderGenie)); + + instance->base.protocol = &subghz_protocol_genie; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 20; + 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_genie_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderGenie* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Set serial number and button number from data + * @param instance Pointer to a SubGhzBlockGeneric* instance +*/ +static void subghz_protocol_genie_set_sn_and_btn(SubGhzBlockGeneric* instance) { + uint64_t key = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); + uint32_t key_fix = key >> 32; + instance->serial = key_fix & 0x0FFFFFFF; + instance->btn = key_fix >> 28; +} + +/** + * Read 16-bits from file + * @param file Pointer to a File instance + * @return 16-bit unsigned integer +*/ +static uint16_t subghz_protocol_genie_storage_file_read16(File* file) { + uint16_t read = 0; + char buffer[2] = {0}; + storage_file_read(file, buffer, 2); + read |= (buffer[0] << 8); + read |= buffer[1]; + return read; +} + +/** + * Read 32-bits from file + * @param file Pointer to a File instance + * @return 32-bit unsigned integer +*/ +static uint32_t subghz_protocol_genie_storage_file_read32(File* file) { + uint32_t read = 0; + char buffer[4] = {0}; + storage_file_read(file, buffer, 4); + read = (buffer[0] << 24); + read |= (buffer[1] << 16); + read |= (buffer[2] << 8); + read |= buffer[3]; + return read; +} + +/** + * Write 16-bits to file + * @param file Pointer to a File instance + * @param data 16-bit unsigned integer + * @return true On success +*/ +static bool subghz_protocol_genie_storage_file_write16(File* file, uint16_t data) { + char buffer[2] = {0}; + buffer[0] = (data >> 8) & 0xFF; + buffer[1] = data & 0xFF; + return storage_file_write(file, buffer, 2) == 2; +} + +/** + * Finds next code from the .gne file associated with the given code_low. + * @param code_low 32-bit unsigned integer (static part of code) + * @param code_high 32-bit unsigned integer (dynamic part of code) + * @param update_index If true, the index of the last sent code will be updated. + * @return 64-bit unsigned integer (next code) or 0xFFFFFFFFFFFFFFFF if not found +*/ +static uint64_t subghz_protocol_genie_next_code_from_file( + uint32_t code_low, + uint32_t code_high, + bool update_index) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + char buffer[256] = {0}; + snprintf(buffer, 128, "%s/%08lX%s", GENIE_SAVE_FOLDER, code_low, GENIE_FILE_EXT); + + uint64_t result = 0xFFFFFFFFFFFFFFFF; + File* file = NULL; + do { + if(!storage) { + FURI_LOG_E(TAG, "Failed to access storage"); + break; + } + + file = storage_file_alloc(storage); + if(!file) { + FURI_LOG_E(TAG, "Failed to alloc file"); + break; + } + + if(!storage_file_exists(storage, buffer)) { + FURI_LOG_E(TAG, "File %s does not exist, rerun Genie Recorder app!", buffer); + break; + } + + if(storage_file_open(file, buffer, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { + if(!storage_file_seek(file, GENIE_VERSION, true)) { + FURI_LOG_E(TAG, "Failed to seek to %d", GENIE_VERSION); + break; + } + + uint16_t version = subghz_protocol_genie_storage_file_read16(file); + if((version >> 8) > GENIE_MAJOR_VERSION) { + FURI_LOG_E(TAG, "Unsupported version: %04X", version); + break; + } + + if(!storage_file_seek(file, GENIE_SN, true)) { + FURI_LOG_E(TAG, "Failed to seek to %d", GENIE_SN); + break; + } + + uint32_t low = subghz_protocol_genie_storage_file_read32(file); + if(low != code_low) { + FURI_LOG_E(TAG, "Btn/SN mismatch. Expected: %08lX, got: %08lX", code_low, low); + break; + } + + if(!storage_file_seek(file, GENIE_LAST_SENT, true)) { + FURI_LOG_E(TAG, "Failed to seek to %d", GENIE_SN); + break; + } + + uint16_t last_sent = subghz_protocol_genie_storage_file_read16(file); + last_sent -= last_sent % (COUNT_OF(buffer) / 4); + result = code_high; + + bool found = false; + int j = 0; + for(int i = 0; i <= 65536; i++) { + if(last_sent % (COUNT_OF(buffer) / 4) == 0) { + if(!storage_file_seek(file, GENIE_DATA + (last_sent * 4), true)) { + FURI_LOG_E(TAG, "Failed to seek to %d", GENIE_DATA + (last_sent * 4)); + break; + } + + if(!storage_file_read(file, buffer, COUNT_OF(buffer))) { + FURI_LOG_E(TAG, "Failed to read %d", COUNT_OF(buffer)); + break; + } + + j = 0; + } + + uint32_t high = (buffer[j++] << 24); + high |= (buffer[j++] << 16); + high |= (buffer[j++] << 8); + high |= buffer[j++]; + + if(found && high != 0) { + result = high; + break; + } + + found |= (high == code_high); + + if(last_sent == 0xFFFF) { + last_sent = 0; + } else { + last_sent++; + } + } + + if(found && update_index) { + if(!storage_file_seek(file, GENIE_LAST_SENT, true)) { + FURI_LOG_E(TAG, "Failed to seek to %d", GENIE_SN); + break; + } + if(!subghz_protocol_genie_storage_file_write16(file, last_sent)) { + FURI_LOG_E(TAG, "Failed to write last sent. %d", last_sent); + break; + } + } else if(!found) { + FURI_LOG_E(TAG, "Code not found: %08lX", code_high); + break; + } + } else { + FURI_LOG_E(TAG, "Failed to open file"); + break; + } + } while(false); + + if(file) { + storage_file_close(file); + storage_file_free(file); + } + + furi_record_close(RECORD_STORAGE); + return result; +} + +/** + * Finds next code for this remote. + * @param instance Pointer to a SubGhzProtocolEncoderGenie* instance + * @param counter_up attempt to find next code if the value is true +*/ +static void + subghz_protocol_genie_find_next_code(SubGhzProtocolEncoderGenie* instance, bool counter_up) { + instance->generic.data_count_bit = 64; + instance->generic.cnt = 0x0000; + + if(counter_up) { + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + uint64_t next_code = + subghz_protocol_genie_next_code_from_file(code_found_lo, code_found_hi, true); + + FURI_LOG_D( + TAG, + "Low: %08lX High: %08lX New-High: %08lX", + code_found_lo, + code_found_hi, + (uint32_t)next_code); + instance->generic.cnt = next_code & + 0xffff; // We don't know counter value, just use bottom of code. + + if((next_code & 0xFFFFFFFF) == 0xFFFFFFFF) { + instance->generic.data = ((uint64_t)code_found_hi) << 32 | code_found_lo; + } else { + instance->generic.data = ((uint64_t)next_code) << 32 | code_found_lo; + } + } +} + +/** + * Key generation from simple data + * @param instance Pointer to a SubGhzProtocolEncoderGenie* instance + * @param btn Button number, 4 bit + * @param counter_up increasing the counter if the value is true + */ +static bool subghz_protocol_genie_gen_data( + SubGhzProtocolEncoderGenie* instance, + uint8_t btn, + bool counter_up) { + uint32_t fix = (uint32_t)btn << 28 | instance->generic.serial; + uint32_t hop = 0; + uint64_t code_found_reverse; + + subghz_protocol_genie_find_next_code(instance, counter_up); + + code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + hop = code_found_reverse & 0x00000000ffffffff; + + if(hop) { + uint64_t yek = (uint64_t)fix << 32 | hop; + instance->generic.data = + subghz_protocol_blocks_reverse_key(yek, instance->generic.data_count_bit); + } + + return true; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderGenie instance + * @return true On success + */ +static bool + subghz_protocol_encoder_genie_get_upload(SubGhzProtocolEncoderGenie* instance, uint8_t btn) { + furi_assert(instance); + + // Generate next key + if(!subghz_protocol_genie_gen_data(instance, btn, true)) { + return false; + } + + size_t index = 0; + size_t size_upload = 11 * 2 + 2 + (instance->generic.data_count_bit * 2) + 4; + 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 + for(uint8_t i = 11; i > 0; i--) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_genie_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_short); + } + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_genie_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_short * 10); + + //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_genie_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_genie_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_short); + } + } + // +send 2 status bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_genie_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_long); + // send end + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_genie_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_short * 40); + + return true; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_genie_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderGenie* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_genie_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_genie_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + subghz_protocol_genie_set_sn_and_btn(&instance->generic); + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + // Get_upload will generate the next key. + if(!subghz_protocol_encoder_genie_get_upload(instance, instance->generic.btn)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; + 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"); + ret = SubGhzProtocolStatusErrorParserKey; + break; + } + + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_genie_stop(void* context) { + SubGhzProtocolEncoderGenie* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_genie_yield(void* context) { + SubGhzProtocolEncoderGenie* 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_genie_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderGenie* instance = malloc(sizeof(SubGhzProtocolDecoderGenie)); + instance->base.protocol = &subghz_protocol_genie; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_genie_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGenie* instance = context; + + free(instance); +} + +void subghz_protocol_decoder_genie_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGenie* instance = context; + instance->decoder.parser_step = GenieDecoderStepReset; +} + +void subghz_protocol_decoder_genie_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderGenie* instance = context; + + switch(instance->decoder.parser_step) { + case GenieDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_genie_const.te_short) < + subghz_protocol_genie_const.te_delta) { + instance->decoder.parser_step = GenieDecoderStepCheckPreambula; + instance->header_count++; + } + break; + case GenieDecoderStepCheckPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_genie_const.te_short) < + subghz_protocol_genie_const.te_delta)) { + instance->decoder.parser_step = GenieDecoderStepReset; + break; + } + if((instance->header_count > 2) && + (DURATION_DIFF(duration, subghz_protocol_genie_const.te_short * 10) < + subghz_protocol_genie_const.te_delta * 10)) { + // Found header + instance->decoder.parser_step = GenieDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = GenieDecoderStepReset; + instance->header_count = 0; + } + break; + case GenieDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = GenieDecoderStepCheckDuration; + } + break; + case GenieDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_genie_const.te_short * 2 + + subghz_protocol_genie_const.te_delta)) { + // Found end TX + instance->decoder.parser_step = GenieDecoderStepReset; + if((instance->decoder.decode_count_bit >= + subghz_protocol_genie_const.min_count_bit_for_found) && + (instance->decoder.decode_count_bit <= + subghz_protocol_genie_const.min_count_bit_for_found + 2)) { + if(instance->generic.data != instance->decoder.decode_data) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = + subghz_protocol_genie_const.min_count_bit_for_found; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + } + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_genie_const.te_short) < + subghz_protocol_genie_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_genie_const.te_long) < + subghz_protocol_genie_const.te_delta * 2)) { + if(instance->decoder.decode_count_bit < + subghz_protocol_genie_const.min_count_bit_for_found) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.decode_count_bit++; + } + instance->decoder.parser_step = GenieDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_genie_const.te_long) < + subghz_protocol_genie_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_genie_const.te_short) < + subghz_protocol_genie_const.te_delta)) { + if(instance->decoder.decode_count_bit < + subghz_protocol_genie_const.min_count_bit_for_found) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else { + instance->decoder.decode_count_bit++; + } + instance->decoder.parser_step = GenieDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GenieDecoderStepReset; + instance->header_count = 0; + } + } else { + instance->decoder.parser_step = GenieDecoderStepReset; + instance->header_count = 0; + } + break; + } +} + +uint8_t subghz_protocol_decoder_genie_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGenie* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_genie_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderGenie* instance = context; + + SubGhzProtocolStatus res = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + subghz_protocol_genie_set_sn_and_btn(&instance->generic); + + return res; +} + +SubGhzProtocolStatus + subghz_protocol_decoder_genie_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderGenie* instance = context; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + do { + if(SubGhzProtocolStatusOk != + subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_genie_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + res = SubGhzProtocolStatusOk; + } while(false); + + return res; +} + +void subghz_protocol_decoder_genie_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderGenie* instance = context; + + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + // If we are in the genie thread (running the Genie Recorder app), + // output the protocol & key, then exit without looking for the next code. + const char* name = furi_thread_get_name(furi_thread_get_current_id()); + if(name != NULL && strcmp(name, "genie-rx") == 0) { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo); + return; + } + + uint64_t next_code = + subghz_protocol_genie_next_code_from_file(code_found_lo, code_found_hi, false); + subghz_protocol_genie_set_sn_and_btn(&instance->generic); + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo); + + if((next_code & 0xFFFFFFFF) == 0xFFFFFFFF) { + furi_string_cat_printf( + output, + "No keys found. Please run the\r\n" + "Genie Recorder app to\r\n" + "extract keys from the\r\n" + "remote."); + instance->generic.cnt = 0; + instance->generic.data = ((uint64_t)code_found_hi) << 32 | code_found_lo; + } else if((next_code & 0xFFFFFFFF) == code_found_hi) { + furi_string_cat_printf( + output, + "Key missing. Please run the\r\n" + "Genie Recorder app to\r\n" + "extract additional keys from\r\n" + "the remote."); + instance->generic.cnt = 0; + instance->generic.data = ((uint64_t)code_found_hi) << 32 | code_found_lo; + } else { + furi_string_cat_printf(output, "Next code: %08lX", (uint32_t)next_code); + } +} diff --git a/lib/subghz/protocols/genie.h b/lib/subghz/protocols/genie.h new file mode 100644 index 0000000000..bcec3da4a9 --- /dev/null +++ b/lib/subghz/protocols/genie.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_GENIE_NAME "Genie" + +typedef struct SubGhzProtocolDecoderGenie SubGhzProtocolDecoderGenie; +typedef struct SubGhzProtocolEncoderGenie SubGhzProtocolEncoderGenie; + +extern const SubGhzProtocolDecoder subghz_protocol_genie_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_genie_encoder; +extern const SubGhzProtocol subghz_protocol_genie; + +/** + * Allocate SubGhzProtocolEncoderGenie. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderGenie* pointer to a SubGhzProtocolEncoderGenie instance + */ +void* subghz_protocol_encoder_genie_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderGenie. + * @param context Pointer to a SubGhzProtocolEncoderGenie instance + */ +void subghz_protocol_encoder_genie_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderGenie instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_genie_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderGenie instance + */ +void subghz_protocol_encoder_genie_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderGenie instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_genie_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderGenie. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderGenie* pointer to a SubGhzProtocolDecoderGenie instance + */ +void* subghz_protocol_decoder_genie_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderGenie. + * @param context Pointer to a SubGhzProtocolDecoderGenie instance + */ +void subghz_protocol_decoder_genie_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderGenie. + * @param context Pointer to a SubGhzProtocolDecoderGenie instance + */ +void subghz_protocol_decoder_genie_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderGenie instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_genie_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderGenie instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_genie_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderGenie. + * @param context Pointer to a SubGhzProtocolDecoderGenie instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return SubGhzProtocolStatus + */ +SubGhzProtocolStatus subghz_protocol_decoder_genie_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderGenie. + * @param context Pointer to a SubGhzProtocolDecoderGenie instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return SubGhzProtocolStatus + */ +SubGhzProtocolStatus + subghz_protocol_decoder_genie_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderGenie instance + * @param output Resulting text + */ +void subghz_protocol_decoder_genie_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index a148dcb016..d60f54d4fe 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -3,6 +3,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_gate_tx, &subghz_protocol_keeloq, + &subghz_protocol_genie, &subghz_protocol_star_line, &subghz_protocol_nice_flo, &subghz_protocol_came, diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 00bb156905..c763a51dd1 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -4,6 +4,7 @@ #include "princeton.h" #include "keeloq.h" +#include "genie.h" #include "star_line.h" #include "nice_flo.h" #include "came.h"