From c1eb968144f41319efb3e7b8cc4eea66327624fd Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Fri, 26 May 2023 17:39:43 +0300 Subject: [PATCH] Merge branch 'subrem' into subrem_configurator --- .../subghz_remote_new/application.fam | 11 +- .../helpers/subrem_custom_event.h | 39 +- .../helpers/subrem_presets.c | 180 ++++++ .../helpers/subrem_presets.h | 39 ++ .../subghz_remote_new/helpers/subrem_types.h | 17 +- .../subghz_remote_new/helpers/txrx/Readme.md | 4 + .../helpers/txrx/subghz_txrx.c | 563 ++++++++++++++++++ .../helpers/txrx/subghz_txrx.h | 318 ++++++++++ .../helpers/txrx/subghz_txrx_i.h | 29 + .../icons/DolphinNice_96x59.png | Bin 0 -> 2459 bytes .../{ => remote_scene}/ButtonDown_7x4.png | Bin .../{ => remote_scene}/ButtonLeft_4x7.png | Bin .../{ => remote_scene}/ButtonRight_4x7.png | Bin .../icons/{ => remote_scene}/ButtonUp_7x4.png | Bin .../icons/{ => remote_scene}/Ok_btn_9x9.png | Bin .../{ => remote_scene}/Pin_arrow_up_7x9.png | Bin .../{ => remote_scene}/Pin_cell_13x13.png | Bin .../icons/{ => remote_scene}/Pin_star_7x7.png | Bin .../icons/{ => remote_scene}/back_10px.png | Bin .../subghz_remote_new/icons/subrem_10px.png | Bin 0 -> 5000 bytes .../scenes/subrem_scene_config.h | 8 +- .../scenes/subrem_scene_edit_label.c | 133 +++++ .../scenes/subrem_scene_edit_menu.c | 123 ++++ .../scenes/subrem_scene_edit_preview.c | 74 +++ .../scenes/subrem_scene_edit_submenu.c | 54 ++ .../scenes/subrem_scene_enter_new_name.c | 71 +++ .../scenes/subrem_scene_open_map_file.c | 29 + .../scenes/subrem_scene_open_sub_file.c | 116 ++++ .../scenes/subrem_scene_openmapfile.c | 40 -- .../scenes/subrem_scene_start.c | 33 +- .../subghz_remote_new/subghz_remote_app.c | 87 ++- .../subghz_remote_new/subghz_remote_app_i.c | 431 +++++--------- .../subghz_remote_new/subghz_remote_app_i.h | 59 +- .../subghz_remote_new/views/edit_menu.c | 290 +++++++++ .../subghz_remote_new/views/edit_menu.h | 29 + .../external/subghz_remote_new/views/remote.c | 184 +++--- .../external/subghz_remote_new/views/remote.h | 14 +- 37 files changed, 2474 insertions(+), 501 deletions(-) create mode 100644 applications/external/subghz_remote_new/helpers/subrem_presets.c create mode 100644 applications/external/subghz_remote_new/helpers/subrem_presets.h create mode 100644 applications/external/subghz_remote_new/helpers/txrx/Readme.md create mode 100644 applications/external/subghz_remote_new/helpers/txrx/subghz_txrx.c create mode 100644 applications/external/subghz_remote_new/helpers/txrx/subghz_txrx.h create mode 100644 applications/external/subghz_remote_new/helpers/txrx/subghz_txrx_i.h create mode 100644 applications/external/subghz_remote_new/icons/DolphinNice_96x59.png rename applications/external/subghz_remote_new/icons/{ => remote_scene}/ButtonDown_7x4.png (100%) rename applications/external/subghz_remote_new/icons/{ => remote_scene}/ButtonLeft_4x7.png (100%) rename applications/external/subghz_remote_new/icons/{ => remote_scene}/ButtonRight_4x7.png (100%) rename applications/external/subghz_remote_new/icons/{ => remote_scene}/ButtonUp_7x4.png (100%) rename applications/external/subghz_remote_new/icons/{ => remote_scene}/Ok_btn_9x9.png (100%) rename applications/external/subghz_remote_new/icons/{ => remote_scene}/Pin_arrow_up_7x9.png (100%) rename applications/external/subghz_remote_new/icons/{ => remote_scene}/Pin_cell_13x13.png (100%) rename applications/external/subghz_remote_new/icons/{ => remote_scene}/Pin_star_7x7.png (100%) rename applications/external/subghz_remote_new/icons/{ => remote_scene}/back_10px.png (100%) create mode 100644 applications/external/subghz_remote_new/icons/subrem_10px.png create mode 100644 applications/external/subghz_remote_new/scenes/subrem_scene_edit_label.c create mode 100644 applications/external/subghz_remote_new/scenes/subrem_scene_edit_menu.c create mode 100644 applications/external/subghz_remote_new/scenes/subrem_scene_edit_preview.c create mode 100644 applications/external/subghz_remote_new/scenes/subrem_scene_edit_submenu.c create mode 100644 applications/external/subghz_remote_new/scenes/subrem_scene_enter_new_name.c create mode 100644 applications/external/subghz_remote_new/scenes/subrem_scene_open_map_file.c create mode 100644 applications/external/subghz_remote_new/scenes/subrem_scene_open_sub_file.c delete mode 100644 applications/external/subghz_remote_new/scenes/subrem_scene_openmapfile.c create mode 100644 applications/external/subghz_remote_new/views/edit_menu.c create mode 100644 applications/external/subghz_remote_new/views/edit_menu.h diff --git a/applications/external/subghz_remote_new/application.fam b/applications/external/subghz_remote_new/application.fam index f2cdd25b0..a232fb30f 100644 --- a/applications/external/subghz_remote_new/application.fam +++ b/applications/external/subghz_remote_new/application.fam @@ -3,17 +3,14 @@ App( name="Sub-GHz Remote", apptype=FlipperAppType.EXTERNAL, entry_point="subghz_remote_app", - cdefines=[ - "APP_SUBGHZREMOTE", - "SUBREM_LIGHT", - ], requires=[ "gui", "dialogs", ], - icon="A_SubGHzRemote_14", - stack_size=4 * 1024, + stack_size=3 * 1024, order=50, - fap_category="Debug", + fap_description="Remote control for transmission multiple *.sub files", + fap_category="Debug", #"Sub-Ghz" fap_icon_assets="icons", + fap_icon="icons/subrem_10px.png", ) \ No newline at end of file diff --git a/applications/external/subghz_remote_new/helpers/subrem_custom_event.h b/applications/external/subghz_remote_new/helpers/subrem_custom_event.h index b6f752ba0..779458c20 100644 --- a/applications/external/subghz_remote_new/helpers/subrem_custom_event.h +++ b/applications/external/subghz_remote_new/helpers/subrem_custom_event.h @@ -1,11 +1,25 @@ #pragma once typedef enum { - //SubmenuIndex + SubRemEditMenuStateUP = 0, + SubRemEditMenuStateDOWN, + SubRemEditMenuStateLEFT, + SubRemEditMenuStateRIGHT, + SubRemEditMenuStateOK, +} SubRemEditMenuState; + +typedef enum { + // SubmenuIndex + SubmenuIndexSubRemEditMapFile = 0, + SubmenuIndexSubRemNewMapFile, SubmenuIndexSubRemRemoteView, SubmenuIndexSubRemAbout, - //SubRemCustomEvent + // EditSubmenuIndex + EditSubmenuIndexEditLabel, + EditSubmenuIndexEditFile, + + // SubRemCustomEvent SubRemCustomEventViewRemoteStartUP = 100, SubRemCustomEventViewRemoteStartDOWN, SubRemCustomEventViewRemoteStartLEFT, @@ -14,4 +28,25 @@ typedef enum { SubRemCustomEventViewRemoteBack, SubRemCustomEventViewRemoteStop, SubRemCustomEventViewRemoteForcedStop, + + SubRemCustomEventViewEditMenuBack, + SubRemCustomEventViewEditMenuUP, + SubRemCustomEventViewEditMenuDOWN, + SubRemCustomEventViewEditMenuEdit, + SubRemCustomEventViewEditMenuSave, + + SubRemCustomEventSceneEditsubmenu, + SubRemCustomEventSceneEditLabelInputDone, + SubRemCustomEventSceneEditLabelWidgetAcces, + SubRemCustomEventSceneEditLabelWidgetBack, + + SubRemCustomEventSceneEditOpenSubErrorPopup, + + SubRemCustomEventSceneEditPreviewSaved, + + SubRemCustomEventSceneNewName, + + // // SceneStates + // SubRemSceneOpenMapFileStateOpen, + // SubRemSceneOpenMapFileStateEdit, } SubRemCustomEvent; \ No newline at end of file diff --git a/applications/external/subghz_remote_new/helpers/subrem_presets.c b/applications/external/subghz_remote_new/helpers/subrem_presets.c new file mode 100644 index 000000000..dc298c069 --- /dev/null +++ b/applications/external/subghz_remote_new/helpers/subrem_presets.c @@ -0,0 +1,180 @@ +#include "subrem_presets.h" + +#define TAG "SubRemPresets" + +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(); + + sub_preset->freq_preset.name = furi_string_alloc(); + + sub_preset->type = SubGhzProtocolTypeUnknown; + sub_preset->load_state = SubRemLoadSubStateNotSet; + + 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); + + furi_string_free(sub_preset->freq_preset.name); + + free(sub_preset); +} + +void subrem_sub_file_preset_reset(SubRemSubFilePreset* sub_preset) { + furi_assert(sub_preset); + + furi_string_set_str(sub_preset->label, ""); + 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; + sub_preset->load_state = SubRemLoadSubStateNotSet; +} + +SubRemLoadSubState subrem_sub_preset_load( + SubRemSubFilePreset* sub_preset, + SubGhzTxRx* txrx, + FlipperFormat* fff_data_file) { + furi_assert(sub_preset); + furi_assert(txrx); + furi_assert(fff_data_file); + + Stream* fff_data_stream = flipper_format_get_raw_stream(sub_preset->fff_data); + + SubRemLoadSubState ret; + FuriString* temp_str = furi_string_alloc(); + uint32_t temp_data32; + uint32_t repeat = 200; + + ret = SubRemLoadSubStateError; + + do { + stream_clean(fff_data_stream); + 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; + } + + SubGhzSetting* setting = subghz_txrx_get_setting(txrx); + + //Load frequency or using default from settings + ret = SubRemLoadSubStateErrorFreq; + 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(setting); + } else if(!furi_hal_subghz_is_tx_allowed(temp_data32)) { + FURI_LOG_E(TAG, "This frequency can only be used for RX"); + break; + } + sub_preset->freq_preset.frequency = temp_data32; + + //Load preset + ret = SubRemLoadSubStateErrorMod; + if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { + FURI_LOG_E(TAG, "Missing Preset"); + break; + } + + furi_string_set_str( + temp_str, subghz_txrx_get_preset_name(txrx, furi_string_get_cstr(temp_str))); + if(!strcmp(furi_string_get_cstr(temp_str), "")) { + break; + } + + if(!strcmp(furi_string_get_cstr(temp_str), "CUSTOM")) { + FURI_LOG_E(TAG, "CUSTOM preset is not supported"); + break; + // TODO Custom preset loading logic if need + // sub_preset->freq_preset.preset_index = + // subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(temp_str)); + } + + furi_string_set(sub_preset->freq_preset.name, temp_str); + + // Load protocol + ret = SubRemLoadSubStateErrorProtocol; + 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)); + } + + if(subghz_txrx_load_decoder_by_name_protocol(txrx, furi_string_get_cstr(temp_str))) { + SubGhzProtocolStatus status = + subghz_protocol_decoder_base_deserialize(subghz_txrx_get_decoder(txrx), fff_data); + if(status != SubGhzProtocolStatusOk) { + break; + } + } else { + FURI_LOG_E(TAG, "Protocol not found"); + break; + } + + const SubGhzProtocol* protocol = subghz_txrx_get_decoder(txrx)->protocol; + + if(protocol->flag & SubGhzProtocolFlag_Send) { + if((protocol->type == SubGhzProtocolTypeStatic) || + (protocol->type == SubGhzProtocolTypeDynamic) || + // TODO: BINRAW It probably works, but checks are needed. + // (protocol->type == SubGhzProtocolTypeBinRAW) || + (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"); + break; + } + + if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &repeat, 1)) { + FURI_LOG_E(TAG, "Unable Repeat"); + break; + } + + ret = SubRemLoadSubStateOK; + +#if FURI_DEBUG + FURI_LOG_I(TAG, "%-16s - protocol Loaded", furi_string_get_cstr(sub_preset->label)); +#endif + } while(false); + + furi_string_free(temp_str); + sub_preset->load_state = ret; + return ret; +} diff --git a/applications/external/subghz_remote_new/helpers/subrem_presets.h b/applications/external/subghz_remote_new/helpers/subrem_presets.h new file mode 100644 index 000000000..d66181b90 --- /dev/null +++ b/applications/external/subghz_remote_new/helpers/subrem_presets.h @@ -0,0 +1,39 @@ +#pragma once + +#include "subrem_types.h" +#include "txrx/subghz_txrx.h" + +#include +#include + +typedef struct { + FuriString* name; + uint32_t frequency; + // size_t preset_index; // Need for custom preset +} FreqPreset; + +// Sub File preset +typedef struct { + FlipperFormat* fff_data; + FreqPreset freq_preset; + FuriString* file_path; + FuriString* protocaol_name; + FuriString* label; + SubGhzProtocolType type; + SubRemLoadSubState load_state; +} SubRemSubFilePreset; + +typedef struct { + SubRemSubFilePreset* subs_preset[SubRemSubKeyNameMaxCount]; +} SubRemMapPreset; + +SubRemSubFilePreset* subrem_sub_file_preset_alloc(); + +void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset); + +void subrem_sub_file_preset_reset(SubRemSubFilePreset* sub_preset); + +SubRemLoadSubState subrem_sub_preset_load( + SubRemSubFilePreset* sub_preset, + SubGhzTxRx* txrx, + FlipperFormat* fff_data_file); diff --git a/applications/external/subghz_remote_new/helpers/subrem_types.h b/applications/external/subghz_remote_new/helpers/subrem_types.h index 13b42897f..b43f8499d 100644 --- a/applications/external/subghz_remote_new/helpers/subrem_types.h +++ b/applications/external/subghz_remote_new/helpers/subrem_types.h @@ -3,9 +3,8 @@ #include #include -// TODO: File version/type logic -// #define SUBREM_APP_APP_FILE_VERSION 1 -// #define SUBREM_APP_APP_FILE_TYPE "Flipper SubRem Map file" +#define SUBREM_APP_APP_FILE_VERSION 1 +#define SUBREM_APP_APP_FILE_TYPE "Flipper SubRem Map file" #define SUBREM_APP_EXTENSION ".txt" typedef enum { @@ -18,17 +17,19 @@ typedef enum { } SubRemSubKeyName; typedef enum { - SubRemViewSubmenu, - SubRemViewWidget, - SubRemViewPopup, - SubRemViewTextInput, + SubRemViewIDSubmenu, + SubRemViewIDWidget, + SubRemViewIDPopup, + SubRemViewIDTextInput, SubRemViewIDRemote, + SubRemViewIDEditMenu, } SubRemViewID; typedef enum { - SubRemLoadSubStateNotSet, + SubRemLoadSubStateNotSet = 0, SubRemLoadSubStatePreloaded, SubRemLoadSubStateError, + SubRemLoadSubStateErrorIncorectPath, SubRemLoadSubStateErrorNoFile, SubRemLoadSubStateErrorFreq, SubRemLoadSubStateErrorMod, diff --git a/applications/external/subghz_remote_new/helpers/txrx/Readme.md b/applications/external/subghz_remote_new/helpers/txrx/Readme.md new file mode 100644 index 000000000..918160198 --- /dev/null +++ b/applications/external/subghz_remote_new/helpers/txrx/Readme.md @@ -0,0 +1,4 @@ +This is part of the official `SubGhz` app from [flipperzero-firmware](https://github.com/flipperdevices/flipperzero-firmware/tree/3217f286f03da119398586daf94c0723d28b872a/applications/main/subghz) + +With changes from [unleashed-firmware +](https://github.com/DarkFlippers/unleashed-firmware/tree/3eac6ccd48a3851cf5d63bf7899b387a293e5319/applications/main/subghz) \ No newline at end of file diff --git a/applications/external/subghz_remote_new/helpers/txrx/subghz_txrx.c b/applications/external/subghz_remote_new/helpers/txrx/subghz_txrx.c new file mode 100644 index 000000000..c1f519ba0 --- /dev/null +++ b/applications/external/subghz_remote_new/helpers/txrx/subghz_txrx.c @@ -0,0 +1,563 @@ +#include "subghz_txrx_i.h" +#include + +#define TAG "SubGhz" + +SubGhzTxRx* subghz_txrx_alloc() { + SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx)); + instance->setting = subghz_setting_alloc(); + subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user")); + + instance->preset = malloc(sizeof(SubGhzRadioPreset)); + instance->preset->name = furi_string_alloc(); + subghz_txrx_set_preset( + instance, "AM650", subghz_setting_get_default_frequency(instance->setting), NULL, 0); + + instance->txrx_state = SubGhzTxRxStateSleep; + + subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF); + subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable); + subghz_txrx_set_debug_pin_state(instance, false); + + instance->worker = subghz_worker_alloc(); + instance->fff_data = flipper_format_string_alloc(); + + instance->environment = subghz_environment_alloc(); + instance->is_database_loaded = subghz_environment_load_keystore( + instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); + subghz_environment_load_keystore( + instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + subghz_environment_set_came_atomo_rainbow_table_file_name( + instance->environment, EXT_PATH("subghz/assets/came_atomo")); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + instance->environment, EXT_PATH("subghz/assets/alutech_at_4n")); + subghz_environment_set_nice_flor_s_rainbow_table_file_name( + instance->environment, EXT_PATH("subghz/assets/nice_flor_s")); + subghz_environment_set_protocol_registry( + instance->environment, (void*)&subghz_protocol_registry); + instance->receiver = subghz_receiver_alloc_init(instance->environment); + + subghz_worker_set_overrun_callback( + instance->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); + subghz_worker_set_pair_callback( + instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); + subghz_worker_set_context(instance->worker, instance->receiver); + + return instance; +} + +void subghz_txrx_free(SubGhzTxRx* instance) { + furi_assert(instance); + + subghz_worker_free(instance->worker); + subghz_receiver_free(instance->receiver); + subghz_environment_free(instance->environment); + flipper_format_free(instance->fff_data); + furi_string_free(instance->preset->name); + subghz_setting_free(instance->setting); + free(instance->preset); + free(instance); +} + +bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->is_database_loaded; +} + +void subghz_txrx_set_preset( + SubGhzTxRx* instance, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size) { + furi_assert(instance); + furi_string_set(instance->preset->name, preset_name); + SubGhzRadioPreset* preset = instance->preset; + preset->frequency = frequency; + preset->data = preset_data; + preset->data_size = preset_data_size; +} + +const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset) { + UNUSED(instance); + 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"; + } else { + FURI_LOG_E(TAG, "Unknown preset"); + } + return preset_name; +} + +SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance) { + furi_assert(instance); + return *instance->preset; +} + +void subghz_txrx_get_frequency_and_modulation( + SubGhzTxRx* instance, + FuriString* frequency, + FuriString* modulation, + bool long_name) { + furi_assert(instance); + SubGhzRadioPreset* preset = instance->preset; + if(frequency != NULL) { + furi_string_printf( + frequency, + "%03ld.%02ld", + preset->frequency / 1000000 % 1000, + preset->frequency / 10000 % 100); + } + if(modulation != NULL) { + if(long_name) { + furi_string_printf(modulation, "%s", furi_string_get_cstr(preset->name)); + } else { + furi_string_printf(modulation, "%.2s", furi_string_get_cstr(preset->name)); + } + } +} + +static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) { + furi_assert(instance); + furi_hal_subghz_reset(); + furi_hal_subghz_idle(); + furi_hal_subghz_load_custom_preset(preset_data); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + instance->txrx_state = SubGhzTxRxStateIDLE; +} + +static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + furi_crash("SubGhz: Incorrect RX frequency."); + } + furi_assert( + instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep); + + furi_hal_subghz_idle(); + uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_subghz_flush_rx(); + subghz_txrx_speaker_on(instance); + furi_hal_subghz_rx(); + + furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, instance->worker); + subghz_worker_start(instance->worker); + instance->txrx_state = SubGhzTxRxStateRx; + return value; +} + +static void subghz_txrx_idle(SubGhzTxRx* instance) { + furi_assert(instance); + furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); + furi_hal_subghz_idle(); + subghz_txrx_speaker_off(instance); + instance->txrx_state = SubGhzTxRxStateIDLE; +} + +static void subghz_txrx_rx_end(SubGhzTxRx* instance) { + furi_assert(instance); + furi_assert(instance->txrx_state == SubGhzTxRxStateRx); + + if(subghz_worker_is_running(instance->worker)) { + subghz_worker_stop(instance->worker); + furi_hal_subghz_stop_async_rx(); + } + furi_hal_subghz_idle(); + subghz_txrx_speaker_off(instance); + instance->txrx_state = SubGhzTxRxStateIDLE; +} + +void subghz_txrx_sleep(SubGhzTxRx* instance) { + furi_assert(instance); + furi_hal_subghz_sleep(); + instance->txrx_state = SubGhzTxRxStateSleep; +} + +static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + furi_crash("SubGhz: Incorrect TX frequency."); + } + furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); + furi_hal_subghz_idle(); + furi_hal_subghz_set_frequency_and_path(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); + bool ret = furi_hal_subghz_tx(); + if(ret) { + subghz_txrx_speaker_on(instance); + instance->txrx_state = SubGhzTxRxStateTx; + } + + return ret; +} + +SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + furi_assert(flipper_format); + + subghz_txrx_stop(instance); + + SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers; + FuriString* temp_str = furi_string_alloc(); + uint32_t repeat = 200; + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { + FURI_LOG_E(TAG, "Unable Repeat"); + break; + } + ret = SubGhzTxRxStartTxStateOk; + + SubGhzRadioPreset* preset = instance->preset; + instance->transmitter = + subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str)); + + if(instance->transmitter) { + if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) == + SubGhzProtocolStatusOk) { + if(strcmp(furi_string_get_cstr(preset->name), "") != 0) { + subghz_txrx_begin( + instance, + subghz_setting_get_preset_data_by_name( + instance->setting, furi_string_get_cstr(preset->name))); + if(preset->frequency) { + if(!subghz_txrx_tx(instance, preset->frequency)) { + FURI_LOG_E(TAG, "Only Rx"); + ret = SubGhzTxRxStartTxStateErrorOnlyRx; + } + } else { + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + + } else { + FURI_LOG_E( + TAG, "Unknown name preset \" %s \"", furi_string_get_cstr(preset->name)); + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + + if(ret == SubGhzTxRxStartTxStateOk) { + //Start TX + furi_hal_subghz_start_async_tx( + subghz_transmitter_yield, instance->transmitter); + } + } else { + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + } else { + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + if(ret != SubGhzTxRxStartTxStateOk) { + subghz_transmitter_free(instance->transmitter); + if(instance->txrx_state != SubGhzTxRxStateIDLE) { + subghz_txrx_idle(instance); + } + } + + } while(false); + furi_string_free(temp_str); + return ret; +} + +void subghz_txrx_rx_start(SubGhzTxRx* instance) { + furi_assert(instance); + subghz_txrx_stop(instance); + subghz_txrx_begin( + instance, + subghz_setting_get_preset_data_by_name( + subghz_txrx_get_setting(instance), furi_string_get_cstr(instance->preset->name))); + subghz_txrx_rx(instance, instance->preset->frequency); +} + +void subghz_txrx_set_need_save_callback( + SubGhzTxRx* instance, + SubGhzTxRxNeedSaveCallback callback, + void* context) { + furi_assert(instance); + instance->need_save_callback = callback; + instance->need_save_context = context; +} + +static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { + furi_assert(instance); + furi_assert(instance->txrx_state == SubGhzTxRxStateTx); + //Stop TX + furi_hal_subghz_stop_async_tx(); + subghz_transmitter_stop(instance->transmitter); + subghz_transmitter_free(instance->transmitter); + + //if protocol dynamic then we save the last upload + if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) { + if(instance->need_save_callback) { + instance->need_save_callback(instance->need_save_context); + } + } + subghz_txrx_idle(instance); + subghz_txrx_speaker_off(instance); + //Todo: Show message + // notification_message(notifications, &sequence_reset_red); +} + +FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->fff_data; +} + +SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->setting; +} + +void subghz_txrx_stop(SubGhzTxRx* instance) { + furi_assert(instance); + + switch(instance->txrx_state) { + case SubGhzTxRxStateTx: + subghz_txrx_tx_stop(instance); + subghz_txrx_speaker_unmute(instance); + break; + case SubGhzTxRxStateRx: + subghz_txrx_rx_end(instance); + subghz_txrx_speaker_mute(instance); + break; + + default: + break; + } +} + +void subghz_txrx_hopper_update(SubGhzTxRx* instance) { + furi_assert(instance); + + switch(instance->hopper_state) { + case SubGhzHopperStateOFF: + case SubGhzHopperStatePause: + return; + case SubGhzHopperStateRSSITimeOut: + if(instance->hopper_timeout != 0) { + instance->hopper_timeout--; + return; + } + break; + default: + break; + } + float rssi = -127.0f; + if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) { + // See RSSI Calculation timings in CC1101 17.3 RSSI + rssi = furi_hal_subghz_get_rssi(); + + // Stay if RSSI is high enough + if(rssi > -90.0f) { + instance->hopper_timeout = 10; + instance->hopper_state = SubGhzHopperStateRSSITimeOut; + return; + } + } else { + instance->hopper_state = SubGhzHopperStateRunning; + } + // Select next frequency + if(instance->hopper_idx_frequency < + subghz_setting_get_hopper_frequency_count(instance->setting) - 1) { + instance->hopper_idx_frequency++; + } else { + instance->hopper_idx_frequency = 0; + } + + if(instance->txrx_state == SubGhzTxRxStateRx) { + subghz_txrx_rx_end(instance); + }; + if(instance->txrx_state == SubGhzTxRxStateIDLE) { + subghz_receiver_reset(instance->receiver); + instance->preset->frequency = + subghz_setting_get_hopper_frequency(instance->setting, instance->hopper_idx_frequency); + subghz_txrx_rx(instance, instance->preset->frequency); + } +} + +SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->hopper_state; +} + +void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state) { + furi_assert(instance); + instance->hopper_state = state; +} + +void subghz_txrx_hopper_unpause(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->hopper_state == SubGhzHopperStatePause) { + instance->hopper_state = SubGhzHopperStateRunning; + } +} + +void subghz_txrx_hopper_pause(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->hopper_state == SubGhzHopperStateRunning) { + instance->hopper_state = SubGhzHopperStatePause; + } +} + +void subghz_txrx_speaker_on(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton); + } + + if(instance->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_acquire(30)) { + if(!instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } + } else { + instance->speaker_state = SubGhzSpeakerStateDisable; + } + } +} + +void subghz_txrx_speaker_off(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } + if(instance->speaker_state != SubGhzSpeakerStateDisable) { + if(furi_hal_speaker_is_mine()) { + if(!instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } + furi_hal_speaker_release(); + if(instance->speaker_state == SubGhzSpeakerStateShutdown) + instance->speaker_state = SubGhzSpeakerStateDisable; + } + } +} + +void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } + if(instance->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_is_mine()) { + if(!instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } + } + } +} + +void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton); + } + if(instance->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_is_mine()) { + if(!instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } + } + } +} + +void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state) { + furi_assert(instance); + instance->speaker_state = state; +} + +SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->speaker_state; +} + +bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol) { + furi_assert(instance); + furi_assert(name_protocol); + bool res = false; + instance->decoder_result = + subghz_receiver_search_decoder_base_by_name(instance->receiver, name_protocol); + if(instance->decoder_result) { + res = true; + } + return res; +} + +SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->decoder_result; +} + +bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance) { + furi_assert(instance); + return ( + (instance->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) == + SubGhzProtocolFlag_Save); +} + +bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type) { + furi_assert(instance); + const SubGhzProtocol* protocol = instance->decoder_result->protocol; + if(check_type) { + return ( + ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) && + protocol->encoder->deserialize && protocol->type == SubGhzProtocolTypeStatic); + } + return ( + ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) && + protocol->encoder->deserialize); +} + +void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter) { + furi_assert(instance); + subghz_receiver_set_filter(instance->receiver, filter); +} + +void subghz_txrx_set_rx_calback( + SubGhzTxRx* instance, + SubGhzReceiverCallback callback, + void* context) { + subghz_receiver_set_rx_callback(instance->receiver, callback, context); +} + +void subghz_txrx_set_raw_file_encoder_worker_callback_end( + SubGhzTxRx* instance, + SubGhzProtocolEncoderRAWCallbackEnd callback, + void* context) { + subghz_protocol_raw_file_encoder_worker_set_callback_end( + (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance(instance->transmitter), + callback, + context); +} + +void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state) { + furi_assert(instance); + instance->debug_pin_state = state; +} + +bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->debug_pin_state; +} + +SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->receiver; +} \ No newline at end of file diff --git a/applications/external/subghz_remote_new/helpers/txrx/subghz_txrx.h b/applications/external/subghz_remote_new/helpers/txrx/subghz_txrx.h new file mode 100644 index 000000000..b2ebcc5f3 --- /dev/null +++ b/applications/external/subghz_remote_new/helpers/txrx/subghz_txrx.h @@ -0,0 +1,318 @@ +#pragma once + +#include +#include +#include +#include +#include + +typedef struct SubGhzTxRx SubGhzTxRx; + +typedef void (*SubGhzTxRxNeedSaveCallback)(void* context); + +typedef enum { + SubGhzTxRxStartTxStateOk, + SubGhzTxRxStartTxStateErrorOnlyRx, + SubGhzTxRxStartTxStateErrorParserOthers, +} SubGhzTxRxStartTxState; + +// Type from subghz_types.h need for txrx working +/** SubGhzTxRx state */ +typedef enum { + SubGhzTxRxStateIDLE, + SubGhzTxRxStateRx, + SubGhzTxRxStateTx, + SubGhzTxRxStateSleep, +} SubGhzTxRxState; + +/** SubGhzHopperState state */ +typedef enum { + SubGhzHopperStateOFF, + SubGhzHopperStateRunning, + SubGhzHopperStatePause, + SubGhzHopperStateRSSITimeOut, +} SubGhzHopperState; + +/** SubGhzSpeakerState state */ +typedef enum { + SubGhzSpeakerStateDisable, + SubGhzSpeakerStateShutdown, + SubGhzSpeakerStateEnable, +} SubGhzSpeakerState; + +/** + * Allocate SubGhzTxRx + * + * @return SubGhzTxRx* pointer to SubGhzTxRx + */ +SubGhzTxRx* subghz_txrx_alloc(); + +/** + * Free SubGhzTxRx + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_free(SubGhzTxRx* instance); + +/** + * Check if the database is loaded + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if the database is loaded + */ +bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance); + +/** + * Set preset + * + * @param instance Pointer to a SubGhzTxRx + * @param preset_name Name of preset + * @param frequency Frequency in Hz + * @param preset_data Data of preset + * @param preset_data_size Size of preset data + */ +void subghz_txrx_set_preset( + SubGhzTxRx* instance, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size); + +/** + * Get name of preset + * + * @param instance Pointer to a SubGhzTxRx + * @param preset String of preset + * @return const char* Name of preset + */ +const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset); + +/** + * Get of preset + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzRadioPreset Preset + */ +SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance); + +/** + * Get string frequency and modulation + * + * @param instance Pointer to a SubGhzTxRx + * @param frequency Pointer to a string frequency + * @param modulation Pointer to a string modulation + */ +void subghz_txrx_get_frequency_and_modulation( + SubGhzTxRx* instance, + FuriString* frequency, + FuriString* modulation, + bool long_name); + +/** + * Start TX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + * @param flipper_format Pointer to a FlipperFormat + * @return SubGhzTxRxStartTxState + */ +SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format); + +/** + * Start RX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_rx_start(SubGhzTxRx* instance); + +/** + * Stop TX/RX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_stop(SubGhzTxRx* instance); + +/** + * Set sleep mode CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_sleep(SubGhzTxRx* instance); + +/** + * Update frequency CC1101 in automatic mode (hopper) + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_update(SubGhzTxRx* instance); + +/** + * Get state hopper + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzHopperState + */ +SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance); + +/** + * Set state hopper + * + * @param instance Pointer to a SubGhzTxRx + * @param state State hopper + */ +void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state); + +/** + * Unpause hopper + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_unpause(SubGhzTxRx* instance); + +/** + * Set pause hopper + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_pause(SubGhzTxRx* instance); + +/** + * Speaker on + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_on(SubGhzTxRx* instance); + +/** + * Speaker off + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_off(SubGhzTxRx* instance); + +/** + * Speaker mute + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_mute(SubGhzTxRx* instance); + +/** + * Speaker unmute + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_unmute(SubGhzTxRx* instance); + +/** + * Set state speaker + * + * @param instance Pointer to a SubGhzTxRx + * @param state State speaker + */ +void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state); + +/** + * Get state speaker + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzSpeakerState + */ +SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance); + +/** + * load decoder by name protocol + * + * @param instance Pointer to a SubGhzTxRx + * @param name_protocol Name protocol + * @return bool True if the decoder is loaded + */ +bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol); + +/** + * Get decoder + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzProtocolDecoderBase* Pointer to a SubGhzProtocolDecoderBase + */ +SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance); + +/** + * Set callback for save data + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for save data + * @param context Context for callback + */ +void subghz_txrx_set_need_save_callback( + SubGhzTxRx* instance, + SubGhzTxRxNeedSaveCallback callback, + void* context); + +/** + * Get pointer to a load data key + * + * @param instance Pointer to a SubGhzTxRx + * @return FlipperFormat* + */ +FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance); + +/** + * Get pointer to a SugGhzSetting + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzSetting* + */ +SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance); + +/** + * Is it possible to save this protocol + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if it is possible to save this protocol + */ +bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance); + +/** + * Is it possible to send this protocol + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if it is possible to send this protocol + */ +bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type); + +/** + * Set filter, what types of decoder to use + * + * @param instance Pointer to a SubGhzTxRx + * @param filter Filter + */ +void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter); + +/** + * Set callback for receive data + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for receive data + * @param context Context for callback + */ +void subghz_txrx_set_rx_calback( + SubGhzTxRx* instance, + SubGhzReceiverCallback callback, + void* context); + +/** + * Set callback for Raw decoder, end of data transfer + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for Raw decoder, end of data transfer + * @param context Context for callback + */ +void subghz_txrx_set_raw_file_encoder_worker_callback_end( + SubGhzTxRx* instance, + SubGhzProtocolEncoderRAWCallbackEnd callback, + void* context); + +void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state); +bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance); + +SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance); // TODO use only in DecodeRaw diff --git a/applications/external/subghz_remote_new/helpers/txrx/subghz_txrx_i.h b/applications/external/subghz_remote_new/helpers/txrx/subghz_txrx_i.h new file mode 100644 index 000000000..680d27158 --- /dev/null +++ b/applications/external/subghz_remote_new/helpers/txrx/subghz_txrx_i.h @@ -0,0 +1,29 @@ + +#pragma once +#include "subghz_txrx.h" + +struct SubGhzTxRx { + SubGhzWorker* worker; + + SubGhzEnvironment* environment; + SubGhzReceiver* receiver; + SubGhzTransmitter* transmitter; + SubGhzProtocolDecoderBase* decoder_result; + FlipperFormat* fff_data; + + SubGhzRadioPreset* preset; + SubGhzSetting* setting; + + uint8_t hopper_timeout; + uint8_t hopper_idx_frequency; + bool is_database_loaded; + SubGhzHopperState hopper_state; + + SubGhzTxRxState txrx_state; + SubGhzSpeakerState speaker_state; + + SubGhzTxRxNeedSaveCallback need_save_callback; + void* need_save_context; + + bool debug_pin_state; +}; \ No newline at end of file diff --git a/applications/external/subghz_remote_new/icons/DolphinNice_96x59.png b/applications/external/subghz_remote_new/icons/DolphinNice_96x59.png new file mode 100644 index 0000000000000000000000000000000000000000..a299d3630239b4486e249cc501872bed5996df3b GIT binary patch literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!QCbE6;07#-3sX@sAxkg$U?hnu$6%_P+PX_Jv(ud zLf1xG|CwUz?7R1Up5OC4zxVgNce*W&4YheW_vK(1mglK+H=%$1JZE+m`hA@F<1zI2 zyA8fptqH{ONK}=TAjGw<2*hDRkufZBKGgVD-U({Lf_l{mhyV1gGrhn5dOvmG&rb;X|2#O* z`;Gdu`y8=lFC6>o;MdlzEhu;(gUPC_z|AEGi;B+`t?Ze5z3-kKC+@xe!rsi0?9s|K zy?-daKNrL9+N8K#jUJb4ydqS`GmmU@)cv;7aPpz%>TUZsFY*}}-;x*iZ59ty6_jpT zvujoMQ}z8jJ+AG;!%Gj}Yq-_gD;(ypTplW&z40q}pQ&N1scCq0d(~q_cR%sbwf8Sv zdVdlA`l;oI1plLZ-jYiT3faL`zr6A#=Lo=7=AJsu{N?^-b1q)%coKW)>T~u}qi^rn z-7>H`clPEJwEVR7TGqAGdqR;5OY#pr*E?^={1s1Y&f(g=vM=|qHywW9AEyugs9|9K z_qUv^T38l3y>(BG-D_BB`RVoV^}JI09`V|mBd`AW<~wBWyCXky1n0|1Nlg+*V)QGN;EdcVFdq|MubW(V_Tn9{lz<&(!Cf?0&8A zl@E$Ck9Ky~46J|Y$whnDXUy8sU3Tos>?t>Un9|+}sNpj`p?cz$4F;W6I^yu1td=)j zwphHBH{ybAO5KJiY~Ik|6F0PrHpy5~o?}l42p|MCfG0x1a7;)zj7eMpo$JG-5l@?` zHXBJXB*PHMf{1m6HIN{}u@W63h2e%VF{(r~MGfORCh)5rn!{*B^Z0mvp@`R;h7ZTa zSU`M`2@oM^6GetXP{HeN+v@{V%k5_5e+8G zkwg*(VF;PVP*i$K$XbuLG3}vK5Kuyqq!%K4ie;ot)zny<8cCZ^NiaQ~ENpU0nj%lI zJjF+!xy>BKy>oC09YBCl_0@MKq5HSOc8#H zHxrPt@G@uPXxUw}=@F^kKtO1=<+RE`fZLx72 zM^h}%PZ&K2qcJ389h0U^tT{O|!J$hHs!^{hL5Gn|PU-6=pgIxrK<@yAog7DH3a%&w z8g!!r!BMD#C>udrd^9VVErOXZqga7TC7!lcqdrv)I*fX4xSm29%!}Gu0vbreA!k;g z%|4nF%vOQs$|!0w97;_$K_%JJIG$`y z0f?!B#blXMGE;<>npEzfp3l7GX_S~MYjF^T&H&=qVRY(yC*C;TeK^CKEcntEB`m4& z*s`e!#M_|0il0b3`57vUflm0by2LgR4nVX&k8KG5wO*Mw+x#3L%ravoDAo)K9*8VK z`z@R#$`oXol=A*h>SY-g(0){rAj zX|i`eX-Qc`CULv;$ClJi>UW`W?b^xP)oq_>=<%(|iFP_&{;^5&uL6OoA}L2u$;}G@ zRN$B(Zj5Yq}83M;=f=r9w8MiVD2l{4`U z0fy0oX&k*FI5pSMi{36|`Ri-l*r@*9d2H`fXk<>LZgmX9OeOkpSK|4KPBfUUdA!xx z?`7r}mf + +typedef enum { + SubRemSceneEditLabelStateTextInput, + SubRemSceneEditLabelStateWidget, +} SubRemSceneEditLabelState; + +void subrem_scene_edit_label_text_input_callback(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event( + app->view_dispatcher, SubRemCustomEventSceneEditLabelInputDone); +} + +void subrem_scene_edit_label_widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + if((result == GuiButtonTypeCenter) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event( + app->view_dispatcher, SubRemCustomEventSceneEditLabelWidgetAcces); + } else if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event( + app->view_dispatcher, SubRemCustomEventSceneEditLabelWidgetBack); + } +} + +void subrem_scene_edit_label_on_enter(void* context) { + SubGhzRemoteApp* app = context; + + SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chusen_sub]; + + FuriString* temp_str = furi_string_alloc(); + + if(furi_string_empty(sub_preset->label)) { + if(furi_string_empty(sub_preset->file_path)) { + path_extract_filename(sub_preset->file_path, temp_str, true); + strcpy(app->file_name_tmp, furi_string_get_cstr(temp_str)); + } else { + strcpy(app->file_name_tmp, ""); + } + } else { + strcpy(app->file_name_tmp, furi_string_get_cstr(sub_preset->label)); + } + + TextInput* text_input = app->text_input; + text_input_set_header_text(text_input, "Label name"); + text_input_set_result_callback( + text_input, + subrem_scene_edit_label_text_input_callback, + app, + app->file_name_tmp, + 25, + false); + + text_input_set_minimum_length(app->text_input, 0); + + widget_add_string_element( + app->widget, 63, 12, AlignCenter, AlignCenter, FontPrimary, "Empty Label Name"); + widget_add_string_element( + app->widget, 63, 32, AlignCenter, AlignCenter, FontSecondary, "Continue?"); + + widget_add_button_element( + app->widget, GuiButtonTypeCenter, "Ok", subrem_scene_edit_label_widget_callback, app); + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Back", subrem_scene_edit_label_widget_callback, app); + + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneEditLabel, SubRemSceneEditLabelStateTextInput); + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDTextInput); + + furi_string_free(temp_str); +} + +bool subrem_scene_edit_label_on_event(void* context, SceneManagerEvent event) { + SubGhzRemoteApp* app = context; + + FuriString* label = app->map_preset->subs_preset[app->chusen_sub]->label; + + if(event.type == SceneManagerEventTypeBack) { + if(scene_manager_get_scene_state(app->scene_manager, SubRemSceneEditLabel) == + SubRemSceneEditLabelStateWidget) { + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneEditLabel, SubRemSceneEditLabelStateTextInput); + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDTextInput); + return true; + } else if( + scene_manager_get_scene_state(app->scene_manager, SubRemSceneEditLabel) == + SubRemSceneEditLabelStateTextInput) { + scene_manager_previous_scene(app->scene_manager); + return true; + } + + scene_manager_previous_scene(app->scene_manager); + return true; + } else if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubRemCustomEventSceneEditLabelInputDone) { + if(strcmp(app->file_name_tmp, "") == 0) { + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneEditLabel, SubRemSceneEditLabelStateWidget); + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDWidget); + + } else { + furi_string_set(label, app->file_name_tmp); + app->map_not_saved = true; + scene_manager_previous_scene(app->scene_manager); + } + return true; + } else if(event.event == SubRemCustomEventSceneEditLabelWidgetAcces) { + furi_string_set(label, app->file_name_tmp); + app->map_not_saved = true; + scene_manager_previous_scene(app->scene_manager); + + return true; + } else if(event.event == SubRemCustomEventSceneEditLabelWidgetBack) { + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneEditLabel, SubRemSceneEditLabelStateTextInput); + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDTextInput); + + return true; + } + } + return false; +} + +void subrem_scene_edit_label_on_exit(void* context) { + SubGhzRemoteApp* app = context; + + // Clear view + text_input_reset(app->text_input); + widget_reset(app->widget); +} diff --git a/applications/external/subghz_remote_new/scenes/subrem_scene_edit_menu.c b/applications/external/subghz_remote_new/scenes/subrem_scene_edit_menu.c new file mode 100644 index 000000000..a8882009a --- /dev/null +++ b/applications/external/subghz_remote_new/scenes/subrem_scene_edit_menu.c @@ -0,0 +1,123 @@ +#include "../subghz_remote_app_i.h" + +void subrem_scene_edit_menu_callback(SubRemCustomEvent event, void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subrem_scene_edit_menu_widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { + app->map_not_saved = false; + view_dispatcher_send_custom_event(app->view_dispatcher, SubRemCustomEventViewEditMenuBack); + } else if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDEditMenu); + } +} + +static uint8_t subrem_scene_edit_menu_state_to_index(SubRemEditMenuState event_id) { + uint8_t ret = 0; + + if(event_id == SubRemEditMenuStateUP) { + ret = SubRemSubKeyNameUp; + } else if(event_id == SubRemEditMenuStateDOWN) { + ret = SubRemSubKeyNameDown; + } else if(event_id == SubRemEditMenuStateLEFT) { + ret = SubRemSubKeyNameLeft; + } else if(event_id == SubRemEditMenuStateRIGHT) { + ret = SubRemSubKeyNameRight; + } else if(event_id == SubRemEditMenuStateOK) { + ret = SubRemSubKeyNameOk; + } + + return ret; +} + +static void subrem_scene_edit_menu_update_data(SubGhzRemoteApp* app) { + furi_assert(app); + uint8_t index = subrem_scene_edit_menu_state_to_index( + scene_manager_get_scene_state(app->scene_manager, SubRemSceneEditMenu)); + + subrem_view_edit_menu_add_data_to_show( + app->subrem_edit_menu, + index, + app->map_preset->subs_preset[index]->label, + app->map_preset->subs_preset[index]->file_path, + app->map_preset->subs_preset[index]->load_state); +} + +void subrem_scene_edit_menu_on_enter(void* context) { + SubGhzRemoteApp* app = context; + + subrem_view_edit_menu_set_callback( + app->subrem_edit_menu, subrem_scene_edit_menu_callback, app); + + subrem_scene_edit_menu_update_data(app); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDEditMenu); + + Widget* widget = app->widget; + + widget_add_string_element( + widget, 63, 12, AlignCenter, AlignBottom, FontPrimary, "Changes are not saved"); + widget_add_string_element( + widget, 63, 32, AlignCenter, AlignBottom, FontPrimary, "do you want to exit?"); + + widget_add_button_element( + widget, GuiButtonTypeRight, "Yes", subrem_scene_edit_menu_widget_callback, app); + widget_add_button_element( + widget, GuiButtonTypeLeft, "No", subrem_scene_edit_menu_widget_callback, app); +} + +bool subrem_scene_edit_menu_on_event(void* context, SceneManagerEvent event) { + SubGhzRemoteApp* app = context; + + if(event.type == SceneManagerEventTypeBack) { + // Catch widget backEvent + return true; + } + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubRemCustomEventViewEditMenuBack) { + if(app->map_not_saved) { + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDWidget); + } else if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SubRemSceneStart)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + + return true; + } else if( + event.event == SubRemCustomEventViewEditMenuUP || + event.event == SubRemCustomEventViewEditMenuDOWN) { + scene_manager_set_scene_state( + app->scene_manager, + SubRemSceneEditMenu, + subrem_view_edit_menu_get_index(app->subrem_edit_menu)); + subrem_scene_edit_menu_update_data(app); + + return true; + } else if(event.event == SubRemCustomEventViewEditMenuEdit) { + app->chusen_sub = subrem_view_edit_menu_get_index(app->subrem_edit_menu); + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneEditSubMenu, EditSubmenuIndexEditLabel); + scene_manager_next_scene(app->scene_manager, SubRemSceneEditSubMenu); + + return true; + } else if(event.event == SubRemCustomEventViewEditMenuSave) { + scene_manager_next_scene(app->scene_manager, SubRemSceneEditPreview); + + return true; + } + } + + return false; +} + +void subrem_scene_edit_menu_on_exit(void* context) { + SubGhzRemoteApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/external/subghz_remote_new/scenes/subrem_scene_edit_preview.c b/applications/external/subghz_remote_new/scenes/subrem_scene_edit_preview.c new file mode 100644 index 000000000..98a423202 --- /dev/null +++ b/applications/external/subghz_remote_new/scenes/subrem_scene_edit_preview.c @@ -0,0 +1,74 @@ +#include "../subghz_remote_app_i.h" +#include "../views/remote.h" + +#define TAG "SubRemScenRemote" + +void subghz_scene_edit_preview_save_popup_callback(void* context) { + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event( + app->view_dispatcher, SubRemCustomEventSceneEditPreviewSaved); +} + +void subrem_scene_edit_preview_callback(SubRemCustomEvent event, void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subrem_scene_edit_preview_on_enter(void* context) { + SubGhzRemoteApp* app = context; + + // Setup view + Popup* popup = app->popup; + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, app); + popup_set_callback(popup, subghz_scene_edit_preview_save_popup_callback); + popup_enable_timeout(popup); + + subrem_view_remote_update_data_labels(app->subrem_remote_view, app->map_preset->subs_preset); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateOFF, 0); + + subrem_view_remote_set_callback( + app->subrem_remote_view, subrem_scene_edit_preview_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDRemote); +} + +bool subrem_scene_edit_preview_on_event(void* context, SceneManagerEvent event) { + SubGhzRemoteApp* app = context; + + if(event.type == SceneManagerEventTypeBack || + (event.type == SceneManagerEventTypeCustom && + (event.event == SubRemCustomEventViewRemoteStartLEFT || + event.event == SubRemCustomEventViewRemoteForcedStop))) { + scene_manager_previous_scene(app->scene_manager); + return true; + } else if( + event.type == SceneManagerEventTypeCustom && + (event.event == SubRemCustomEventViewRemoteStartRIGHT || + event.event == SubRemCustomEventViewRemoteStartOK)) { + if(subrem_save_map_to_file(app)) { + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDPopup); + app->map_not_saved = false; + return true; + } + // TODO error screen + return true; + } else if( + event.type == SceneManagerEventTypeCustom && + event.event == SubRemCustomEventSceneEditPreviewSaved) { + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, SubRemSceneEditMenu); + } + // } else if(event.type == SceneManagerEventTypeTick) { + // } + return false; +} + +void subrem_scene_edit_preview_on_exit(void* context) { + SubGhzRemoteApp* app = context; + + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle, 0); + popup_reset(app->popup); +} diff --git a/applications/external/subghz_remote_new/scenes/subrem_scene_edit_submenu.c b/applications/external/subghz_remote_new/scenes/subrem_scene_edit_submenu.c new file mode 100644 index 000000000..447beb96d --- /dev/null +++ b/applications/external/subghz_remote_new/scenes/subrem_scene_edit_submenu.c @@ -0,0 +1,54 @@ +#include "../subghz_remote_app_i.h" +#include "../helpers/subrem_custom_event.h" + +void subrem_scene_edit_submenu_text_input_callback(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SubRemCustomEventSceneEditsubmenu); +} + +void subrem_scene_edit_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + SubGhzRemoteApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void subrem_scene_edit_submenu_on_enter(void* context) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + Submenu* submenu = app->submenu; + submenu_add_item( + submenu, "Edit Label", EditSubmenuIndexEditLabel, subrem_scene_edit_submenu_callback, app); + submenu_add_item( + submenu, "Edit File", EditSubmenuIndexEditFile, subrem_scene_edit_submenu_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDSubmenu); +} + +bool subrem_scene_edit_submenu_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == EditSubmenuIndexEditLabel) { + scene_manager_next_scene(app->scene_manager, SubRemSceneEditLabel); + consumed = true; + } else if(event.event == EditSubmenuIndexEditFile) { + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenSubFile); + consumed = true; + } + } + + return consumed; +} + +void subrem_scene_edit_submenu_on_exit(void* context) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/external/subghz_remote_new/scenes/subrem_scene_enter_new_name.c b/applications/external/subghz_remote_new/scenes/subrem_scene_enter_new_name.c new file mode 100644 index 000000000..d33e070af --- /dev/null +++ b/applications/external/subghz_remote_new/scenes/subrem_scene_enter_new_name.c @@ -0,0 +1,71 @@ +#include "../subghz_remote_app_i.h" +#include "../helpers/subrem_custom_event.h" + +#include + +void subrem_scene_enter_new_name_text_input_callback(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SubRemCustomEventSceneNewName); +} + +void subrem_scene_enter_new_name_on_enter(void* context) { + SubGhzRemoteApp* app = context; + + // Setup view + TextInput* text_input = app->text_input; + bool dev_name_empty = false; + + strncpy(app->file_name_tmp, "subrem_", SUBREM_MAX_LEN_NAME); + text_input_set_header_text(text_input, "Map file Name"); + text_input_set_result_callback( + text_input, + subrem_scene_enter_new_name_text_input_callback, + app, + app->file_name_tmp, + 25, + dev_name_empty); + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + furi_string_get_cstr(app->file_path), SUBREM_APP_EXTENSION, ""); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDTextInput); +} + +bool subrem_scene_enter_new_name_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubRemCustomEventSceneNewName) { + if(strcmp(app->file_name_tmp, "") != 0) { + furi_string_set(app->file_path, SUBREM_APP_FOLDER); + furi_string_cat_printf( + app->file_path, "/%s%s", app->file_name_tmp, SUBREM_APP_EXTENSION); + + subrem_map_preset_reset(app->map_preset); + scene_manager_next_scene(app->scene_manager, SubRemSceneEditMenu); + } else { //error + } + consumed = true; + } + } + + return consumed; +} + +void subrem_scene_enter_new_name_on_exit(void* context) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + submenu_reset(app->submenu); + + // Clear validator & view + void* validator_context = text_input_get_validator_callback_context(app->text_input); + text_input_set_validator(app->text_input, NULL, NULL); + validator_is_file_free(validator_context); + text_input_reset(app->text_input); +} diff --git a/applications/external/subghz_remote_new/scenes/subrem_scene_open_map_file.c b/applications/external/subghz_remote_new/scenes/subrem_scene_open_map_file.c new file mode 100644 index 000000000..77f2d2d56 --- /dev/null +++ b/applications/external/subghz_remote_new/scenes/subrem_scene_open_map_file.c @@ -0,0 +1,29 @@ +#include "../subghz_remote_app_i.h" + +void subrem_scene_open_map_file_on_enter(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + + SubRemLoadMapState load_state = subrem_load_from_file(app); + uint32_t start_scene_state = + scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart); + + // TODO if optimization + + if(load_state == SubRemLoadMapStateBack) { + scene_manager_previous_scene(app->scene_manager); + } else if(start_scene_state == SubmenuIndexSubRemEditMapFile) { + scene_manager_set_scene_state(app->scene_manager, SubRemSceneEditMenu, SubRemSubKeyNameUp); + scene_manager_next_scene(app->scene_manager, SubRemSceneEditMenu); + } +} + +bool subrem_scene_open_map_file_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void subrem_scene_open_map_file_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/subghz_remote_new/scenes/subrem_scene_open_sub_file.c b/applications/external/subghz_remote_new/scenes/subrem_scene_open_sub_file.c new file mode 100644 index 000000000..75dc21670 --- /dev/null +++ b/applications/external/subghz_remote_new/scenes/subrem_scene_open_sub_file.c @@ -0,0 +1,116 @@ +#include "../subghz_remote_app_i.h" + +void subrem_scene_open_sub_file_error_popup_callback(void* context) { + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event( + app->view_dispatcher, SubRemCustomEventSceneEditOpenSubErrorPopup); +} + +SubRemLoadSubState subrem_scene_open_sub_file_dialog(SubGhzRemoteApp* app) { + furi_assert(app); + + SubRemSubFilePreset* sub = app->map_preset->subs_preset[app->chusen_sub]; + + FuriString* temp_file_path = furi_string_alloc_set(sub->file_path); + + SubRemLoadSubState ret = SubRemLoadSubStateNotSet; + + DialogsFileBrowserOptions browser_options; + + dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px); + browser_options.base_path = SUBGHZ_APP_FOLDER; + + // Input events and views are managed by file_select + if(!dialog_file_browser_show( + app->dialogs, + temp_file_path, + furi_string_empty(temp_file_path) ? NULL : temp_file_path, + &browser_options)) { + } else { + // Check sub file + SubRemSubFilePreset* sub_candidate = subrem_sub_file_preset_alloc(); + furi_string_set(sub_candidate->label, sub->label); + furi_string_set(sub_candidate->file_path, temp_file_path); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_file = flipper_format_file_alloc(storage); + + if(flipper_format_file_open_existing( + fff_file, furi_string_get_cstr(sub_candidate->file_path))) { + ret = subrem_sub_preset_load(sub_candidate, app->txrx, fff_file); + } + + flipper_format_file_close(fff_file); + flipper_format_free(fff_file); + furi_record_close(RECORD_STORAGE); + + if(ret == SubRemLoadSubStateOK) { + subrem_sub_file_preset_free(app->map_preset->subs_preset[app->chusen_sub]); + app->map_preset->subs_preset[app->chusen_sub] = sub_candidate; + app->map_not_saved = true; + } else { + subrem_sub_file_preset_free(sub_candidate); + } + } + + furi_string_free(temp_file_path); + + return ret; +} + +void subrem_scene_open_sub_file_on_enter(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + + SubRemLoadSubState load_state = subrem_scene_open_sub_file_dialog(app); + + Popup* popup = app->popup; + // popup_set_icon(); + popup_set_header(popup, "ERROR", 63, 16, AlignCenter, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, app); + popup_set_callback(popup, subrem_scene_open_sub_file_error_popup_callback); + popup_enable_timeout(popup); + + if(load_state == SubRemLoadSubStateOK) { + scene_manager_previous_scene(app->scene_manager); + } else if(load_state == SubRemLoadSubStateNotSet) { + scene_manager_previous_scene(app->scene_manager); + } else { + switch(load_state) { + case SubRemLoadSubStateErrorFreq: + + popup_set_text(popup, "Bad frequency", 63, 30, AlignCenter, AlignBottom); + break; + case SubRemLoadSubStateErrorMod: + + popup_set_text(popup, "Bad modulation", 63, 30, AlignCenter, AlignBottom); + break; + case SubRemLoadSubStateErrorProtocol: + + popup_set_text(popup, "Unsupported protocol", 63, 30, AlignCenter, AlignBottom); + break; + + default: + break; + } + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDPopup); + } +} + +bool subrem_scene_open_sub_file_on_event(void* context, SceneManagerEvent event) { + SubGhzRemoteApp* app = context; + + if(event.type == SceneManagerEventTypeCustom && + event.event == SubRemCustomEventSceneEditOpenSubErrorPopup) { + scene_manager_previous_scene(app->scene_manager); + return true; + } + return false; +} + +void subrem_scene_open_sub_file_on_exit(void* context) { + SubGhzRemoteApp* app = context; + + popup_reset(app->popup); +} diff --git a/applications/external/subghz_remote_new/scenes/subrem_scene_openmapfile.c b/applications/external/subghz_remote_new/scenes/subrem_scene_openmapfile.c deleted file mode 100644 index 8e6d9d0dd..000000000 --- a/applications/external/subghz_remote_new/scenes/subrem_scene_openmapfile.c +++ /dev/null @@ -1,40 +0,0 @@ -#include "../subghz_remote_app_i.h" - -void subrem_scene_openmapfile_on_enter(void* context) { - SubGhzRemoteApp* app = context; - SubRemLoadMapState load_state = subrem_load_from_file(app); - - if(load_state == SubRemLoadMapStateError) { -#ifdef SUBREM_LIGHT - dialog_message_show_storage_error(app->dialogs, "Can't load\nMap file"); -#else - DialogMessage* message = dialog_message_alloc(); - - dialog_message_set_header(message, "Map File Error", 64, 8, AlignCenter, AlignCenter); - dialog_message_set_text(message, "Can't load\nMap file", 64, 32, AlignCenter, AlignCenter); - dialog_message_set_buttons(message, "Back", NULL, NULL); - dialog_message_show(app->dialogs, message); - - dialog_message_free(message); -#endif - } - if(load_state == SubRemLoadMapStateOK || load_state == SubRemLoadMapStateNotAllOK) { - } else { - // TODO: Map Preset Reset - if(!scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, SubRemSceneStart)) { - scene_manager_stop(app->scene_manager); - view_dispatcher_stop(app->view_dispatcher); - } - } -} - -bool subrem_scene_openmapfile_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void subrem_scene_openmapfile_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/subghz_remote_new/scenes/subrem_scene_start.c b/applications/external/subghz_remote_new/scenes/subrem_scene_start.c index fd2d61c48..e5a254111 100644 --- a/applications/external/subghz_remote_new/scenes/subrem_scene_start.c +++ b/applications/external/subghz_remote_new/scenes/subrem_scene_start.c @@ -13,14 +13,18 @@ void subrem_scene_start_on_enter(void* context) { SubGhzRemoteApp* app = context; Submenu* submenu = app->submenu; -#if FURI_DEBUG submenu_add_item( submenu, - "Remote_Debug", - SubmenuIndexSubRemRemoteView, + "Edit Map File", + SubmenuIndexSubRemEditMapFile, + subrem_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "New Map File", + SubmenuIndexSubRemNewMapFile, subrem_scene_start_submenu_callback, app); -#endif // submenu_add_item( // submenu, // "About", @@ -28,12 +32,10 @@ void subrem_scene_start_on_enter(void* context) { // subrem_scene_start_submenu_callback, // app); - // TODO: set scene state in subrem alloc - // submenu_set_selected_item( - // submenu, scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart)); - submenu_set_selected_item(submenu, 0); + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart)); - view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewSubmenu); + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDSubmenu); } bool subrem_scene_start_on_event(void* context, SceneManagerEvent event) { @@ -41,8 +43,19 @@ bool subrem_scene_start_on_event(void* context, SceneManagerEvent event) { SubGhzRemoteApp* app = context; bool consumed = false; - UNUSED(app); + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSubRemEditMapFile) { + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemEditMapFile); + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); + consumed = true; + } else if(event.event == SubmenuIndexSubRemNewMapFile) { + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemNewMapFile); + scene_manager_next_scene(app->scene_manager, SubRemSceneEnterNewName); + consumed = true; + } // } else if(event.event == SubmenuIndexSubRemAbout) { // scene_manager_next_scene(app->scene_manager, SubRemSceneAbout); // consumed = true; diff --git a/applications/external/subghz_remote_new/subghz_remote_app.c b/applications/external/subghz_remote_new/subghz_remote_app.c index b957fb8bc..e026fe4de 100644 --- a/applications/external/subghz_remote_new/subghz_remote_app.c +++ b/applications/external/subghz_remote_new/subghz_remote_app.c @@ -1,7 +1,5 @@ #include "subghz_remote_app_i.h" -#include - static bool subghz_remote_app_custom_event_callback(void* context, uint32_t event) { furi_assert(context); SubGhzRemoteApp* app = context; @@ -23,6 +21,14 @@ static void subghz_remote_app_tick_event_callback(void* context) { SubGhzRemoteApp* subghz_remote_app_alloc() { SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp)); + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_common_migrate(storage, EXT_PATH("unirf"), SUBREM_APP_FOLDER); + + if(!storage_simply_mkdir(storage, SUBREM_APP_FOLDER)) { + //FURI_LOG_E(TAG, "Could not create folder %s", SUBREM_APP_FOLDER); + } + furi_record_close(RECORD_STORAGE); + // Enable power for External CC1101 if it is connected furi_hal_subghz_enable_ext_power(); // Auto switch to internal radio if external radio is not available @@ -62,11 +68,25 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { // SubMenu app->submenu = submenu_alloc(); view_dispatcher_add_view( - app->view_dispatcher, SubRemViewSubmenu, submenu_get_view(app->submenu)); + app->view_dispatcher, SubRemViewIDSubmenu, submenu_get_view(app->submenu)); - //Dialog + // Dialog app->dialogs = furi_record_open(RECORD_DIALOGS); + // TextInput + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubRemViewIDTextInput, text_input_get_view(app->text_input)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubRemViewIDWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, SubRemViewIDPopup, popup_get_view(app->popup)); + // Remote view app->subrem_remote_view = subrem_view_remote_alloc(); view_dispatcher_add_view( @@ -74,34 +94,30 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { SubRemViewIDRemote, subrem_view_remote_get_view(app->subrem_remote_view)); + // Edit Menu view + app->subrem_edit_menu = subrem_view_edit_menu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubRemViewIDEditMenu, + subrem_view_edit_menu_get_view(app->subrem_edit_menu)); + + app->map_preset = malloc(sizeof(SubRemMapPreset)); for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - app->subs_preset[i] = subrem_sub_file_preset_alloc(); + app->map_preset->subs_preset[i] = subrem_sub_file_preset_alloc(); } - app->setting = subghz_setting_alloc(); - subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user")); + app->txrx = subghz_txrx_alloc(); - app->environment = subghz_environment_alloc(); + subghz_txrx_set_need_save_callback(app->txrx, subrem_save_active_sub, app); - subghz_environment_load_keystore(app->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore( - app->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); - subghz_environment_set_came_atomo_rainbow_table_file_name( - app->environment, EXT_PATH("subghz/assets/came_atomo")); - subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - app->environment, EXT_PATH("subghz/assets/alutech_at_4n")); - subghz_environment_set_nice_flor_s_rainbow_table_file_name( - app->environment, EXT_PATH("subghz/assets/nice_flor_s")); - subghz_environment_set_protocol_registry(app->environment, (void*)&subghz_protocol_registry); - - app->receiver = subghz_receiver_alloc_init(app->environment); - - app->tx_running = false; + app->map_not_saved = false; #ifdef SUBREM_LIGHT scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); #else scene_manager_next_scene(app->scene_manager, SubRemSceneStart); + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemEditMapFile); #endif return app; @@ -118,26 +134,41 @@ void subghz_remote_app_free(SubGhzRemoteApp* app) { furi_hal_subghz_init_radio_type(SubGhzRadioInternal); // Submenu - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewSubmenu); + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDSubmenu); submenu_free(app->submenu); - //Dialog + // Dialog furi_record_close(RECORD_DIALOGS); + // TextInput + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDTextInput); + text_input_free(app->text_input); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDWidget); + widget_free(app->widget); + + // Popup + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDPopup); + popup_free(app->popup); + // Remote view view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDRemote); subrem_view_remote_free(app->subrem_remote_view); + // Edit view + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDEditMenu); + subrem_view_edit_menu_free(app->subrem_edit_menu); + scene_manager_free(app->scene_manager); view_dispatcher_free(app->view_dispatcher); - subghz_receiver_free(app->receiver); - subghz_environment_free(app->environment); - subghz_setting_free(app->setting); + subghz_txrx_free(app->txrx); for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - subrem_sub_file_preset_free(app->subs_preset[i]); + subrem_sub_file_preset_free(app->map_preset->subs_preset[i]); } + free(app->map_preset); // Notifications furi_record_close(RECORD_NOTIFICATION); diff --git a/applications/external/subghz_remote_new/subghz_remote_app_i.c b/applications/external/subghz_remote_new/subghz_remote_app_i.c index aa8983951..1eb64541f 100644 --- a/applications/external/subghz_remote_new/subghz_remote_app_i.c +++ b/applications/external/subghz_remote_new/subghz_remote_app_i.c @@ -2,12 +2,13 @@ #include #include -#include +#include "helpers/txrx/subghz_txrx.h" // #include // #include -#include +// #include +// #include #define TAG "SubGhzRemote" @@ -19,280 +20,46 @@ static const char* map_file_labels[SubRemSubKeyNameMaxCount][2] = { [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; - sub_preset->load_state = SubRemLoadSubStateNotSet; - - 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; - sub_preset->load_state = SubRemLoadSubStateNotSet; -} - -void subrem_map_preset_reset(SubGhzRemoteApp* app) { - furi_assert(app); +void subrem_map_preset_reset(SubRemMapPreset* map_preset) { + furi_assert(map_preset); for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - subrem_sub_file_preset_reset(app->subs_preset[i]); + subrem_sub_file_preset_reset(map_preset->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 { - // Preload seccesful - 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; - sub_preset->load_state = SubRemLoadSubStatePreloaded; - } - flipper_format_rewind(fff_data_file); - } - return ret; -} +static SubRemLoadMapState subrem_map_preset_check( + SubRemMapPreset* map_preset, + SubGhzTxRx* txrx, + FlipperFormat* fff_data_file) { + furi_assert(map_preset); + furi_assert(txrx); -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; - uint32_t repeat = 200; - 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); - - if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { - FURI_LOG_E(TAG, "Unable Repeat"); - break; - } - - saved = true; - } while(0); - - furi_string_free(file_dir); - furi_record_close(RECORD_STORAGE); - return saved; -} - -static SubRemLoadMapState - subrem_map_preset_check(SubGhzRemoteApp* app, FlipperFormat* fff_data_file) { - furi_assert(app); - FuriString* temp_str = furi_string_alloc(); - uint32_t temp_data32; bool all_loaded = true; SubRemLoadMapState ret = SubRemLoadMapStateErrorBrokenFile; - SubRemLoadSubState sub_preset_loaded; + + SubRemLoadSubState sub_loadig_state; SubRemSubFilePreset* sub_preset; - uint32_t repeat = 200; + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - sub_preset = app->subs_preset[i]; + sub_preset = map_preset->subs_preset[i]; + + sub_loadig_state = SubRemLoadSubStateErrorNoFile; + if(furi_string_empty(sub_preset->file_path)) { // FURI_LOG_I(TAG, "Empty file path"); - continue; + } else if(!flipper_format_file_open_existing( + fff_data_file, furi_string_get_cstr(sub_preset->file_path))) { + sub_preset->load_state = SubRemLoadSubStateErrorNoFile; + FURI_LOG_W(TAG, "Error open file %s", furi_string_get_cstr(sub_preset->file_path)); + } else { + sub_loadig_state = subrem_sub_preset_load(sub_preset, txrx, fff_data_file); } - sub_preset_loaded = SubRemLoadSubStateErrorNoFile; - - repeat = 200; - 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 - sub_preset_loaded = SubRemLoadSubStateErrorFreq; - 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 - sub_preset_loaded = SubRemLoadSubStateErrorMod; - 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 - sub_preset_loaded = SubRemLoadSubStateErrorProtocol; - 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"); - } - - if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &repeat, 1)) { - FURI_LOG_E(TAG, "Unable Repeat"); - break; - } - - sub_preset_loaded = SubRemLoadSubStateOK; - ret = SubRemLoadMapStateNotAllOK; - -#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 - // Move to remote scene - - if(sub_preset_loaded != SubRemLoadSubStateOK) { - furi_string_set_str(sub_preset->label, "N/A"); + if(sub_loadig_state != SubRemLoadSubStateOK) { all_loaded = false; + } else { + ret = SubRemLoadMapStateNotAllOK; } if(ret != SubRemLoadMapStateErrorBrokenFile && all_loaded) { @@ -301,11 +68,52 @@ static SubRemLoadMapState flipper_format_file_close(fff_data_file); } - furi_string_free(temp_str); return ret; } +static bool subrem_map_preset_load(SubRemMapPreset* map_preset, FlipperFormat* fff_data_file) { + furi_assert(map_preset); + bool ret = false; + SubRemSubFilePreset* sub_preset; + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = map_preset->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(!path_contains_only_ascii(furi_string_get_cstr(sub_preset->file_path))) { + FURI_LOG_E(TAG, "Incorrect characters in [%s] file path", map_file_labels[i][0]); + 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 + ret = true; + } else { + ret = true; + } + if(ret) { + // Preload seccesful + 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)); + sub_preset->load_state = SubRemLoadSubStatePreloaded; + } + + flipper_format_rewind(fff_data_file); + } + return ret; +} + SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path) { furi_assert(app); furi_assert(file_path); @@ -318,19 +126,19 @@ SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_p #if FURI_DEBUG FURI_LOG_I(TAG, "Open Map File.."); #endif - subrem_map_preset_reset(app); + subrem_map_preset_reset(app->map_preset); if(!flipper_format_file_open_existing(fff_data_file, file_path)) { FURI_LOG_E(TAG, "Could not open MAP file %s", file_path); ret = SubRemLoadMapStateErrorOpenError; } else { - if(!subrem_map_preset_load(app, fff_data_file)) { + if(!subrem_map_preset_load(app->map_preset, 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)) { ret = SubRemLoadMapStateErrorOpenError; } else { - ret = subrem_map_preset_check(app, fff_data_file); + ret = subrem_map_preset_check(app->map_preset, app->txrx, fff_data_file); } } @@ -349,6 +157,53 @@ SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_p return ret; } +bool subrem_save_protocol_to_file(FlipperFormat* flipper_format, const char* sub_file_name) { + furi_assert(flipper_format); + furi_assert(sub_file_name); + + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format); + + bool saved = false; + uint32_t repeat = 200; + FuriString* file_dir = furi_string_alloc(); + + path_extract_dirname(sub_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, sub_file_name)) { + break; + } + + //ToDo check Write + stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); + stream_save_to_file(flipper_format_stream, storage, sub_file_name, FSOM_CREATE_ALWAYS); + + if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { + FURI_LOG_E(TAG, "Unable Repeat"); + break; + } + + saved = true; + } while(0); + + furi_string_free(file_dir); + furi_record_close(RECORD_STORAGE); + return saved; +} + +void subrem_save_active_sub(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + + SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chusen_sub]; + subrem_save_protocol_to_file( + sub_preset->fff_data, furi_string_get_cstr(sub_preset->file_path)); +} + SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { furi_assert(app); @@ -356,7 +211,7 @@ SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { SubRemLoadMapState ret = SubRemLoadMapStateBack; DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, SUBREM_APP_EXTENSION, &I_sub1_10px); + dialog_file_browser_set_basic_options(&browser_options, SUBREM_APP_EXTENSION, &I_subrem_10px); browser_options.base_path = SUBREM_APP_FOLDER; // Input events and views are managed by file_select @@ -369,3 +224,47 @@ SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { return ret; } + +bool subrem_save_map_to_file(SubGhzRemoteApp* app) { + furi_assert(app); + + const char* file_name = furi_string_get_cstr(app->file_path); + bool saved = false; + FlipperFormat* fff_data = flipper_format_string_alloc(); + + SubRemSubFilePreset* sub_preset; + + flipper_format_write_header_cstr( + fff_data, SUBREM_APP_APP_FILE_TYPE, SUBREM_APP_APP_FILE_VERSION); + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->map_preset->subs_preset[i]; + if(!furi_string_empty(sub_preset->file_path)) { + flipper_format_write_string(fff_data, map_file_labels[i][0], sub_preset->file_path); + } + } + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->map_preset->subs_preset[i]; + if(!furi_string_empty(sub_preset->file_path)) { + flipper_format_write_string(fff_data, map_file_labels[i][1], sub_preset->label); + } + } + + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* flipper_format_stream = flipper_format_get_raw_stream(fff_data); + + do { + if(!storage_simply_remove(storage, file_name)) { + break; + } + //ToDo check Write + stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); + stream_save_to_file(flipper_format_stream, storage, file_name, FSOM_CREATE_ALWAYS); + + saved = true; + } while(0); + + furi_record_close(RECORD_STORAGE); + flipper_format_free(fff_data); + + return saved; +} \ No newline at end of file diff --git a/applications/external/subghz_remote_new/subghz_remote_app_i.h b/applications/external/subghz_remote_new/subghz_remote_app_i.h index 1bcb79d8f..7a07d768a 100644 --- a/applications/external/subghz_remote_new/subghz_remote_app_i.h +++ b/applications/external/subghz_remote_new/subghz_remote_app_i.h @@ -1,29 +1,28 @@ #pragma once #include "helpers/subrem_types.h" +#include "helpers/subrem_presets.h" +#include "scenes/subrem_scene.h" + +#include "helpers/txrx/subghz_txrx.h" #include -// #include - #include "views/remote.h" - -#include "scenes/subrem_scene.h" +#include "views/edit_menu.h" #include #include #include #include #include -#include #include -#include -#include #include +#include +#include +#include #include -#include - #include #include #include @@ -31,48 +30,38 @@ #define SUBREM_APP_FOLDER EXT_PATH("subghz_remote") #define SUBREM_MAX_LEN_NAME 64 -typedef struct { - uint32_t frequency; - uint8_t* data; -} FreqPreset; - -// Sub File preset -typedef struct { - FlipperFormat* fff_data; - FreqPreset freq_preset; - FuriString* file_path; - FuriString* protocaol_name; - FuriString* label; - SubGhzProtocolType type; - SubRemLoadSubState load_state; -} SubRemSubFilePreset; - -SubRemSubFilePreset* subrem_sub_file_preset_alloc(); - -void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset); - typedef struct { Gui* gui; ViewDispatcher* view_dispatcher; SceneManager* scene_manager; NotificationApp* notifications; DialogsApp* dialogs; + Widget* widget; + Popup* popup; + TextInput* text_input; Submenu* submenu; + FuriString* file_path; char file_name_tmp[SUBREM_MAX_LEN_NAME]; SubRemViewRemote* subrem_remote_view; + SubRemViewEditMenu* subrem_edit_menu; - SubRemSubFilePreset* subs_preset[SubRemSubKeyNameMaxCount]; + SubRemMapPreset* map_preset; - SubGhzSetting* setting; - SubGhzEnvironment* environment; - SubGhzReceiver* receiver; - SubGhzTransmitter* transmitter; + SubGhzTxRx* txrx; - bool tx_running; + bool map_not_saved; uint8_t chusen_sub; } SubGhzRemoteApp; SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app); + +SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path); + +void subrem_map_preset_reset(SubRemMapPreset* map_preset); + +bool subrem_save_map_to_file(SubGhzRemoteApp* app); + +void subrem_save_active_sub(void* context); \ No newline at end of file diff --git a/applications/external/subghz_remote_new/views/edit_menu.c b/applications/external/subghz_remote_new/views/edit_menu.c new file mode 100644 index 000000000..9b88182b5 --- /dev/null +++ b/applications/external/subghz_remote_new/views/edit_menu.c @@ -0,0 +1,290 @@ +#include "edit_menu.h" +#include "../subghz_remote_app_i.h" + +#include +#include + +#define subrem_view_edit_menu_MAX_LABEL_LENGTH 12 + +#define FRAME_HEIGHT 12 + +struct SubRemViewEditMenu { + View* view; + SubRemViewEditMenuCallback callback; + void* context; +}; + +typedef struct { + FuriString* label; + FuriString* file_path; + SubRemLoadSubState sub_state; + + uint8_t chusen; +} SubRemViewEditMenuModel; + +void subrem_view_edit_menu_set_callback( + SubRemViewEditMenu* subrem_view_edit_menu, + SubRemViewEditMenuCallback callback, + void* context) { + furi_assert(subrem_view_edit_menu); + + subrem_view_edit_menu->callback = callback; + subrem_view_edit_menu->context = context; +} + +void subrem_view_edit_menu_add_data_to_show( + SubRemViewEditMenu* subrem_view_edit_remote, + uint8_t index, + FuriString* label, + FuriString* path, + SubRemLoadSubState state) { + furi_assert(subrem_view_edit_remote); + + with_view_model( + subrem_view_edit_remote->view, + SubRemViewEditMenuModel * model, + { + model->chusen = index; + if(!furi_string_empty(label)) { + furi_string_set(model->label, label); + } else { + furi_string_set(model->label, "Empty label"); + } + furi_string_set(model->file_path, path); + model->sub_state = state; + }, + true); +} + +uint8_t subrem_view_edit_menu_get_index(SubRemViewEditMenu* subrem_view_edit_remote) { + furi_assert(subrem_view_edit_remote); + uint8_t index; + + with_view_model( + subrem_view_edit_remote->view, + SubRemViewEditMenuModel * model, + { index = model->chusen; }, + true); + return index; +} + +void subrem_view_edit_menu_draw(Canvas* canvas, SubRemViewEditMenuModel* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_clear(canvas); + + // Draw bottom btn + canvas_set_font(canvas, FontSecondary); + elements_button_left(canvas, "Back"); + elements_button_center(canvas, "Edit"); + elements_button_right(canvas, "Save"); + + // Draw top frame + canvas_draw_line(canvas, 1, 0, 125, 0); + canvas_draw_box(canvas, 0, 1, 127, FRAME_HEIGHT - 2); + canvas_draw_line(canvas, 1, FRAME_HEIGHT - 1, 125, FRAME_HEIGHT - 1); + + canvas_set_color(canvas, ColorWhite); + + // Draw btn name + canvas_set_font(canvas, FontPrimary); + switch(model->chusen) { + case SubRemSubKeyNameUp: + canvas_draw_str(canvas, 3, FRAME_HEIGHT - 2, "UP"); + break; + + case SubRemSubKeyNameDown: + canvas_draw_str(canvas, 3, FRAME_HEIGHT - 2, "DOWN"); + break; + + case SubRemSubKeyNameLeft: + canvas_draw_str(canvas, 3, FRAME_HEIGHT - 2, "LEFT"); + break; + + case SubRemSubKeyNameRight: + canvas_draw_str(canvas, 3, FRAME_HEIGHT - 2, "RIGHT"); + break; + + case SubRemSubKeyNameOk: + canvas_draw_str(canvas, 3, FRAME_HEIGHT - 2, "OK"); + break; + + default: + break; + } + + // Draw Label + canvas_set_font(canvas, FontSecondary); + elements_text_box( + canvas, + 38, + 2, + 127 - 38, + FRAME_HEIGHT, + AlignCenter, + AlignBottom, + furi_string_empty(model->label) ? "Empty label" : furi_string_get_cstr(model->label), + true); + + // Draw arrow + canvas_set_color(canvas, ColorBlack); + if(model->chusen != 0) { + canvas_draw_icon(canvas, 119, 13, &I_Pin_arrow_up_7x9); + } + if(model->chusen != 4) { + canvas_draw_icon_ex(canvas, 119, 42, &I_Pin_arrow_up_7x9, IconRotation180); + } + + // Draw file_path + if(model->sub_state == SubRemLoadSubStateOK) { + canvas_set_font(canvas, FontSecondary); + elements_text_box( + canvas, + 1, + FRAME_HEIGHT + 1, + 118, + (63 - FRAME_HEIGHT * 2), + AlignLeft, + AlignTop, + furi_string_get_cstr(model->file_path), + false); + } else if(furi_string_empty(model->file_path)) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 1, FRAME_HEIGHT * 2 - 2, "Button not set"); + } else { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 1, FRAME_HEIGHT * 2 - 2, "ERR:"); + canvas_set_font(canvas, FontSecondary); + switch(model->sub_state) { + case SubRemLoadSubStateErrorNoFile: + canvas_draw_str(canvas, 26, FRAME_HEIGHT * 2 - 2, "File not found"); + break; + case SubRemLoadSubStateErrorFreq: + canvas_draw_str(canvas, 26, FRAME_HEIGHT * 2 - 2, "Bad frequency"); + break; + case SubRemLoadSubStateErrorMod: + canvas_draw_str(canvas, 26, FRAME_HEIGHT * 2 - 2, "Bad modulation"); + break; + case SubRemLoadSubStateErrorProtocol: + canvas_draw_str(canvas, 26, FRAME_HEIGHT * 2 - 2, "Unsupported protocol"); + break; + + default: + break; + } + elements_text_box( + canvas, + 1, + FRAME_HEIGHT * 2, + 118, + 30, + AlignLeft, + AlignTop, + furi_string_get_cstr(model->file_path), + false); + } +} + +bool subrem_view_edit_menu_input(InputEvent* event, void* context) { + furi_assert(context); + SubRemViewEditMenu* subrem_view_edit_menu = context; + + if((event->key == InputKeyBack || event->key == InputKeyLeft) && + event->type == InputTypeShort) { + subrem_view_edit_menu->callback( + SubRemCustomEventViewEditMenuBack, subrem_view_edit_menu->context); + return true; + } else if(event->key == InputKeyUp && event->type == InputTypeShort) { + with_view_model( + subrem_view_edit_menu->view, + SubRemViewEditMenuModel * model, + { + if(model->chusen > 0) { + model->chusen -= 1; + }; + }, + true); + subrem_view_edit_menu->callback( + SubRemCustomEventViewEditMenuUP, subrem_view_edit_menu->context); + return true; + } else if(event->key == InputKeyDown && event->type == InputTypeShort) { + with_view_model( + subrem_view_edit_menu->view, + SubRemViewEditMenuModel * model, + { + if(model->chusen < 4) { + model->chusen += 1; + }; + }, + true); + subrem_view_edit_menu->callback( + SubRemCustomEventViewEditMenuDOWN, subrem_view_edit_menu->context); + return true; + } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + subrem_view_edit_menu->callback( + SubRemCustomEventViewEditMenuEdit, subrem_view_edit_menu->context); + return true; + } else if(event->key == InputKeyRight && event->type == InputTypeShort) { + subrem_view_edit_menu->callback( + SubRemCustomEventViewEditMenuSave, subrem_view_edit_menu->context); + return true; + } + + return true; +} + +void subrem_view_edit_menu_enter(void* context) { + furi_assert(context); +} + +void subrem_view_edit_menu_exit(void* context) { + furi_assert(context); +} + +SubRemViewEditMenu* subrem_view_edit_menu_alloc() { + SubRemViewEditMenu* subrem_view_edit_menu = malloc(sizeof(SubRemViewEditMenu)); + + // View allocation and configuration + subrem_view_edit_menu->view = view_alloc(); + view_allocate_model( + subrem_view_edit_menu->view, ViewModelTypeLocking, sizeof(SubRemViewEditMenuModel)); + view_set_context(subrem_view_edit_menu->view, subrem_view_edit_menu); + view_set_draw_callback( + subrem_view_edit_menu->view, (ViewDrawCallback)subrem_view_edit_menu_draw); + view_set_input_callback(subrem_view_edit_menu->view, subrem_view_edit_menu_input); + view_set_enter_callback(subrem_view_edit_menu->view, subrem_view_edit_menu_enter); + view_set_exit_callback(subrem_view_edit_menu->view, subrem_view_edit_menu_exit); + + with_view_model( + subrem_view_edit_menu->view, + SubRemViewEditMenuModel * model, + { + model->label = furi_string_alloc(); // furi_string_alloc_set_str("LABEL"); + model->file_path = furi_string_alloc(); // furi_string_alloc_set_str("FILE_PATH"); + + model->chusen = 0; + }, + true); + return subrem_view_edit_menu; +} + +void subrem_view_edit_menu_free(SubRemViewEditMenu* subghz_edit_menu) { + furi_assert(subghz_edit_menu); + + with_view_model( + subghz_edit_menu->view, + SubRemViewEditMenuModel * model, + { + furi_string_free(model->label); + furi_string_free(model->file_path); + }, + true); + view_free(subghz_edit_menu->view); + free(subghz_edit_menu); +} + +View* subrem_view_edit_menu_get_view(SubRemViewEditMenu* subrem_view_edit_menu) { + furi_assert(subrem_view_edit_menu); + return subrem_view_edit_menu->view; +} \ No newline at end of file diff --git a/applications/external/subghz_remote_new/views/edit_menu.h b/applications/external/subghz_remote_new/views/edit_menu.h new file mode 100644 index 000000000..7ceb7fac0 --- /dev/null +++ b/applications/external/subghz_remote_new/views/edit_menu.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include "../helpers/subrem_custom_event.h" +#include "../helpers/subrem_presets.h" + +typedef struct SubRemViewEditMenu SubRemViewEditMenu; + +typedef void (*SubRemViewEditMenuCallback)(SubRemCustomEvent event, void* context); + +void subrem_view_edit_menu_set_callback( + SubRemViewEditMenu* subrem_view_edit_menu, + SubRemViewEditMenuCallback callback, + void* context); + +SubRemViewEditMenu* subrem_view_edit_menu_alloc(); + +void subrem_view_edit_menu_free(SubRemViewEditMenu* subrem_view_edit_menu); + +View* subrem_view_edit_menu_get_view(SubRemViewEditMenu* subrem_view_edit_menu); + +void subrem_view_edit_menu_add_data_to_show( + SubRemViewEditMenu* subrem_view_edit_remote, + uint8_t index, + FuriString* label, + FuriString* path, + SubRemLoadSubState state); + +uint8_t subrem_view_edit_menu_get_index(SubRemViewEditMenu* subrem_view_edit_remote); \ No newline at end of file diff --git a/applications/external/subghz_remote_new/views/remote.c b/applications/external/subghz_remote_new/views/remote.c index 1f867cd0b..c2b41cfd6 100644 --- a/applications/external/subghz_remote_new/views/remote.c +++ b/applications/external/subghz_remote_new/views/remote.c @@ -4,7 +4,11 @@ #include #include -#define SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH 12 +#include + +#define SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH 30 +#define SUBREM_VIEW_REMOTE_LEFT_OFFSET 10 +#define SUBREM_VIEW_REMOTE_RIGHT_OFFSET 22 struct SubRemViewRemote { View* view; @@ -12,19 +16,8 @@ struct SubRemViewRemote { void* context; }; -// TODO: model typedef struct { - // FuriString* up_label; - // FuriString* down_label; - // FuriString* left_label; - // FuriString* right_label; - // FuriString* ok_label; - - char* up_label; - char* down_label; - char* left_label; - char* right_label; - char* ok_label; + char* labels[SubRemSubKeyNameMaxCount]; SubRemViewRemoteState state; @@ -41,49 +34,76 @@ void subrem_view_remote_set_callback( subrem_view_remote->context = context; } -void subrem_view_remote_add_data_to_show( +void subrem_view_remote_update_data_labels( SubRemViewRemote* subrem_view_remote, - const char* up_label, - const char* down_label, - const char* left_label, - const char* right_label, - const char* ok_label) { + SubRemSubFilePreset** subs_presets) { furi_assert(subrem_view_remote); + furi_assert(subs_presets); + + FuriString* labels[SubRemSubKeyNameMaxCount]; + SubRemSubFilePreset* sub_preset; + + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = subs_presets[i]; + switch(sub_preset->load_state) { + case SubRemLoadSubStateOK: + if(!furi_string_empty(sub_preset->label)) { + labels[i] = furi_string_alloc_set(sub_preset->label); + } else if(!furi_string_empty(sub_preset->file_path)) { + labels[i] = furi_string_alloc(); + path_extract_filename(sub_preset->file_path, labels[i], true); + } else { + labels[i] = furi_string_alloc_set("Empty Label"); + } + break; + + case SubRemLoadSubStateErrorNoFile: + labels[i] = furi_string_alloc_set("[X] Can't open file"); + break; + + case SubRemLoadSubStateErrorFreq: + case SubRemLoadSubStateErrorMod: + case SubRemLoadSubStateErrorProtocol: + labels[i] = furi_string_alloc_set("[X] Error in .sub file"); + break; + + default: + labels[i] = furi_string_alloc_set(""); + break; + } + } with_view_model( subrem_view_remote->view, SubRemViewRemoteModel * model, { - strncpy(model->up_label, up_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); - strncpy(model->down_label, down_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); - strncpy(model->left_label, left_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); - strncpy(model->right_label, right_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); - strncpy(model->ok_label, ok_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); - - // furi_string_set(model->up_label, up_label); - // furi_string_set(model->down_label, down_label); - // furi_string_set(model->left_label, left_label); - // furi_string_set(model->right_label, right_label); - // furi_string_set(model->ok_label, ok_label); + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + strncpy( + model->labels[i], + furi_string_get_cstr(labels[i]), + SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); + } }, true); -} -void subrem_view_remote_set_presed_btn(SubRemViewRemote* subrem_view_remote, uint8_t presed_btn) { - furi_assert(subrem_view_remote); - with_view_model( - subrem_view_remote->view, - SubRemViewRemoteModel * model, - { model->pressed_btn = presed_btn; }, - true); + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + furi_string_free(labels[i]); + } } void subrem_view_remote_set_state( SubRemViewRemote* subrem_view_remote, - SubRemViewRemoteState state) { + SubRemViewRemoteState state, + uint8_t presed_btn) { furi_assert(subrem_view_remote); with_view_model( - subrem_view_remote->view, SubRemViewRemoteModel * model, { model->state = state; }, true); + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { + model->state = state; + model->pressed_btn = presed_btn; + }, + true); } void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { @@ -103,24 +123,32 @@ void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { //Labels canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 10, 10, model->up_label); - canvas_draw_str(canvas, 10, 20, model->down_label); - canvas_draw_str(canvas, 10, 30, model->left_label); - canvas_draw_str(canvas, 10, 40, model->right_label); - canvas_draw_str(canvas, 10, 50, model->ok_label); + uint8_t y = 0; + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + elements_text_box( + canvas, + SUBREM_VIEW_REMOTE_LEFT_OFFSET, + y + 2, + 126 - SUBREM_VIEW_REMOTE_LEFT_OFFSET - SUBREM_VIEW_REMOTE_RIGHT_OFFSET, + 12, + AlignLeft, + AlignBottom, + model->labels[i], + false); + y += 10; + } - // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); - // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); - // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); - // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); - // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); - - canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Hold=Exit."); + if(model->state == SubRemViewRemoteStateOFF) { + elements_button_left(canvas, "Back"); + elements_button_right(canvas, "Save"); + } else { + canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Hold=Exit."); + } //Status text and indicator canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - if(model->state == SubRemViewRemoteStateIdle) { + if(model->state == SubRemViewRemoteStateIdle || model->state == SubRemViewRemoteStateOFF) { canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Idle"); } else { switch(model->state) { @@ -155,10 +183,6 @@ void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { break; } } - //Repeat indicator - //canvas_draw_str_aligned(canvas, 125, 40, AlignRight, AlignBottom, "Repeat:"); - //canvas_draw_icon(canvas, 115, 39, &I_SubGHzRemote_Repeat_12x14); - //canvas_draw_str_aligned(canvas, 125, 62, AlignRight, AlignBottom, int_to_char(app->repeat)); } bool subrem_view_remote_input(InputEvent* event, void* context) { @@ -166,17 +190,6 @@ bool subrem_view_remote_input(InputEvent* event, void* context) { SubRemViewRemote* subrem_view_remote = context; if(event->key == InputKeyBack && event->type == InputTypeLong) { - with_view_model( - subrem_view_remote->view, - SubRemViewRemoteModel * model, - { - strcpy(model->up_label, "N/A"); - strcpy(model->down_label, "N/A"); - strcpy(model->left_label, "N/A"); - strcpy(model->right_label, "N/A"); - strcpy(model->ok_label, "N/A"); - }, - false); subrem_view_remote->callback(SubRemCustomEventViewRemoteBack, subrem_view_remote->context); return true; } else if(event->key == InputKeyBack && event->type == InputTypeShort) { @@ -248,23 +261,10 @@ SubRemViewRemote* subrem_view_remote_alloc() { { model->state = SubRemViewRemoteStateIdle; - model->up_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); - model->down_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); - model->left_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); - model->right_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); - model->ok_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); - - strcpy(model->up_label, "N/A"); - strcpy(model->down_label, "N/A"); - strcpy(model->left_label, "N/A"); - strcpy(model->right_label, "N/A"); - strcpy(model->ok_label, "N/A"); - - // model->up_label = furi_string_alloc_set_str("N/A"); - // model->down_label = furi_string_alloc_set_str("N/A"); - // model->left_label = furi_string_alloc_set_str("N/A"); - // model->right_label = furi_string_alloc_set_str("N/A"); - // model->ok_label = furi_string_alloc_set_str("N/A"); + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + model->labels[i] = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); + strcpy(model->labels[i], ""); + } model->pressed_btn = 0; }, @@ -279,17 +279,9 @@ void subrem_view_remote_free(SubRemViewRemote* subghz_remote) { subghz_remote->view, SubRemViewRemoteModel * model, { - free(model->up_label); - free(model->down_label); - free(model->left_label); - free(model->right_label); - free(model->ok_label); - - // furi_string_free(model->up_label); - // furi_string_free(model->down_label); - // furi_string_free(model->left_label); - // furi_string_free(model->right_label); - // furi_string_free(model->ok_label); + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + free(model->labels[i]); + } }, true); view_free(subghz_remote->view); diff --git a/applications/external/subghz_remote_new/views/remote.h b/applications/external/subghz_remote_new/views/remote.h index 76121cf8a..5b1e8153a 100644 --- a/applications/external/subghz_remote_new/views/remote.h +++ b/applications/external/subghz_remote_new/views/remote.h @@ -2,11 +2,13 @@ #include #include "../helpers/subrem_custom_event.h" +#include "../helpers/subrem_presets.h" typedef enum { SubRemViewRemoteStateIdle, SubRemViewRemoteStateLoading, SubRemViewRemoteStateSending, + SubRemViewRemoteStateOFF, } SubRemViewRemoteState; typedef struct SubRemViewRemote SubRemViewRemote; @@ -24,15 +26,11 @@ void subrem_view_remote_free(SubRemViewRemote* subrem_view_remote); View* subrem_view_remote_get_view(SubRemViewRemote* subrem_view_remote); -void subrem_view_remote_add_data_to_show( +void subrem_view_remote_update_data_labels( SubRemViewRemote* subrem_view_remote, - const char* up_label, - const char* down_label, - const char* left_label, - const char* right_label, - const char* ok_label); + SubRemSubFilePreset** subs_presets); -void subrem_view_remote_set_presed_btn(SubRemViewRemote* subrem_view_remote, uint8_t presed_btn); void subrem_view_remote_set_state( SubRemViewRemote* subrem_view_remote, - SubRemViewRemoteState state); \ No newline at end of file + SubRemViewRemoteState state, + uint8_t presed_btn); \ No newline at end of file