#include "subghz_remote_app_i.h" #include <lib/toolbox/path.h> #include <flipper_format/flipper_format_i.h> #include <lib/subghz/protocols/protocol_items.h> // #include <lib/subghz/protocols/keeloq.h> // #include <lib/subghz/protocols/star_line.h> #include <lib/subghz/blocks/custom_btn.h> #define TAG "SubGhzRemote" static const char* map_file_labels[SubRemSubKeyNameMaxCount][2] = { [SubRemSubKeyNameUp] = {"UP", "ULABEL"}, [SubRemSubKeyNameDown] = {"DOWN", "DLABEL"}, [SubRemSubKeyNameLeft] = {"LEFT", "LLABEL"}, [SubRemSubKeyNameRight] = {"RIGHT", "RLABEL"}, [SubRemSubKeyNameOk] = {"OK", "OKLABEL"}, }; static bool subrem_set_preset_data(SubGhzSetting* setting, FreqPreset* freq_preset, const char* preset) { const char* preset_name = ""; if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { preset_name = "AM270"; } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { preset_name = "AM650"; } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { preset_name = "FM238"; } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { preset_name = "FM476"; } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { // preset_name = "CUSTOM"; return false; } else { FURI_LOG_E(TAG, "Unknown preset"); return false; } size_t preset_index = subghz_setting_get_inx_preset_by_name(setting, preset_name); freq_preset->data = subghz_setting_get_preset_data(setting, preset_index); return true; } SubRemSubFilePreset* subrem_sub_file_preset_alloc() { SubRemSubFilePreset* sub_preset = malloc(sizeof(SubRemSubFilePreset)); sub_preset->fff_data = flipper_format_string_alloc(); sub_preset->file_path = furi_string_alloc(); sub_preset->protocaol_name = furi_string_alloc(); sub_preset->label = furi_string_alloc_set_str("N/A"); sub_preset->type = SubGhzProtocolTypeUnknown; return sub_preset; } void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset) { furi_assert(sub_preset); furi_string_free(sub_preset->label); furi_string_free(sub_preset->protocaol_name); furi_string_free(sub_preset->file_path); flipper_format_free(sub_preset->fff_data); free(sub_preset); } static void subrem_sub_file_preset_reset(SubRemSubFilePreset* sub_preset) { furi_assert(sub_preset); furi_string_set_str(sub_preset->label, "N/A"); furi_string_reset(sub_preset->protocaol_name); furi_string_reset(sub_preset->file_path); Stream* fff_data_stream = flipper_format_get_raw_stream(sub_preset->fff_data); stream_clean(fff_data_stream); sub_preset->type = SubGhzProtocolTypeUnknown; } void subrem_map_preset_reset(SubGhzRemoteApp* app) { furi_assert(app); for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { subrem_sub_file_preset_reset(app->subs_preset[i]); } } static bool subrem_map_preset_load(SubGhzRemoteApp* app, FlipperFormat* fff_data_file) { furi_assert(app); bool ret = false; SubRemSubFilePreset* sub_preset; for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { sub_preset = app->subs_preset[i]; if(!flipper_format_read_string( fff_data_file, map_file_labels[i][0], sub_preset->file_path)) { #if FURI_DEBUG FURI_LOG_W(TAG, "No file patch for %s", map_file_labels[i][0]); #endif sub_preset->type = SubGhzProtocolTypeUnknown; } else if(!flipper_format_rewind(fff_data_file)) { // Rewind error } else if(!flipper_format_read_string( fff_data_file, map_file_labels[i][1], sub_preset->label)) { #if FURI_DEBUG FURI_LOG_W(TAG, "No Label for %s", map_file_labels[i][0]); #endif path_extract_filename(sub_preset->file_path, sub_preset->label, true); } else { FURI_LOG_I( TAG, "%-5s: %s %s", map_file_labels[i][0], furi_string_get_cstr(sub_preset->label), furi_string_get_cstr(sub_preset->file_path)); ret = true; } flipper_format_rewind(fff_data_file); } return ret; } bool subrem_save_protocol_to_file(FlipperFormat* flipper_format, const char* dev_file_name) { furi_assert(flipper_format); furi_assert(dev_file_name); Storage* storage = furi_record_open(RECORD_STORAGE); Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format); bool saved = false; FuriString* file_dir = furi_string_alloc(); path_extract_dirname(dev_file_name, file_dir); do { //removing additional fields flipper_format_delete_key(flipper_format, "Repeat"); //flipper_format_delete_key(flipper_format, "Manufacture"); if(!storage_simply_remove(storage, dev_file_name)) { break; } //ToDo check Write stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS); saved = true; } while(0); furi_string_free(file_dir); furi_record_close(RECORD_STORAGE); return saved; } bool subrem_tx_start_sub( SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset, SubGhzProtocolEncoderRAWCallbackEnd callback) { furi_assert(app); furi_assert(sub_preset); bool ret = false; subrem_tx_stop_sub(app, true); if(sub_preset->type == SubGhzProtocolTypeUnknown) { return false; } FURI_LOG_I(TAG, "Send %s", furi_string_get_cstr(sub_preset->label)); subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK); keeloq_reset_original_btn(); subghz_custom_btns_reset(); do { flipper_format_rewind(sub_preset->fff_data); // app->transmitter = subghz_transmitter_alloc_init( app->environment, furi_string_get_cstr(sub_preset->protocaol_name)); if(app->transmitter) { if(subghz_transmitter_deserialize(app->transmitter, sub_preset->fff_data) != SubGhzProtocolStatusOk) { FURI_LOG_E(TAG, "Deserialize error!"); break; } furi_hal_subghz_reset(); furi_hal_subghz_idle(); furi_hal_subghz_load_custom_preset(sub_preset->freq_preset.data); furi_hal_gpio_init( furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_subghz_idle(); furi_hal_subghz_set_frequency_and_path(sub_preset->freq_preset.frequency); furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); furi_hal_gpio_init( furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); if(!furi_hal_subghz_tx()) { FURI_LOG_E(TAG, "Sending not allowed"); break; } if(sub_preset->type == SubGhzProtocolTypeRAW) { subghz_protocol_raw_file_encoder_worker_set_callback_end( (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance( app->transmitter), callback, app); } furi_hal_subghz_start_async_tx(subghz_transmitter_yield, app->transmitter); ret = true; } } while(false); app->tx_running = ret; return ret; } static void subghz_tx_stop(SubGhzRemoteApp* app) { furi_assert(app); //Stop TX furi_hal_subghz_stop_async_tx(); subghz_transmitter_stop(app->transmitter); subghz_transmitter_free(app->transmitter); furi_hal_subghz_idle(); } bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced) { furi_assert(app); SubRemSubFilePreset* sub_preset = app->subs_preset[app->chusen_sub]; if(forced || (sub_preset->type != SubGhzProtocolTypeRAW)) { // SubRemSubKeyTypeRawKey)) { if(app->tx_running) { subghz_tx_stop(app); if(sub_preset->type == SubGhzProtocolTypeDynamic) { subrem_save_protocol_to_file( sub_preset->fff_data, furi_string_get_cstr(sub_preset->file_path)); keeloq_reset_mfname(); keeloq_reset_kl_type(); keeloq_reset_original_btn(); subghz_custom_btns_reset(); star_line_reset_mfname(); star_line_reset_kl_type(); } app->tx_running = false; return true; } } return false; } static bool subrem_map_preset_check(SubGhzRemoteApp* app, FlipperFormat* fff_data_file) { furi_assert(app); FuriString* temp_str = furi_string_alloc(); uint32_t temp_data32; bool ret = false; bool sub_preset_loaded = false; SubRemSubFilePreset* sub_preset; for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { sub_preset = app->subs_preset[i]; sub_preset_loaded = false; if(furi_string_empty(sub_preset->file_path)) { // FURI_LOG_I(TAG, "Empty file path"); continue; } do { if(!flipper_format_file_open_existing( fff_data_file, furi_string_get_cstr(sub_preset->file_path))) { FURI_LOG_W(TAG, "Error open file %s", furi_string_get_cstr(sub_preset->file_path)); break; } if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { FURI_LOG_E(TAG, "Missing or incorrect header"); break; } if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && temp_data32 == SUBGHZ_KEY_FILE_VERSION) { } else { FURI_LOG_E(TAG, "Type or version mismatch"); break; } //Load frequency if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) { FURI_LOG_W(TAG, "Cannot read frequency. Set default frequency"); sub_preset->freq_preset.frequency = subghz_setting_get_default_frequency(app->setting); } else if(!furi_hal_subghz_is_tx_allowed(temp_data32)) { FURI_LOG_E(TAG, "This frequency can only be used for RX"); break; } else { sub_preset->freq_preset.frequency = temp_data32; } //Load preset if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { FURI_LOG_E(TAG, "Missing Preset"); break; } if(!subrem_set_preset_data( app->setting, &sub_preset->freq_preset, furi_string_get_cstr(temp_str))) { FURI_LOG_E(TAG, "Cannot load preset."); break; } //Load protocol if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) { FURI_LOG_E(TAG, "Missing Protocol"); break; } FlipperFormat* fff_data = sub_preset->fff_data; if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { //if RAW subghz_protocol_raw_gen_fff_data( fff_data, furi_string_get_cstr(sub_preset->file_path)); } else { stream_copy_full( flipper_format_get_raw_stream(fff_data_file), flipper_format_get_raw_stream(fff_data)); } const SubGhzProtocolRegistry* protocol_registry_items = subghz_environment_get_protocol_registry(app->environment); const SubGhzProtocol* protocol = subghz_protocol_registry_get_by_name( protocol_registry_items, furi_string_get_cstr(temp_str)); if(!protocol) { FURI_LOG_E(TAG, "Protocol not found"); break; } else if(protocol->flag & SubGhzProtocolFlag_Send) { if((protocol->type == SubGhzProtocolTypeStatic) || (protocol->type == SubGhzProtocolTypeDynamic) || // (protocol->type == SubGhzProtocolTypeBinRAW) || // TODO: BINRAW (protocol->type == SubGhzProtocolTypeRAW)) { sub_preset->type = protocol->type; } else { FURI_LOG_E(TAG, "Unsuported Protocol"); break; } furi_string_set(sub_preset->protocaol_name, temp_str); } else { FURI_LOG_E(TAG, "Protocol does not support transmission"); } sub_preset_loaded = true; ret |= true; #if FURI_DEBUG FURI_LOG_I(TAG, "%-16s - protocol Loaded", furi_string_get_cstr(sub_preset->label)); #endif } while(false); // TODO: // Load file state logic // Label depending on the state if(!sub_preset_loaded) { furi_string_set_str(sub_preset->label, "N/A"); } flipper_format_file_close(fff_data_file); } furi_string_free(temp_str); return ret; } bool subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path) { furi_assert(app); furi_assert(file_path); #if FURI_DEBUG FURI_LOG_I(TAG, "Load Map File Start"); #endif Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); bool ret = false; #if FURI_DEBUG FURI_LOG_I(TAG, "Open Map File.."); #endif subrem_map_preset_reset(app); if(!flipper_format_file_open_existing(fff_data_file, file_path)) { FURI_LOG_E(TAG, "Could not open MAP file %s", file_path); } else { if(!subrem_map_preset_load(app, fff_data_file)) { FURI_LOG_E(TAG, "Could no Sub file path in MAP file"); // ret = // error for popup } else if( (flipper_format_file_close(fff_data_file)) && (subrem_map_preset_check(app, fff_data_file))) { FURI_LOG_I(TAG, "Load Map File Seccesful"); ret = true; } } // TODO: Popup for error or return error type if(!ret) { FURI_LOG_E(TAG, "Broken Map File"); } flipper_format_file_close(fff_data_file); flipper_format_free(fff_data_file); furi_record_close(RECORD_STORAGE); return ret; } SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { furi_assert(app); FuriString* file_path = furi_string_alloc(); SubRemLoadMapState ret = SubRemLoadMapStateBack; DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, SUBREM_APP_EXTENSION, &I_sub1_10px); browser_options.base_path = SUBREM_APP_FOLDER; // Input events and views are managed by file_select if(!dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) { } else if(subrem_map_file_load(app, furi_string_get_cstr(app->file_path))) { ret = SubRemLoadMapStateOK; } else { ret = SubRemLoadMapStateError; } furi_string_free(file_path); return ret; }