diff --git a/applications/main/subghz_remote/application.fam b/applications/main/subghz_remote/application.fam index e09f8500f..e785043e6 100644 --- a/applications/main/subghz_remote/application.fam +++ b/applications/main/subghz_remote/application.fam @@ -3,7 +3,10 @@ App( name="Sub-GHz Remote", apptype=FlipperAppType.APP, entry_point="subghz_remote_app", - cdefines=["APP_SUBGHZREMOTE"], + cdefines=[ + "APP_SUBGHZREMOTE", + "SUBREM_LIGHT", + ], requires=[ "gui", "dialogs", @@ -11,4 +14,4 @@ App( icon="A_SubGHzRemote_14", stack_size=4 * 1024, order=11, -) +) \ No newline at end of file diff --git a/applications/main/subghz_remote/helpers/subrem_custom_event.h b/applications/main/subghz_remote/helpers/subrem_custom_event.h new file mode 100644 index 000000000..46ab8ad54 --- /dev/null +++ b/applications/main/subghz_remote/helpers/subrem_custom_event.h @@ -0,0 +1,18 @@ +#pragma once + +typedef enum { + //SubmenuIndex + SubmenuIndexSubRemOpenMapFile, + SubmenuIndexSubRemRemoteView, + SubmenuIndexSubRemAbout, + + //SubRemCustomEvent + SubRemCustomEventViewRemoteStartUP = 100, + SubRemCustomEventViewRemoteStartDOWN, + SubRemCustomEventViewRemoteStartLEFT, + SubRemCustomEventViewRemoteStartRIGHT, + SubRemCustomEventViewRemoteStartOK, + SubRemCustomEventViewRemoteBack, + SubRemCustomEventViewRemoteStop, + SubRemCustomEventViewRemoteForcedStop, +} SubRemCustomEvent; \ No newline at end of file diff --git a/applications/main/subghz_remote/helpers/subrem_types.h b/applications/main/subghz_remote/helpers/subrem_types.h new file mode 100644 index 000000000..1b99aac6d --- /dev/null +++ b/applications/main/subghz_remote/helpers/subrem_types.h @@ -0,0 +1,32 @@ +#pragma once + +#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_EXTENSION ".txt" + +typedef enum { + SubRemSubKeyNameUp = (0U), + SubRemSubKeyNameDown, + SubRemSubKeyNameLeft, + SubRemSubKeyNameRight, + SubRemSubKeyNameOk, + SubRemSubKeyNameMaxCount, +} SubRemSubKeyName; + +typedef enum { + SubRemViewSubmenu, + SubRemViewWidget, + SubRemViewPopup, + SubRemViewTextInput, + SubRemViewIDRemote, +} SubRemViewID; + +typedef enum { + SubRemLoadMapStateBack = 0, + SubRemLoadMapStateError, + SubRemLoadMapStateOK, +} SubRemLoadMapState; \ No newline at end of file diff --git a/applications/main/subghz_remote/scenes/subrem_scene.c b/applications/main/subghz_remote/scenes/subrem_scene.c new file mode 100644 index 000000000..c45285b96 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene.c @@ -0,0 +1,30 @@ +#include "../subghz_remote_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const subrem_scene_on_enter_handlers[])(void*) = { +#include "subrem_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const subrem_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "subrem_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const subrem_scene_on_exit_handlers[])(void* context) = { +#include "subrem_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers subrem_scene_handlers = { + .on_enter_handlers = subrem_scene_on_enter_handlers, + .on_event_handlers = subrem_scene_on_event_handlers, + .on_exit_handlers = subrem_scene_on_exit_handlers, + .scene_num = SubRemSceneNum, +}; diff --git a/applications/main/subghz_remote/scenes/subrem_scene.h b/applications/main/subghz_remote/scenes/subrem_scene.h new file mode 100644 index 000000000..5c01f8ca5 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) SubRemScene##id, +typedef enum { +#include "subrem_scene_config.h" + SubRemSceneNum, +} SubRemScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers subrem_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "subrem_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "subrem_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "subrem_scene_config.h" +#undef ADD_SCENE diff --git a/applications/main/subghz_remote/scenes/subrem_scene_config.h b/applications/main/subghz_remote/scenes/subrem_scene_config.h new file mode 100644 index 000000000..93d4de642 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(subrem, start, Start) +ADD_SCENE(subrem, openmapfile, OpenMapFile) +ADD_SCENE(subrem, remote, Remote) \ No newline at end of file diff --git a/applications/main/subghz_remote/scenes/subrem_scene_openmapfile.c b/applications/main/subghz_remote/scenes/subrem_scene_openmapfile.c new file mode 100644 index 000000000..3391845e1 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_openmapfile.c @@ -0,0 +1,41 @@ +#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) { + scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); + } 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/main/subghz_remote/scenes/subrem_scene_remote.c b/applications/main/subghz_remote/scenes/subrem_scene_remote.c new file mode 100644 index 000000000..c24583233 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_remote.c @@ -0,0 +1,130 @@ +#include "../subghz_remote_app_i.h" +#include "../views/remote.h" + +#include + +#define TAG "SubRemScenRemote" + +void subrem_scene_remote_callback(SubRemCustomEvent event, void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subrem_scene_remote_raw_callback_end_tx(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SubRemCustomEventViewRemoteForcedStop); +} + +static uint8_t subrem_scene_remote_event_to_index(SubRemCustomEvent event_id) { + uint8_t ret = 0; + + if(event_id == SubRemCustomEventViewRemoteStartUP) { + ret = SubRemSubKeyNameUp; + } else if(event_id == SubRemCustomEventViewRemoteStartDOWN) { + ret = SubRemSubKeyNameDown; + } else if(event_id == SubRemCustomEventViewRemoteStartLEFT) { + ret = SubRemSubKeyNameLeft; + } else if(event_id == SubRemCustomEventViewRemoteStartRIGHT) { + ret = SubRemSubKeyNameRight; + } else if(event_id == SubRemCustomEventViewRemoteStartOK) { + ret = SubRemSubKeyNameOk; + } + + return ret; +} + +static bool subrem_scene_remote_update_data_show(void* context) { + SubGhzRemoteApp* app = context; + bool ret = false; + + subrem_view_remote_add_data_to_show( + app->subrem_remote_view, + furi_string_get_cstr(app->subs_preset[0]->label), + furi_string_get_cstr(app->subs_preset[1]->label), + furi_string_get_cstr(app->subs_preset[2]->label), + furi_string_get_cstr(app->subs_preset[3]->label), + furi_string_get_cstr(app->subs_preset[4]->label)); + + return ret; +} + +void subrem_scene_remote_on_enter(void* context) { + SubGhzRemoteApp* app = context; + + subrem_scene_remote_update_data_show(app); + + subrem_view_remote_set_callback(app->subrem_remote_view, subrem_scene_remote_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDRemote); +} + +bool subrem_scene_remote_on_event(void* context, SceneManagerEvent event) { + SubGhzRemoteApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubRemCustomEventViewRemoteBack) { + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SubRemSceneOpenMapFile)) { + 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 == SubRemCustomEventViewRemoteStartUP || + event.event == SubRemCustomEventViewRemoteStartDOWN || + event.event == SubRemCustomEventViewRemoteStartLEFT || + event.event == SubRemCustomEventViewRemoteStartRIGHT || + event.event == SubRemCustomEventViewRemoteStartOK) { + // Start sending sub + subrem_tx_stop_sub(app, true); + app->chusen_sub = subrem_scene_remote_event_to_index(event.event); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateLoading); + if(subrem_tx_start_sub( + app, + app->subs_preset[app->chusen_sub], + subrem_scene_remote_raw_callback_end_tx)) { + subrem_view_remote_set_presed_btn(app->subrem_remote_view, app->chusen_sub); + subrem_view_remote_set_state( + app->subrem_remote_view, SubRemViewRemoteStateSending); + notification_message(app->notifications, &sequence_blink_start_magenta); + } else { + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + notification_message(app->notifications, &sequence_blink_stop); + } + return true; + } else if(event.event == SubRemCustomEventViewRemoteForcedStop) { + subrem_tx_stop_sub(app, true); + subrem_view_remote_set_presed_btn(app->subrem_remote_view, 0); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + + notification_message(app->notifications, &sequence_blink_stop); + return true; + } else if(event.event == SubRemCustomEventViewRemoteStop) { + if(subrem_tx_stop_sub(app, false)) { + subrem_view_remote_set_presed_btn(app->subrem_remote_view, 0); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + + notification_message(app->notifications, &sequence_blink_stop); + } + return true; + } + } + // } else if(event.type == SceneManagerEventTypeTick) { + // } + return false; +} + +void subrem_scene_remote_on_exit(void* context) { + SubGhzRemoteApp* app = context; + + subrem_tx_stop_sub(app, true); + + subrem_view_remote_set_presed_btn(app->subrem_remote_view, 0); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + + notification_message(app->notifications, &sequence_blink_stop); +} diff --git a/applications/main/subghz_remote/scenes/subrem_scene_start.c b/applications/main/subghz_remote/scenes/subrem_scene_start.c new file mode 100644 index 000000000..962eda54c --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_start.c @@ -0,0 +1,74 @@ +#include "../subghz_remote_app_i.h" +#include "../helpers/subrem_custom_event.h" + +void subrem_scene_start_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_start_on_enter(void* context) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + Submenu* submenu = app->submenu; + submenu_add_item( + submenu, + "Open Map File", + SubmenuIndexSubRemOpenMapFile, + subrem_scene_start_submenu_callback, + app); +#if FURI_DEBUG + submenu_add_item( + submenu, + "Remote_Debug", + SubmenuIndexSubRemRemoteView, + subrem_scene_start_submenu_callback, + app); +#endif + // submenu_add_item( + // submenu, + // "About", + // SubmenuIndexSubGhzRemoteAbout, + // subrem_scene_start_submenu_callback, + // app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewSubmenu); +} + +bool subrem_scene_start_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSubRemOpenMapFile) { + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); + consumed = true; + } + // } else if(event.event == SubmenuIndexSubRemAbout) { + // scene_manager_next_scene(app->scene_manager, SubRemSceneAbout); + // consumed = true; + // } +#if FURI_DEBUG + else if(event.event == SubmenuIndexSubRemRemoteView) { + scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); + consumed = true; + } +#endif + } + + return consumed; +} + +void subrem_scene_start_on_exit(void* context) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/main/subghz_remote/subghz_remote_app.c b/applications/main/subghz_remote/subghz_remote_app.c index 8cef4da3a..0b3f645ab 100644 --- a/applications/main/subghz_remote/subghz_remote_app.c +++ b/applications/main/subghz_remote/subghz_remote_app.c @@ -1,766 +1,27 @@ -#include +#include "subghz_remote_app_i.h" -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include #include -#include -#include -#include -#include - -#define SUBREMOTEMAP_FOLDER "/ext/subghz_remote" -#define SUBREMOTEMAP_EXTENSION ".txt" - -#define TAG "SubGHzRemote" - -typedef struct { - uint32_t frequency; - FuriString* name; - - FuriString* protocol; - uint32_t repeat; - - uint8_t* data; - size_t data_size; - - SubGhzProtocolDecoderBase* decoder; -} SubRemotePreset; - -typedef struct { - FuriMutex* model_mutex; - - FuriMessageQueue* input_queue; - - ViewPort* view_port; - Gui* gui; - - SubGhzSetting* setting; - SubGhzEnvironment* environment; - SubGhzReceiver* subghz_receiver; - NotificationApp* notification; - SubRemotePreset* txpreset; - - FuriString* up_file; - FuriString* down_file; - FuriString* left_file; - FuriString* right_file; - FuriString* ok_file; - - FuriString* file_path; - - char* up_label; - char* down_label; - char* left_label; - char* right_label; - char* ok_label; - - int up_enabled; - int down_enabled; - int left_enabled; - int right_enabled; - int ok_enabled; - - char* send_status; - int send_status_c; - int processing; - - SubGhzTransmitter* tx_transmitter; - FlipperFormat* tx_fff_data; - const char* tx_file_path; - int button; - - int file_result; - bool tx_not_allowed; - - FuriString* signal; -} SubGHzRemote; - -SubRemotePreset* subghz_remote_preset_alloc(void) { - SubRemotePreset* preset = malloc(sizeof(SubRemotePreset)); - preset->name = furi_string_alloc(); - preset->protocol = furi_string_alloc(); - preset->repeat = 200; - return preset; +static bool subghz_remote_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + SubGhzRemoteApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); } -void subghz_remote_preset_free(SubRemotePreset* preset) { - furi_string_free(preset->name); - furi_string_free(preset->protocol); - free(preset); +static bool subghz_remote_app_back_event_callback(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); } -static char* char_to_str(char* str, int i) { - char* converted = malloc(sizeof(char) * i + 1); - memcpy(converted, str, i); - - converted[i] = '\0'; - - return converted; +static void subghz_remote_app_tick_event_callback(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); } -//get filename without path -static char* extract_filename(const char* name, int len) { - FuriString* tmp; - tmp = furi_string_alloc(); - - //remove path - path_extract_filename_no_ext(name, tmp); - - return char_to_str((char*)furi_string_get_cstr(tmp), len); -} - -static void cfg_read_file_path( - FlipperFormat* fff_file, - FuriString* text_file_path, - char** text_file_label, - const char* read_key, - int* is_enabled) { - if(!flipper_format_read_string(fff_file, read_key, text_file_path)) { - FURI_LOG_W(TAG, "Could not read %s string", read_key); - *text_file_label = "N/A"; - *is_enabled = 0; - } else { - *text_file_label = extract_filename(furi_string_get_cstr(text_file_path), 16); - //FURI_LOG_D(TAG, "%s file: %s", read_key, furi_string_get_cstr(text_file_path)); - *is_enabled = 1; - } - flipper_format_rewind(fff_file); -} - -static void cfg_read_file_label( - FlipperFormat* fff_file, - char** text_file_label, - const char* read_key, - bool is_enabled) { - FuriString* temp_label = furi_string_alloc(); - - if(!flipper_format_read_string(fff_file, read_key, temp_label)) { - FURI_LOG_W(TAG, "Could not read %s string", read_key); - } else { - if(is_enabled == 1) { - *text_file_label = char_to_str((char*)furi_string_get_cstr(temp_label), 16); - } - //FURI_LOG_D(TAG, "%s label: %s", read_key, *text_file_label); - } - flipper_format_rewind(fff_file); - furi_string_free(temp_label); -} - -/* - * check that map file exists - * assign variables to values within map file - * set missing filenames to N/A - * set filename as label if label definitions are missing - * set error flag if all buttons are N/A - * set error flag if missing map file - */ - -void subghz_remote_cfg_set_check(SubGHzRemote* app, FuriString* file_name) { - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - - app->file_result = 1; - - app->up_enabled = 0; - app->down_enabled = 0; - app->left_enabled = 0; - app->right_enabled = 0; - app->ok_enabled = 0; - - //check that map file exists - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_E(TAG, "Could not open MAP file %s", furi_string_get_cstr(file_name)); - } else { - //Filename Assignment/Check Start - - //assign variables to values within map file - //set missing filenames to N/A - cfg_read_file_path(fff_data_file, app->up_file, &app->up_label, "UP", &app->up_enabled); - - cfg_read_file_path( - fff_data_file, app->down_file, &app->down_label, "DOWN", &app->down_enabled); - - cfg_read_file_path( - fff_data_file, app->left_file, &app->left_label, "LEFT", &app->left_enabled); - - cfg_read_file_path( - fff_data_file, app->right_file, &app->right_label, "RIGHT", &app->right_enabled); - - cfg_read_file_path(fff_data_file, app->ok_file, &app->ok_label, "OK", &app->ok_enabled); - - //File definitions are done. - //File checks will follow after label assignment in order to close the universal_rf_map file without the need to reopen it again. - - //Label Assignment/Check Start - - cfg_read_file_label(fff_data_file, &app->up_label, "ULABEL", app->up_enabled); - cfg_read_file_label(fff_data_file, &app->down_label, "DLABEL", app->down_enabled); - cfg_read_file_label(fff_data_file, &app->left_label, "LLABEL", app->left_enabled); - cfg_read_file_label(fff_data_file, &app->right_label, "RLABEL", app->right_enabled); - cfg_read_file_label(fff_data_file, &app->ok_label, "OKLABEL", app->ok_enabled); - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - - //File Existence Check - //Check each file definition if not already set to "N/A" - - //determine if files exist. - //determine whether or not to continue to launch app with missing variables - //if 5 files are missing, throw error - - //if button is still enabled, check that file exists - if(app->up_enabled == 1) { - furi_string_set(file_name, app->up_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open UP file %s", furi_string_get_cstr(file_name)); - - //disable button, and set label to "N/A" - app->up_enabled = 0; - app->up_label = "N/A"; - } - - //close the file - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->down_enabled == 1) { - furi_string_set(file_name, app->down_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open DOWN file %s", furi_string_get_cstr(file_name)); - - app->down_enabled = 0; - app->down_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->left_enabled == 1) { - furi_string_set(file_name, app->left_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open LEFT file %s", furi_string_get_cstr(file_name)); - - app->left_enabled = 0; - app->left_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->right_enabled == 1) { - furi_string_set(file_name, app->right_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open RIGHT file %s", furi_string_get_cstr(file_name)); - - app->right_enabled = 0; - app->right_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->ok_enabled == 1) { - furi_string_set(file_name, app->ok_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open OK file %s", furi_string_get_cstr(file_name)); - - app->ok_enabled = 0; - app->ok_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - furi_record_close(RECORD_STORAGE); - - if(app->up_enabled == 0 && app->down_enabled == 0 && app->left_enabled == 0 && - app->right_enabled == 0 && app->ok_enabled == 0) { - app->file_result = 1; - } else { - app->file_result = 2; - } -} - -static void subghz_remote_end_send(SubGHzRemote* app) { - app->processing = 0; -} - -bool subghz_remote_set_preset(SubRemotePreset* p, const char* preset) { - if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { - furi_string_set(p->name, "AM270"); - } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { - furi_string_set(p->name, "AM650"); - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { - furi_string_set(p->name, "FM238"); - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { - furi_string_set(p->name, "FM476"); - } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { - FURI_LOG_E(TAG, "Custom preset unsupported now"); - return false; - // furi_string_set(p->name, "CUSTOM"); - } else { - FURI_LOG_E(TAG, "Unsupported preset"); - return false; - } - return true; -} - -bool subghz_remote_key_load( - SubRemotePreset* preset, - FlipperFormat* fff_file, - FlipperFormat* fff_data, - SubGhzSetting* setting, - SubGhzReceiver* receiver, - const char* path) { - // - if(!flipper_format_rewind(fff_file)) { - FURI_LOG_E(TAG, "Rewind error"); - return false; - } - - FuriString* temp_str; - temp_str = furi_string_alloc(); - - bool res = false; - - subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK); - keeloq_reset_original_btn(); - subghz_custom_btns_reset(); - - do { - // load frequency from file - if(!flipper_format_read_uint32(fff_file, "Frequency", &preset->frequency, 1)) { - FURI_LOG_W(TAG, "Cannot read frequency. Defaulting to 433.92 MHz"); - preset->frequency = 433920000; - } - - // load preset from file - if(!flipper_format_read_string(fff_file, "Preset", temp_str)) { - FURI_LOG_W(TAG, "Could not read Preset. Defaulting to Ook650Async"); - furi_string_set(temp_str, "FuriHalSubGhzPresetOok650Async"); - } - if(!subghz_remote_set_preset(preset, furi_string_get_cstr(temp_str))) { - FURI_LOG_E(TAG, "Could not set preset"); - break; - } - if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { - // TODO: check if preset is custom - FURI_LOG_E(TAG, "Could not use custom preset"); - break; - } - size_t preset_index = - subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(preset->name)); - preset->data = subghz_setting_get_preset_data(setting, preset_index); - preset->data_size = subghz_setting_get_preset_data_size(setting, preset_index); - - // load protocol from file - if(!flipper_format_read_string(fff_file, "Protocol", preset->protocol)) { - FURI_LOG_E(TAG, "Could not read Protocol."); - break; - } - if(!flipper_format_rewind(fff_data)) { - FURI_LOG_E(TAG, "Rewind error"); - return false; - } - - if(!furi_string_cmp_str(preset->protocol, "RAW")) { - subghz_protocol_raw_gen_fff_data(fff_data, path); - // repeat - if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) { - FURI_LOG_E(TAG, "Unable to insert or update Repeat"); - break; - } - - } else { - stream_copy_full( - flipper_format_get_raw_stream(fff_file), flipper_format_get_raw_stream(fff_data)); - // repeat - if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) { - FURI_LOG_E(TAG, "Unable to insert or update Repeat"); - break; - } - } - - if(!flipper_format_rewind(fff_file)) { - FURI_LOG_E(TAG, "Rewind error"); - return false; - } - - preset->decoder = subghz_receiver_search_decoder_base_by_name( - receiver, furi_string_get_cstr(preset->protocol)); - if(preset->decoder) { - SubGhzProtocolStatus status = - subghz_protocol_decoder_base_deserialize(preset->decoder, fff_data); - if(status != SubGhzProtocolStatusOk) { - FURI_LOG_E(TAG, "Protocol deserialize failed, status = %d", status); - break; - } - } else { - FURI_LOG_E(TAG, "Protocol %s not found", furi_string_get_cstr(temp_str)); - } - - res = true; - } while(0); - - furi_string_free(temp_str); - - return res; -} - -// method modified from subghz_i.c -// https://github.com/flipperdevices/flipperzero-firmware/blob/b0daa601ad5b87427a45f9089c8b403a01f72c2a/applications/subghz/subghz_i.c#L417-L456 -bool subghz_remote_save_protocol_to_file(FlipperFormat* fff_file, const char* dev_file_name) { - furi_assert(fff_file); - furi_assert(dev_file_name); - - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* flipper_format_stream = flipper_format_get_raw_stream(fff_file); - - bool saved = false; - FuriString* file_dir; - file_dir = furi_string_alloc(); - - path_extract_dirname(dev_file_name, file_dir); - do { - if(!storage_simply_mkdir(storage, furi_string_get_cstr(file_dir))) { - FURI_LOG_E(TAG, "(save) Cannot mkdir"); - break; - } - - if(!storage_simply_remove(storage, dev_file_name)) { - FURI_LOG_E(TAG, "(save) Cannot remove"); - break; - } - - stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); - stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS); - - saved = true; - //FURI_LOG_D(TAG, "(save) OK Save"); - } while(0); - furi_string_free(file_dir); - furi_record_close(RECORD_STORAGE); - return saved; -} - -void subghz_remote_tx_stop(SubGHzRemote* app) { - if(app->processing == 0) { - return; - } - - if(!furi_string_cmp_str(app->txpreset->protocol, "RAW")) { - while(!furi_hal_subghz_is_async_tx_complete()) { - furi_delay_ms(15); - } - } - - //Stop TX - furi_hal_subghz_stop_async_tx(); - //FURI_LOG_I(TAG, "TX Done!"); - subghz_transmitter_stop(app->tx_transmitter); - - //FURI_LOG_D(TAG, "Checking if protocol is dynamic"); - const SubGhzProtocolRegistry* protocol_registry_items = - subghz_environment_get_protocol_registry(app->environment); - const SubGhzProtocol* proto = subghz_protocol_registry_get_by_name( - protocol_registry_items, furi_string_get_cstr(app->txpreset->protocol)); - //FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type); - - if(proto && proto->type == SubGhzProtocolTypeDynamic) { - //FURI_LOG_D(TAG, "Protocol is dynamic. Saving key"); - // Remove repeat if it was present - flipper_format_delete_key(app->tx_fff_data, "Repeat"); - - subghz_remote_save_protocol_to_file(app->tx_fff_data, app->tx_file_path); - - keeloq_reset_mfname(); - keeloq_reset_kl_type(); - keeloq_reset_original_btn(); - subghz_custom_btns_reset(); - star_line_reset_mfname(); - star_line_reset_kl_type(); - } - - subghz_transmitter_free(app->tx_transmitter); - furi_hal_subghz_idle(); - - notification_message(app->notification, &sequence_blink_stop); - - subghz_remote_preset_free(app->txpreset); - flipper_format_free(app->tx_fff_data); - subghz_remote_end_send(app); -} - -static bool subghz_remote_send_sub(SubGHzRemote* app, FlipperFormat* fff_data) { - // - bool res = false; - do { - if(!furi_hal_subghz_is_tx_allowed(app->txpreset->frequency)) { - FURI_LOG_E( - TAG, - "In your settings, only reception on this frequency (%lu) is allowed,\r\n" - "the actual operation of the subghz remote app is not possible\r\n ", - app->txpreset->frequency); - app->tx_not_allowed = true; - subghz_remote_end_send(app); - break; - } else { - app->tx_not_allowed = false; - } - - app->tx_transmitter = subghz_transmitter_alloc_init( - app->environment, furi_string_get_cstr(app->txpreset->protocol)); - if(!app->tx_transmitter) { - break; - } - - if(subghz_transmitter_deserialize(app->tx_transmitter, fff_data) != - SubGhzProtocolStatusOk) { - FURI_LOG_E(TAG, "Deserialize error!"); - break; - } - - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(app->txpreset->data); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); - - furi_hal_subghz_idle(); - - furi_hal_subghz_set_frequency_and_path(app->txpreset->frequency); - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - - if(!furi_hal_subghz_tx()) { - FURI_LOG_E(TAG, "Sending not allowed"); - break; - } - - //FURI_LOG_I(TAG, "Sending..."); - notification_message(app->notification, &sequence_blink_start_magenta); - - furi_hal_subghz_start_async_tx(subghz_transmitter_yield, app->tx_transmitter); - - res = true; - } while(0); - - return res; -} - -static void subghz_remote_send_signal(SubGHzRemote* app, Storage* storage, const char* path) { - //FURI_LOG_D(TAG, "Sending: %s", path); - - app->tx_file_path = path; - - app->tx_fff_data = flipper_format_string_alloc(); - - app->txpreset = subghz_remote_preset_alloc(); - - // load settings/stream from .sub file - FlipperFormat* fff_file = flipper_format_file_alloc(storage); - bool open_ok = false; - do { - if(!flipper_format_file_open_existing(fff_file, path)) { - FURI_LOG_E(TAG, "Could not open file %s", path); - break; - } - if(!subghz_remote_key_load( - app->txpreset, - fff_file, - app->tx_fff_data, - app->setting, - app->subghz_receiver, - path)) { - FURI_LOG_E(TAG, "Could not load key"); - break; - } - open_ok = true; - } while(0); - flipper_format_free(fff_file); - if(!open_ok) { - FURI_LOG_E(TAG, "Could not load file!"); - return; - } - - subghz_remote_send_sub(app, app->tx_fff_data); -} - -static void subghz_remote_process_signal(SubGHzRemote* app, FuriString* signal) { - view_port_update(app->view_port); - - //FURI_LOG_D(TAG, "signal = %s", furi_string_get_cstr(signal)); - - if(strlen(furi_string_get_cstr(signal)) > 12) { - Storage* storage = furi_record_open(RECORD_STORAGE); - subghz_remote_send_signal(app, storage, furi_string_get_cstr(signal)); - furi_record_close(RECORD_STORAGE); - } else if(strlen(furi_string_get_cstr(signal)) < 10) { - subghz_remote_end_send(app); - } -} - -static void render_callback(Canvas* canvas, void* ctx) { - SubGHzRemote* app = ctx; - furi_check(furi_mutex_acquire(app->model_mutex, FuriWaitForever) == FuriStatusOk); - - //setup different canvas settings - if(app->file_result == 1) { - //if map has no valid filenames defined - canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 62, 5, AlignCenter, AlignTop, "Config is incorrect."); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignTop, "Please configure map."); - canvas_draw_str_aligned(canvas, 62, 60, AlignCenter, AlignBottom, "Press Back to Exit."); - } else if(app->file_result == 3) { - //if map has no valid filenames defined - canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 62, 5, AlignCenter, AlignTop, "Checking config."); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignTop, "If app is stuck..."); - canvas_draw_str_aligned(canvas, 62, 60, AlignCenter, AlignBottom, "Press Back to Exit."); - } else if(app->tx_not_allowed) { - canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 62, 5, AlignCenter, AlignTop, "Transmission is blocked."); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 62, 15, AlignCenter, AlignTop, "Frequency is outside of"); - canvas_draw_str_aligned(canvas, 62, 25, AlignCenter, AlignTop, "default range."); - canvas_draw_str_aligned(canvas, 62, 35, AlignCenter, AlignTop, "Check docs."); - canvas_draw_str_aligned(canvas, 62, 60, AlignCenter, AlignBottom, "Press Back to Exit."); - } else { - //map found, draw all the things - canvas_clear(canvas); - - //canvas_set_font(canvas, FontPrimary); - //canvas_draw_str(canvas, 0, 10, "U: "); - //canvas_draw_str(canvas, 0, 20, "L: "); - //canvas_draw_str(canvas, 0, 30, "R: "); - //canvas_draw_str(canvas, 0, 40, "D: "); - //canvas_draw_str(canvas, 0, 50, "Ok: "); - - //PNGs are located in assets/icons/SubGHzRemote before compilation - - //Icons for Labels - //canvas_draw_icon(canvas, 0, 0, &I_SubGHzRemote_LeftAlignedButtons_9x64); - canvas_draw_icon(canvas, 1, 5, &I_ButtonUp_7x4); - canvas_draw_icon(canvas, 1, 15, &I_ButtonDown_7x4); - canvas_draw_icon(canvas, 2, 23, &I_ButtonLeft_4x7); - canvas_draw_icon(canvas, 2, 33, &I_ButtonRight_4x7); - canvas_draw_icon(canvas, 0, 42, &I_Ok_btn_9x9); - canvas_draw_icon(canvas, 0, 53, &I_back_10px); - - //Labels - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 10, 10, app->up_label); - canvas_draw_str(canvas, 10, 20, app->down_label); - canvas_draw_str(canvas, 10, 30, app->left_label); - canvas_draw_str(canvas, 10, 40, app->right_label); - canvas_draw_str(canvas, 10, 50, app->ok_label); - - canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Press=Exit."); - - //Status text and indicator - canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, app->send_status); - - switch(app->send_status_c) { - case 0: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - break; - case 1: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 116, 17, &I_Pin_arrow_up_7x9); - break; - case 2: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon_ex(canvas, 116, 17, &I_Pin_arrow_up_7x9, IconRotation180); - break; - case 3: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation90); - break; - case 4: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation270); - break; - case 5: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 116, 18, &I_Pin_star_7x7); - 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)); - } - - furi_mutex_release(app->model_mutex); -} - -static void input_callback(InputEvent* input_event, void* ctx) { - SubGHzRemote* app = ctx; - furi_message_queue_put(app->input_queue, input_event, 0); -} - -void subghz_remote_subghz_alloc(SubGHzRemote* app) { - // load subghz presets - app->setting = subghz_setting_alloc(); - subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user")); - - // load mfcodes - app->environment = subghz_environment_alloc(); - 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_nice_flor_s_rainbow_table_file_name( - app->environment, EXT_PATH("subghz/assets/nice_flor_s")); - subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - app->environment, EXT_PATH("subghz/assets/alutech_at_4n")); - subghz_environment_set_protocol_registry(app->environment, (void*)&subghz_protocol_registry); - - app->subghz_receiver = subghz_receiver_alloc_init(app->environment); -} - -SubGHzRemote* subghz_remote_alloc(void) { - SubGHzRemote* app = malloc(sizeof(SubGHzRemote)); +SubGhzRemoteApp* subghz_remote_app_alloc() { + SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp)); // Enable power for External CC1101 if it is connected furi_hal_subghz_enable_ext_power(); @@ -773,24 +34,81 @@ SubGHzRemote* subghz_remote_alloc(void) { furi_hal_power_suppress_charge_enter(); - app->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + app->file_path = furi_string_alloc(); + furi_string_set(app->file_path, SUBREM_APP_FOLDER); - app->input_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); - - app->view_port = view_port_alloc(); - view_port_draw_callback_set(app->view_port, render_callback, app); - view_port_input_callback_set(app->view_port, input_callback, app); - - // Open GUI and register view_port + // GUI app->gui = furi_record_open(RECORD_GUI); - gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); - app->notification = furi_record_open(RECORD_NOTIFICATION); + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&subrem_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, subghz_remote_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, subghz_remote_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, subghz_remote_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubRemViewSubmenu, submenu_get_view(app->submenu)); + + //Dialog + app->dialogs = furi_record_open(RECORD_DIALOGS); + + // Remote view + app->subrem_remote_view = subrem_view_remote_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubRemViewIDRemote, + subrem_view_remote_get_view(app->subrem_remote_view)); + + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + app->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->environment = subghz_environment_alloc(); + + 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; + +#ifdef SUBREM_LIGHT + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); +#else + scene_manager_next_scene(app->scene_manager, SubRemSceneStart); +#endif return app; } -void subghz_remote_free(SubGHzRemote* app, bool with_subghz) { +void subghz_remote_app_free(SubGhzRemoteApp* app) { + furi_assert(app); + furi_hal_power_suppress_charge_exit(); // Disable power for External CC1101 if it was enabled and module is connected @@ -798,304 +116,47 @@ void subghz_remote_free(SubGHzRemote* app, bool with_subghz) { // Reinit SPI handles for internal radio / nfc furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - furi_string_free(app->up_file); - furi_string_free(app->down_file); - furi_string_free(app->left_file); - furi_string_free(app->right_file); - furi_string_free(app->ok_file); + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewSubmenu); + submenu_free(app->submenu); - furi_string_free(app->file_path); - furi_string_free(app->signal); + //Dialog + furi_record_close(RECORD_DIALOGS); - gui_remove_view_port(app->gui, app->view_port); - furi_record_close(RECORD_GUI); - view_port_free(app->view_port); - app->gui = NULL; + // Remote view + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDRemote); + subrem_view_remote_free(app->subrem_remote_view); - furi_message_queue_free(app->input_queue); + subghz_receiver_free(app->receiver); + subghz_environment_free(app->environment); + subghz_setting_free(app->setting); - furi_mutex_free(app->model_mutex); - - if(with_subghz) { - furi_hal_subghz_sleep(); - subghz_setting_free(app->setting); - subghz_receiver_free(app->subghz_receiver); - subghz_environment_free(app->environment); + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + subrem_sub_file_preset_free(app->subs_preset[i]); } + // Notifications furi_record_close(RECORD_NOTIFICATION); - app->notification = NULL; + app->notifications = NULL; + + // Close records + furi_record_close(RECORD_GUI); + + // Path strings + furi_string_free(app->file_path); free(app); } int32_t subghz_remote_app(void* p) { UNUSED(p); - SubGHzRemote* app = subghz_remote_alloc(); + SubGhzRemoteApp* subghz_remote_app = subghz_remote_app_alloc(); - app->file_path = furi_string_alloc(); - app->signal = furi_string_alloc(); + furi_string_set(subghz_remote_app->file_path, SUBREM_APP_FOLDER); - //setup variables before population - app->up_file = furi_string_alloc(); - app->down_file = furi_string_alloc(); - app->left_file = furi_string_alloc(); - app->right_file = furi_string_alloc(); - app->ok_file = furi_string_alloc(); + view_dispatcher_run(subghz_remote_app->view_dispatcher); - app->file_result = 3; - - Storage* storage = furi_record_open(RECORD_STORAGE); - storage_common_migrate(storage, EXT_PATH("unirf"), SUBREMOTEMAP_FOLDER); - - if(!storage_simply_mkdir(storage, SUBREMOTEMAP_FOLDER)) { - FURI_LOG_E(TAG, "Could not create folder %s", SUBREMOTEMAP_FOLDER); - } - furi_record_close(RECORD_STORAGE); - - furi_string_set(app->file_path, SUBREMOTEMAP_FOLDER); - - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, SUBREMOTEMAP_EXTENSION, &I_sub1_10px); - browser_options.base_path = SUBREMOTEMAP_FOLDER; - - bool res = dialog_file_browser_show(dialogs, app->file_path, app->file_path, &browser_options); - - furi_record_close(RECORD_DIALOGS); - if(!res) { - FURI_LOG_E(TAG, "No file selected"); - subghz_remote_free(app, false); - return 255; - } else { - //check map and population variables - subghz_remote_cfg_set_check(app, app->file_path); - } - - // init subghz stuff - subghz_remote_subghz_alloc(app); - - bool exit_loop = false; - - if(app->file_result == 2) { - //FURI_LOG_D( - //TAG, - //"U: %s - D: %s - L: %s - R: %s - O: %s ", - //furi_string_get_cstr(app->up_file), - //furi_string_get_cstr(app->down_file), - //furi_string_get_cstr(app->left_file), - //furi_string_get_cstr(app->right_file), - //furi_string_get_cstr(app->ok_file)); - - //variables to control multiple button presses and status updates - app->send_status = "Idle"; - app->send_status_c = 0; - app->processing = 0; - //app->repeat = 1; - app->button = 0; - - //refresh screen to update variables before processing main screen or error screens - furi_mutex_release(app->model_mutex); - view_port_update(app->view_port); - - //input detect loop start - InputEvent input; - while(1) { - furi_check( - furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); - //FURI_LOG_D( - //TAG, - //"key: %s type: %s", - //input_get_key_name(input.key), - //input_get_type_name(input.type)); - - switch(input.key) { - case InputKeyUp: - if(input.type == InputTypePress) { - if(app->up_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->up_file); - app->button = 1; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->up_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyDown: - if(input.type == InputTypePress) { - if(app->down_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->down_file); - app->button = 2; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->down_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyRight: - if(input.type == InputTypePress) { - if(app->right_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->right_file); - app->button = 3; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->right_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyLeft: - if(input.type == InputTypePress) { - if(app->left_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->left_file); - app->button = 4; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->left_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyOk: - if(input.type == InputTypePress) { - if(app->ok_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->ok_file); - app->button = 5; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->ok_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyBack: - subghz_remote_tx_stop(app); - exit_loop = true; - break; - default: - break; - } - - if(app->processing == 0) { - //FURI_LOG_D(TAG, "processing 0"); - app->send_status = "Idle"; - app->send_status_c = 0; - app->button = 0; - } else if(app->processing == 1) { - //FURI_LOG_D(TAG, "processing 1"); - - app->send_status = "Send"; - - switch(app->button) { - case 1: - app->send_status_c = 1; - break; - case 2: - app->send_status_c = 2; - break; - case 3: - app->send_status_c = 3; - break; - case 4: - app->send_status_c = 4; - break; - case 5: - app->send_status_c = 5; - break; - } - - app->processing = 2; - - subghz_remote_process_signal(app, app->signal); - } - - if(exit_loop == true) { - furi_mutex_release(app->model_mutex); - break; - } - - furi_mutex_release(app->model_mutex); - view_port_update(app->view_port); - } - } else if(app->file_result == 1 || app->file_result == 3) { - //refresh screen to update variables before processing main screen or error screens - view_port_update(app->view_port); - - InputEvent input; - while(1) { - furi_check( - furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); - //FURI_LOG_D( - //TAG, - //"key: %s type: %s", - //input_get_key_name(input.key), - //input_get_type_name(input.type)); - - switch(input.key) { - case InputKeyRight: - break; - case InputKeyLeft: - break; - case InputKeyUp: - break; - case InputKeyDown: - break; - case InputKeyOk: - break; - case InputKeyBack: - exit_loop = true; - break; - default: - break; - } - - if(exit_loop == true) { - furi_mutex_release(app->model_mutex); - break; - } - - furi_mutex_release(app->model_mutex); - view_port_update(app->view_port); - } - } else { - furi_mutex_release(app->model_mutex); - } - - // remove & free all stuff created by app - subghz_remote_free(app, true); + subghz_remote_app_free(subghz_remote_app); return 0; } diff --git a/applications/main/subghz_remote/subghz_remote_app_i.c b/applications/main/subghz_remote/subghz_remote_app_i.c new file mode 100644 index 000000000..9b0f77d17 --- /dev/null +++ b/applications/main/subghz_remote/subghz_remote_app_i.c @@ -0,0 +1,448 @@ +#include "subghz_remote_app_i.h" +#include +#include + +#include + +// #include +// #include + +#include + +#define TAG "SubGhzRemote" + +static const char* map_file_labels[SubRemSubKeyNameMaxCount][2] = { + [SubRemSubKeyNameUp] = {"UP", "ULABEL"}, + [SubRemSubKeyNameDown] = {"DOWN", "DLABEL"}, + [SubRemSubKeyNameLeft] = {"LEFT", "LLABEL"}, + [SubRemSubKeyNameRight] = {"RIGHT", "RLABEL"}, + [SubRemSubKeyNameOk] = {"OK", "OKLABEL"}, +}; + +static bool + subrem_set_preset_data(SubGhzSetting* setting, FreqPreset* freq_preset, const char* preset) { + const char* preset_name = ""; + if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { + preset_name = "AM270"; + } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { + preset_name = "AM650"; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { + preset_name = "FM238"; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { + preset_name = "FM476"; + } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { + // preset_name = "CUSTOM"; + return false; + } else { + FURI_LOG_E(TAG, "Unknown preset"); + return false; + } + size_t preset_index = subghz_setting_get_inx_preset_by_name(setting, preset_name); + freq_preset->data = subghz_setting_get_preset_data(setting, preset_index); + return true; +} + +SubRemSubFilePreset* subrem_sub_file_preset_alloc() { + SubRemSubFilePreset* sub_preset = malloc(sizeof(SubRemSubFilePreset)); + + sub_preset->fff_data = flipper_format_string_alloc(); + sub_preset->file_path = furi_string_alloc(); + sub_preset->protocaol_name = furi_string_alloc(); + sub_preset->label = furi_string_alloc_set_str("N/A"); + + sub_preset->type = SubGhzProtocolTypeUnknown; + + return sub_preset; +} + +void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset) { + furi_assert(sub_preset); + + furi_string_free(sub_preset->label); + furi_string_free(sub_preset->protocaol_name); + furi_string_free(sub_preset->file_path); + flipper_format_free(sub_preset->fff_data); + + free(sub_preset); +} + +static void subrem_sub_file_preset_reset(SubRemSubFilePreset* sub_preset) { + furi_assert(sub_preset); + + furi_string_set_str(sub_preset->label, "N/A"); + furi_string_reset(sub_preset->protocaol_name); + furi_string_reset(sub_preset->file_path); + + Stream* fff_data_stream = flipper_format_get_raw_stream(sub_preset->fff_data); + stream_clean(fff_data_stream); + + sub_preset->type = SubGhzProtocolTypeUnknown; +} + +void subrem_map_preset_reset(SubGhzRemoteApp* app) { + furi_assert(app); + + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + subrem_sub_file_preset_reset(app->subs_preset[i]); + } +} + +static bool subrem_map_preset_load(SubGhzRemoteApp* app, FlipperFormat* fff_data_file) { + furi_assert(app); + bool ret = false; + SubRemSubFilePreset* sub_preset; + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->subs_preset[i]; + if(!flipper_format_read_string( + fff_data_file, map_file_labels[i][0], sub_preset->file_path)) { +#if FURI_DEBUG + FURI_LOG_W(TAG, "No file patch for %s", map_file_labels[i][0]); +#endif + sub_preset->type = SubGhzProtocolTypeUnknown; + } else if(!flipper_format_rewind(fff_data_file)) { + // Rewind error + } else if(!flipper_format_read_string( + fff_data_file, map_file_labels[i][1], sub_preset->label)) { +#if FURI_DEBUG + FURI_LOG_W(TAG, "No Label for %s", map_file_labels[i][0]); +#endif + path_extract_filename(sub_preset->file_path, sub_preset->label, true); + } else { + FURI_LOG_I( + TAG, + "%-5s: %s %s", + map_file_labels[i][0], + furi_string_get_cstr(sub_preset->label), + furi_string_get_cstr(sub_preset->file_path)); + ret = true; + } + flipper_format_rewind(fff_data_file); + } + return ret; +} + +bool subrem_save_protocol_to_file(FlipperFormat* flipper_format, const char* dev_file_name) { + furi_assert(flipper_format); + furi_assert(dev_file_name); + + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format); + + bool saved = false; + FuriString* file_dir = furi_string_alloc(); + + path_extract_dirname(dev_file_name, file_dir); + do { + //removing additional fields + flipper_format_delete_key(flipper_format, "Repeat"); + //flipper_format_delete_key(flipper_format, "Manufacture"); + + if(!storage_simply_remove(storage, dev_file_name)) { + break; + } + //ToDo check Write + stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); + stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS); + + saved = true; + } while(0); + furi_string_free(file_dir); + furi_record_close(RECORD_STORAGE); + return saved; +} + +bool subrem_tx_start_sub( + SubGhzRemoteApp* app, + SubRemSubFilePreset* sub_preset, + SubGhzProtocolEncoderRAWCallbackEnd callback) { + furi_assert(app); + furi_assert(sub_preset); + bool ret = false; + + subrem_tx_stop_sub(app, true); + + if(sub_preset->type == SubGhzProtocolTypeUnknown) { + return false; + } + + FURI_LOG_I(TAG, "Send %s", furi_string_get_cstr(sub_preset->label)); + + subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK); + keeloq_reset_original_btn(); + subghz_custom_btns_reset(); + + do { + flipper_format_rewind(sub_preset->fff_data); // + + app->transmitter = subghz_transmitter_alloc_init( + app->environment, furi_string_get_cstr(sub_preset->protocaol_name)); + + if(app->transmitter) { + if(subghz_transmitter_deserialize(app->transmitter, sub_preset->fff_data) != + SubGhzProtocolStatusOk) { + FURI_LOG_E(TAG, "Deserialize error!"); + break; + } + furi_hal_subghz_reset(); + furi_hal_subghz_idle(); + furi_hal_subghz_load_custom_preset(sub_preset->freq_preset.data); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + + furi_hal_subghz_idle(); + + furi_hal_subghz_set_frequency_and_path(sub_preset->freq_preset.frequency); + furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + + if(!furi_hal_subghz_tx()) { + FURI_LOG_E(TAG, "Sending not allowed"); + break; + } + + if(sub_preset->type == SubGhzProtocolTypeRAW) { + subghz_protocol_raw_file_encoder_worker_set_callback_end( + (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance( + app->transmitter), + callback, + app); + } + + furi_hal_subghz_start_async_tx(subghz_transmitter_yield, app->transmitter); + + ret = true; + } + } while(false); + + app->tx_running = ret; + + return ret; +} + +static void subghz_tx_stop(SubGhzRemoteApp* app) { + furi_assert(app); + + //Stop TX + furi_hal_subghz_stop_async_tx(); + + subghz_transmitter_stop(app->transmitter); + subghz_transmitter_free(app->transmitter); + furi_hal_subghz_idle(); +} + +bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced) { + furi_assert(app); + SubRemSubFilePreset* sub_preset = app->subs_preset[app->chusen_sub]; + + if(forced || (sub_preset->type != SubGhzProtocolTypeRAW)) { + // SubRemSubKeyTypeRawKey)) { + if(app->tx_running) { + subghz_tx_stop(app); + + if(sub_preset->type == SubGhzProtocolTypeDynamic) { + subrem_save_protocol_to_file( + sub_preset->fff_data, furi_string_get_cstr(sub_preset->file_path)); + + keeloq_reset_mfname(); + keeloq_reset_kl_type(); + keeloq_reset_original_btn(); + subghz_custom_btns_reset(); + star_line_reset_mfname(); + star_line_reset_kl_type(); + } + + app->tx_running = false; + return true; + } + } + + return false; +} + +static bool subrem_map_preset_check(SubGhzRemoteApp* app, FlipperFormat* fff_data_file) { + furi_assert(app); + FuriString* temp_str = furi_string_alloc(); + uint32_t temp_data32; + bool ret = false; + bool sub_preset_loaded = false; + SubRemSubFilePreset* sub_preset; + + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->subs_preset[i]; + sub_preset_loaded = false; + if(furi_string_empty(sub_preset->file_path)) { + // FURI_LOG_I(TAG, "Empty file path"); + continue; + } + do { + if(!flipper_format_file_open_existing( + fff_data_file, furi_string_get_cstr(sub_preset->file_path))) { + FURI_LOG_W(TAG, "Error open file %s", furi_string_get_cstr(sub_preset->file_path)); + break; + } + + if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + + if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || + (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && + temp_data32 == SUBGHZ_KEY_FILE_VERSION) { + } else { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + //Load frequency + if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) { + FURI_LOG_W(TAG, "Cannot read frequency. Set default frequency"); + sub_preset->freq_preset.frequency = + subghz_setting_get_default_frequency(app->setting); + } else if(!furi_hal_subghz_is_tx_allowed(temp_data32)) { + FURI_LOG_E(TAG, "This frequency can only be used for RX"); + break; + } else { + sub_preset->freq_preset.frequency = temp_data32; + } + + //Load preset + if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { + FURI_LOG_E(TAG, "Missing Preset"); + break; + } + + if(!subrem_set_preset_data( + app->setting, &sub_preset->freq_preset, furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Cannot load preset."); + break; + } + + //Load protocol + if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + + FlipperFormat* fff_data = sub_preset->fff_data; + if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { + //if RAW + subghz_protocol_raw_gen_fff_data( + fff_data, furi_string_get_cstr(sub_preset->file_path)); + } else { + stream_copy_full( + flipper_format_get_raw_stream(fff_data_file), + flipper_format_get_raw_stream(fff_data)); + } + + const SubGhzProtocolRegistry* protocol_registry_items = + subghz_environment_get_protocol_registry(app->environment); + + const SubGhzProtocol* protocol = subghz_protocol_registry_get_by_name( + protocol_registry_items, furi_string_get_cstr(temp_str)); + + if(!protocol) { + FURI_LOG_E(TAG, "Protocol not found"); + break; + } else if(protocol->flag & SubGhzProtocolFlag_Send) { + if((protocol->type == SubGhzProtocolTypeStatic) || + (protocol->type == SubGhzProtocolTypeDynamic) || + // (protocol->type == SubGhzProtocolTypeBinRAW) || // TODO: BINRAW + (protocol->type == SubGhzProtocolTypeRAW)) { + sub_preset->type = protocol->type; + } else { + FURI_LOG_E(TAG, "Unsuported Protocol"); + break; + } + + furi_string_set(sub_preset->protocaol_name, temp_str); + } else { + FURI_LOG_E(TAG, "Protocol does not support transmission"); + } + + sub_preset_loaded = true; + ret |= true; +#if FURI_DEBUG + FURI_LOG_I(TAG, "%-16s - protocol Loaded", furi_string_get_cstr(sub_preset->label)); +#endif + } while(false); + + // TODO: + // Load file state logic + // Label depending on the state + + if(!sub_preset_loaded) { + furi_string_set_str(sub_preset->label, "N/A"); + } + + flipper_format_file_close(fff_data_file); + } + furi_string_free(temp_str); + + return ret; +} + +bool subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path) { + furi_assert(app); + furi_assert(file_path); +#if FURI_DEBUG + FURI_LOG_I(TAG, "Load Map File Start"); +#endif + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + bool ret = false; +#if FURI_DEBUG + FURI_LOG_I(TAG, "Open Map File.."); +#endif + subrem_map_preset_reset(app); + + if(!flipper_format_file_open_existing(fff_data_file, file_path)) { + FURI_LOG_E(TAG, "Could not open MAP file %s", file_path); + } else { + if(!subrem_map_preset_load(app, fff_data_file)) { + FURI_LOG_E(TAG, "Could no Sub file path in MAP file"); + // ret = // error for popup + } else if( + (flipper_format_file_close(fff_data_file)) && + (subrem_map_preset_check(app, fff_data_file))) { + FURI_LOG_I(TAG, "Load Map File Seccesful"); + ret = true; + } + } + + // TODO: Popup for error or return error type + if(!ret) { + FURI_LOG_E(TAG, "Broken Map File"); + } + + flipper_format_file_close(fff_data_file); + flipper_format_free(fff_data_file); + + furi_record_close(RECORD_STORAGE); + + return ret; +} + +SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { + furi_assert(app); + + FuriString* file_path = furi_string_alloc(); + SubRemLoadMapState ret = SubRemLoadMapStateBack; + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, SUBREM_APP_EXTENSION, &I_sub1_10px); + browser_options.base_path = SUBREM_APP_FOLDER; + + // Input events and views are managed by file_select + if(!dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) { + } else if(subrem_map_file_load(app, furi_string_get_cstr(app->file_path))) { + ret = SubRemLoadMapStateOK; + } else { + ret = SubRemLoadMapStateError; + } + + furi_string_free(file_path); + + return ret; +} diff --git a/applications/main/subghz_remote/subghz_remote_app_i.h b/applications/main/subghz_remote/subghz_remote_app_i.h new file mode 100644 index 000000000..1cbdbd5cd --- /dev/null +++ b/applications/main/subghz_remote/subghz_remote_app_i.h @@ -0,0 +1,84 @@ +#pragma once + +#include "helpers/subrem_types.h" +#include + +#include "views/remote.h" + +#include "scenes/subrem_scene.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#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; +} 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; + Submenu* submenu; + FuriString* file_path; + char file_name_tmp[SUBREM_MAX_LEN_NAME]; + + SubRemViewRemote* subrem_remote_view; + + SubRemSubFilePreset* subs_preset[SubRemSubKeyNameMaxCount]; + + SubGhzSetting* setting; + SubGhzEnvironment* environment; + SubGhzReceiver* receiver; + SubGhzTransmitter* transmitter; + + bool tx_running; + + uint8_t chusen_sub; + + // TODO: LoadFileError +} SubGhzRemoteApp; + +SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app); + +bool subrem_tx_start_sub( + SubGhzRemoteApp* app, + SubRemSubFilePreset* sub_preset, + SubGhzProtocolEncoderRAWCallbackEnd callback); + +bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced); \ No newline at end of file diff --git a/applications/main/subghz_remote/views/remote.c b/applications/main/subghz_remote/views/remote.c new file mode 100644 index 000000000..85c63568e --- /dev/null +++ b/applications/main/subghz_remote/views/remote.c @@ -0,0 +1,302 @@ +#include "remote.h" +#include "../subghz_remote_app_i.h" + +#include +#include + +#define SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH 16 + +struct SubRemViewRemote { + View* view; + SubRemViewRemoteCallback callback; + 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; + + SubRemViewRemoteState state; + + uint8_t pressed_btn; +} SubRemViewRemoteModel; + +void subrem_view_remote_set_callback( + SubRemViewRemote* subrem_view_remote, + SubRemViewRemoteCallback callback, + void* context) { + furi_assert(subrem_view_remote); + + subrem_view_remote->callback = callback; + subrem_view_remote->context = context; +} + +void subrem_view_remote_add_data_to_show( + SubRemViewRemote* subrem_view_remote, + const char* up_label, + const char* down_label, + const char* left_label, + const char* right_label, + const char* ok_label) { + furi_assert(subrem_view_remote); + + 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); + }, + 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); +} + +void subrem_view_remote_set_state( + SubRemViewRemote* subrem_view_remote, + SubRemViewRemoteState state) { + furi_assert(subrem_view_remote); + with_view_model( + subrem_view_remote->view, SubRemViewRemoteModel * model, { model->state = state; }, true); +} + +void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_clear(canvas); + + //Icons for Labels + //canvas_draw_icon(canvas, 0, 0, &I_SubGHzRemote_LeftAlignedButtons_9x64); + canvas_draw_icon(canvas, 1, 5, &I_ButtonUp_7x4); + canvas_draw_icon(canvas, 1, 15, &I_ButtonDown_7x4); + canvas_draw_icon(canvas, 2, 23, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 2, 33, &I_ButtonRight_4x7); + canvas_draw_icon(canvas, 0, 42, &I_Ok_btn_9x9); + canvas_draw_icon(canvas, 0, 53, &I_back_10px); + + //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); + + // 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."); + + //Status text and indicator + canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); + + if(model->state == SubRemViewRemoteStateIdle) { + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Idle"); + } else { + switch(model->state) { + case SubRemViewRemoteStateSending: + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Send"); + break; + case SubRemViewRemoteStateLoading: + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Load"); + break; + default: +#if FURI_DEBUG + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Wrong_state"); +#endif + break; + } + + switch(model->pressed_btn) { + case SubRemSubKeyNameUp: + canvas_draw_icon(canvas, 116, 17, &I_Pin_arrow_up_7x9); + break; + case SubRemSubKeyNameDown: + canvas_draw_icon_ex(canvas, 116, 17, &I_Pin_arrow_up_7x9, IconRotation180); + break; + case SubRemSubKeyNameLeft: + canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation270); + break; + case SubRemSubKeyNameRight: + canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation90); + break; + case SubRemSubKeyNameOk: + canvas_draw_icon(canvas, 116, 18, &I_Pin_star_7x7); + 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) { + furi_assert(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) { + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { model->pressed_btn = 0; }, + true); + subrem_view_remote->callback( + SubRemCustomEventViewRemoteForcedStop, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyBack) { + return true; + } + // BACK button processing end + + if(event->key == InputKeyUp && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartUP, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyDown && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartDOWN, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyLeft && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartLEFT, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyRight && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartRIGHT, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyOk && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartOK, subrem_view_remote->context); + return true; + } else if(event->type == InputTypeRelease) { + subrem_view_remote->callback(SubRemCustomEventViewRemoteStop, subrem_view_remote->context); + return true; + } + + return true; +} + +void subrem_view_remote_enter(void* context) { + furi_assert(context); +} + +void subrem_view_remote_exit(void* context) { + furi_assert(context); +} + +SubRemViewRemote* subrem_view_remote_alloc() { + SubRemViewRemote* subrem_view_remote = malloc(sizeof(SubRemViewRemote)); + + // View allocation and configuration + subrem_view_remote->view = view_alloc(); + view_allocate_model( + subrem_view_remote->view, ViewModelTypeLocking, sizeof(SubRemViewRemoteModel)); + view_set_context(subrem_view_remote->view, subrem_view_remote); + view_set_draw_callback(subrem_view_remote->view, (ViewDrawCallback)subrem_view_remote_draw); + view_set_input_callback(subrem_view_remote->view, subrem_view_remote_input); + view_set_enter_callback(subrem_view_remote->view, subrem_view_remote_enter); + view_set_exit_callback(subrem_view_remote->view, subrem_view_remote_exit); + + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { + 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"); + + model->pressed_btn = 0; + }, + true); + return subrem_view_remote; +} + +void subrem_view_remote_free(SubRemViewRemote* subghz_remote) { + furi_assert(subghz_remote); + + with_view_model( + 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); + }, + true); + view_free(subghz_remote->view); + free(subghz_remote); +} + +View* subrem_view_remote_get_view(SubRemViewRemote* subrem_view_remote) { + furi_assert(subrem_view_remote); + return subrem_view_remote->view; +} \ No newline at end of file diff --git a/applications/main/subghz_remote/views/remote.h b/applications/main/subghz_remote/views/remote.h new file mode 100644 index 000000000..76121cf8a --- /dev/null +++ b/applications/main/subghz_remote/views/remote.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include "../helpers/subrem_custom_event.h" + +typedef enum { + SubRemViewRemoteStateIdle, + SubRemViewRemoteStateLoading, + SubRemViewRemoteStateSending, +} SubRemViewRemoteState; + +typedef struct SubRemViewRemote SubRemViewRemote; + +typedef void (*SubRemViewRemoteCallback)(SubRemCustomEvent event, void* context); + +void subrem_view_remote_set_callback( + SubRemViewRemote* subrem_view_remote, + SubRemViewRemoteCallback callback, + void* context); + +SubRemViewRemote* subrem_view_remote_alloc(); + +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( + SubRemViewRemote* subrem_view_remote, + const char* up_label, + const char* down_label, + const char* left_label, + const char* right_label, + const char* ok_label); + +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