diff --git a/application.fam b/application.fam index c332723..7d6b133 100644 --- a/application.fam +++ b/application.fam @@ -17,6 +17,8 @@ _ENABLE_EMULATE = ProtoPirateDefineEnabled("ENABLE_EMULATE_FEATURE") _MAIN_APP_SOURCES = [ "*.c*", + "!helpers/protopirate_models.c", + "!helpers/variable_item_list.c", "!protocols/plugins", "!scenes/plugins", "!protocols", @@ -419,3 +421,19 @@ App( fap_icon_assets="images", fal_embedded=True, ) + +App( + appid="protopirate_config_plugin", + apptype=FlipperAppType.PLUGIN, + entry_point="protopirate_config_plugin_ep", + requires=["proto_pirate"], + sources=[ + "helpers/protopirate_protocol_plugin_host.c", + "helpers/protopirate_models.c", + "helpers/protopirate_txrx.c", + "helpers/variable_item_list.c", + "protocols/protocol_items.c", + "scenes/plugins/protopirate_config_plugin.c", + ], + fal_embedded=True, +) \ No newline at end of file diff --git a/helpers/protopirate_models.c b/helpers/protopirate_models.c new file mode 100644 index 0000000..7bf63af --- /dev/null +++ b/helpers/protopirate_models.c @@ -0,0 +1,283 @@ +#include "protopirate_models.h" + +#define TAG "ProtoPirateModels" + +#define PROTOPIRATE_MODELS_FILE APP_ASSETS_PATH("models.txt") + +//Models File Format. +#define MODELS_FILE_HEADER "ProtoPirate Car Models Database" +#define MODELS_FILE_VERSION 2 +#define MODELS_FIELD_MODEL_COUNT "ModelCount" +#define MODELS_FIELD_MODEL_NAME "ModelName" +#define MODELS_FIELD_FREQUENCY "Frequency" +#define MODELS_FIELD_PRESET_NAME "PresetName" +#define MODELS_FIELD_CUSTOM_PRESET_DATA "CustomPresetData" + +bool car_model_get_by_index( + ProtoPirateCarModel* car_model, + uint16_t index, + uint16_t car_models_count, + SubGhzSetting* app_settings) { + //Temp variables + uint32_t version = 0; + uint32_t frequency; + uint8_t* preset_data = NULL; + uint32_t preset_data_size = 0; + bool error = true; + bool custom_preset = false; + + //Objects that need allocating. + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + FuriString* preset_name = furi_string_alloc(); + FuriString* model_name = furi_string_alloc(); + FuriString* header = furi_string_alloc(); + + //Index 0 is None. + switch(index) { + case 0: + //Leave as same as error (none) + break; + default: { + //Open Models File, check the Header, check Version + if(!flipper_format_file_open_existing(ff, PROTOPIRATE_MODELS_FILE)) { + FURI_LOG_I(TAG, "models.txt file not found"); + break; + } else if(!flipper_format_read_header(ff, header, &version)) { + FURI_LOG_W(TAG, "Failed to read models.txt header"); + break; + } else if(furi_string_cmp_str(header, MODELS_FILE_HEADER) != 0) { + FURI_LOG_W(TAG, "Invalid models.txt file header"); + break; + } else if(version != MODELS_FILE_VERSION) { + FURI_LOG_W(TAG, "Unsupported models.txt version %lu", (unsigned long)version); + break; + } + + //Fields we add the number to for searching the file. + char model_name_index[16]; //Frequency65535 + NULL + char frequency_index[16]; //Frequency65535 + NULL + char preset_name_index[18]; //PresetName65535 + NULL + char preset_data_index[23]; //CustomPresetData65535 + NULL + + //Build the values we need to grab. + snprintf(model_name_index, sizeof(model_name_index), MODELS_FIELD_MODEL_NAME "%u", index); + snprintf(frequency_index, sizeof(frequency_index), MODELS_FIELD_FREQUENCY "%u", index); + snprintf( + preset_name_index, sizeof(preset_name_index), MODELS_FIELD_PRESET_NAME "%u", index); + snprintf( + preset_data_index, + sizeof(preset_data_index), + MODELS_FIELD_CUSTOM_PRESET_DATA "%u", + index); + + //Read the Model Name. + if(!flipper_format_read_string(ff, model_name_index, model_name)) { + FURI_LOG_E("ProtoPirate", "Failed to read %s", model_name_index); + break; + } + + // Read frequency + if(!flipper_format_read_uint32(ff, frequency_index, &frequency, 1)) { + FURI_LOG_W(TAG, "Failed to read frequency."); + frequency = 0; + break; + } + + //Read the Modulation Preset. + if(!flipper_format_read_string(ff, preset_name_index, preset_name)) { + //Set the Custom preset name. + furi_string_set_str(preset_name, "Custom"); + custom_preset = true; + } else { + //Get the built in preset if it exists + const char* preset_name_long = furi_string_get_cstr(preset_name); + const char* preset_name_short = preset_name_long; + + //Which Preset have we selected? + if(!strcmp(preset_name_long, "FuriHalSubGhzPresetOok270Async")) { + preset_name_short = "AM270"; + } else if(!strcmp(preset_name_long, "FuriHalSubGhzPresetOok650Async")) { + preset_name_short = "AM650"; + } else if(!strcmp(preset_name_long, "FuriHalSubGhzPreset2FSKDev238Async")) { + preset_name_short = "FM238"; + } else if(!strcmp(preset_name_long, "FuriHalSubGhzPreset2FSKDev12KAsync")) { + preset_name_short = "FM12K"; + } else if(!strcmp(preset_name_long, "FuriHalSubGhzPreset2FSKDev476Async")) { + preset_name_short = "FM476"; + } else if(!strcmp(preset_name_long, "FuriHalSubGhzPresetCustom")) { + preset_name_short = "Custom"; + custom_preset = true; + } + + //Set the preset_name + furi_string_set_str(preset_name, preset_name_short); + + if(!custom_preset) { + size_t preset_index = subghz_setting_get_preset_count(app_settings); + for(size_t i = 0; i < subghz_setting_get_preset_count(app_settings); i++) { + if(!strcmp( + subghz_setting_get_preset_name(app_settings, i), preset_name_short)) { + preset_index = i; + break; + } + } + if(preset_index >= subghz_setting_get_preset_count(app_settings)) { + preset_name_short = "AM650"; + for(size_t i = 0; i < subghz_setting_get_preset_count(app_settings); i++) { + if(!strcmp( + subghz_setting_get_preset_name(app_settings, i), + preset_name_short)) { + preset_index = i; + break; + } + } + if(preset_index >= subghz_setting_get_preset_count(app_settings)) { + FURI_LOG_E(TAG, "Failed to get preset index!"); + break; + } + } + + //Get the preset data into a buffer to copy it. + uint8_t* temp_preset_data = NULL; + uint32_t temp_preset_data_size = 0; + temp_preset_data = subghz_setting_get_preset_data(app_settings, preset_index); + temp_preset_data_size = + subghz_setting_get_preset_data_size(app_settings, preset_index); + + //Error fallback + if(temp_preset_data == NULL) { + FURI_LOG_E(TAG, "Failed to get preset data!"); + break; + } else { + //Copy the preset data, so we can initialize it. + preset_data = malloc(temp_preset_data_size); + memcpy(preset_data, temp_preset_data, temp_preset_data_size); + preset_data_size = temp_preset_data_size; + + //Yay, we made it... + error = false; + } + } + } + + /* Custom_preset_data (only if present) */ + if(custom_preset && + flipper_format_get_value_count(ff, preset_data_index, &preset_data_size) && + preset_data_size > 0) { + if(preset_data_size >= 1024) { + FURI_LOG_E( + "ProtoPirate", + "%s too large: %lu", + preset_data_index, + (unsigned long)preset_data_size); + break; + } + + preset_data = malloc(preset_data_size); + if(!preset_data) { + FURI_LOG_E( + "ProtoPirate", + "Malloc failed: %s (%lu bytes)", + preset_data_index, + (unsigned long)preset_data_size); + break; + } + + flipper_format_rewind(ff); + if(!flipper_format_read_hex(ff, preset_data_index, preset_data, preset_data_size)) { + break; + } + + //Yay, we made it... + error = false; + } + } + } + + if(!error) { + //Set the Car Model name and index. + furi_string_set((car_model)->name, model_name); + car_model->index = index; + + //Alloc a preset if none, or recycle. + if(!car_model->preset) { + car_model->preset = malloc(sizeof(SubGhzRadioPreset)); + car_model->preset->name = furi_string_alloc(); + } else { + //Free old data.. + if(car_model->preset->data) { + free(car_model->preset->data); + car_model->preset->data = NULL; + } + } + furi_string_set(car_model->preset->name, preset_name); + car_model->preset->data = preset_data; + car_model->preset->data_size = preset_data_size; + car_model->preset->frequency = frequency; + } else { + car_model->index = 0; + furi_string_set_str( + car_model->name, + (car_models_count) ? "< Select a Car Model >" : "No Models in Database"); + + if(car_model->preset) { + if(car_model->preset->data) { + free(car_model->preset->data); + car_model->preset->data = NULL; + } + + car_model->preset->data_size = 0; + + if(car_model->preset->name) { + furi_string_free(car_model->preset->name); + car_model->preset->name = NULL; + } + + free(car_model->preset); + car_model->preset = NULL; + } + + // If we allocated preset_data but never assigned it, free it + if(custom_preset && preset_data_size) { + free(preset_data); + } + } + + //Close up and return. + flipper_format_free(ff); + furi_string_free(header); + furi_string_free(model_name); + furi_string_free(preset_name); + furi_record_close(RECORD_STORAGE); + return (!error); +} + +uint16_t car_model_get_count(void) { + uint32_t version = 0; + uint32_t model_count = 0; + + //Set up storage, init temp variables + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + FuriString* header = furi_string_alloc(); + + //Open file, read header, check version, and grab the model count. + if(!flipper_format_file_open_existing(ff, PROTOPIRATE_MODELS_FILE)) { + FURI_LOG_W(TAG, "Failed to open " PROTOPIRATE_MODELS_FILE); + } else if(!flipper_format_read_header(ff, header, &version)) { + FURI_LOG_W(TAG, "Failed to read models file header."); + } else if(version != MODELS_FILE_VERSION || furi_string_cmp_str(header, MODELS_FILE_HEADER) != 0) { + FURI_LOG_W(TAG, "Models file version is invalid."); + } else if(!flipper_format_read_uint32(ff, MODELS_FIELD_MODEL_COUNT, &model_count, 1)) { + FURI_LOG_W(TAG, "Failed to read " MODELS_FIELD_MODEL_COUNT "."); + } + + //Free temp variables, close storage. + furi_string_free(header); + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + //Finished, return the count or 0 if error. + return (uint16_t)model_count; +} diff --git a/helpers/protopirate_models.h b/helpers/protopirate_models.h new file mode 100644 index 0000000..2ac69ff --- /dev/null +++ b/helpers/protopirate_models.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include +#include "core/record.h" +#include + +typedef struct ProtoPirateCarModel { + uint16_t index; + FuriString* name; + SubGhzRadioPreset* preset; + int16_t last_preset_index; +} ProtoPirateCarModel; + +bool car_model_get_by_index( + ProtoPirateCarModel* car_model, + uint16_t index, + uint16_t model_count, + SubGhzSetting* app_settings); + +uint16_t car_model_get_count(void); diff --git a/helpers/protopirate_psa_bf_host.c b/helpers/protopirate_psa_bf_host.c index ee7ff07..6a7774c 100644 --- a/helpers/protopirate_psa_bf_host.c +++ b/helpers/protopirate_psa_bf_host.c @@ -110,14 +110,14 @@ static void psa_bf_plugin_unload(ProtoPirateApp* app) { furi_check(app); app->psa_bf_plugin = NULL; - if(app->psa_bf_plugin_manager) { - plugin_manager_free(app->psa_bf_plugin_manager); - app->psa_bf_plugin_manager = NULL; + if(app->plugin_manager) { + plugin_manager_free(app->plugin_manager); + app->plugin_manager = NULL; } - if(app->psa_bf_plugin_resolver) { - composite_api_resolver_free(app->psa_bf_plugin_resolver); - app->psa_bf_plugin_resolver = NULL; + if(app->plugin_resolver) { + composite_api_resolver_free(app->plugin_resolver); + app->plugin_resolver = NULL; } } @@ -126,7 +126,7 @@ bool protopirate_psa_bf_plugin_ensure_loaded(ProtoPirateApp* app) { if(app->psa_bf_plugin) return true; - if(app->psa_bf_plugin_manager || app->psa_bf_plugin_resolver) { + if(app->plugin_manager || app->plugin_resolver) { psa_bf_plugin_unload(app); } @@ -163,8 +163,8 @@ bool protopirate_psa_bf_plugin_ensure_loaded(ProtoPirateApp* app) { return false; } - app->psa_bf_plugin_resolver = resolver; - app->psa_bf_plugin_manager = manager; + app->plugin_resolver = resolver; + app->plugin_manager = manager; app->psa_bf_plugin = plugin; plugin->set_host_api(&protopirate_psa_bf_host_api); return true; diff --git a/helpers/protopirate_settings.c b/helpers/protopirate_settings.c index 4b19117..4a53313 100644 --- a/helpers/protopirate_settings.c +++ b/helpers/protopirate_settings.c @@ -22,6 +22,7 @@ void protopirate_settings_set_defaults(ProtoPirateSettings* settings) { settings->emulate_feature_enabled = false; settings->check_saved = false; settings->datetime_filenames = false; + settings->car_model_index = 0; } void protopirate_settings_load(ProtoPirateSettings* settings) { @@ -134,6 +135,13 @@ void protopirate_settings_load(ProtoPirateSettings* settings) { } settings->datetime_filenames = (datetime_filenames_temp == 1); + // Read Selected Car Model + uint32_t car_model_index_temp = 0; + if(!flipper_format_read_uint32(ff, "CarModelIndex", &car_model_index_temp, 1)) { + car_model_index_temp = 0; + } + settings->car_model_index = car_model_index_temp; + FURI_LOG_I( TAG, "Settings loaded: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d, check_saved=%d, sound = %d", @@ -225,6 +233,11 @@ void protopirate_settings_save(ProtoPirateSettings* settings) { uint32_t datetime_filenames_temp = settings->datetime_filenames ? 1 : 0; if(!flipper_format_write_uint32(ff, "DateTimeFilenames", &datetime_filenames_temp, 1)) { FURI_LOG_E(TAG, "Failed to write Date Time Filenames"); + } + uint32_t car_model_index_temp = settings->car_model_index; + if(!flipper_format_write_uint32(ff, "CarModelIndex", &car_model_index_temp, 1)) { + FURI_LOG_E(TAG, "Failed to write car model"); + break; } write_ok = true; @@ -239,7 +252,6 @@ void protopirate_settings_save(ProtoPirateSettings* settings) { settings->emulate_feature_enabled, settings->check_saved, settings->sound); - } while(false); flipper_format_free(ff); diff --git a/helpers/protopirate_settings.h b/helpers/protopirate_settings.h index ea993ca..b25fbaa 100644 --- a/helpers/protopirate_settings.h +++ b/helpers/protopirate_settings.h @@ -19,6 +19,7 @@ typedef struct { bool emulate_feature_enabled; bool check_saved; bool datetime_filenames; + uint16_t car_model_index; } ProtoPirateSettings; void protopirate_settings_load(ProtoPirateSettings* settings); diff --git a/helpers/protopirate_tool_scene_host.c b/helpers/protopirate_tool_scene_host.c index 3c492b4..1559e0b 100644 --- a/helpers/protopirate_tool_scene_host.c +++ b/helpers/protopirate_tool_scene_host.c @@ -131,14 +131,14 @@ static void protopirate_tool_scene_plugin_unload(ProtoPirateApp* app) { app->tool_scene_plugin = NULL; - if(app->tool_scene_plugin_manager) { - plugin_manager_free(app->tool_scene_plugin_manager); - app->tool_scene_plugin_manager = NULL; + if(app->plugin_manager) { + plugin_manager_free(app->plugin_manager); + app->plugin_manager = NULL; } - if(app->tool_scene_plugin_resolver) { - composite_api_resolver_free(app->tool_scene_plugin_resolver); - app->tool_scene_plugin_resolver = NULL; + if(app->plugin_resolver) { + composite_api_resolver_free(app->plugin_resolver); + app->plugin_resolver = NULL; } } @@ -198,8 +198,8 @@ static bool protopirate_tool_scene_plugin_ensure_loaded( return false; } - app->tool_scene_plugin_resolver = resolver; - app->tool_scene_plugin_manager = manager; + app->plugin_resolver = resolver; + app->plugin_manager = manager; app->tool_scene_plugin = plugin; app->tool_scene_plugin_kind = kind; plugin->set_host_api(&protopirate_tool_scene_host_api); diff --git a/helpers/protopirate_views.c b/helpers/protopirate_views.c index 00eae79..28af194 100644 --- a/helpers/protopirate_views.c +++ b/helpers/protopirate_views.c @@ -5,24 +5,6 @@ #define TAG "ProtoPirateViews" -bool protopirate_ensure_variable_item_list(ProtoPirateApp* app) { - furi_check(app); - if(app->variable_item_list) { - return true; - } - - app->variable_item_list = variable_item_list_alloc(); - if(!app->variable_item_list) { - return false; - } - - view_dispatcher_add_view( - app->view_dispatcher, - ProtoPirateViewVariableItemList, - variable_item_list_get_view(app->variable_item_list)); - return true; -} - bool protopirate_ensure_widget(ProtoPirateApp* app) { furi_check(app); if(app->widget) { @@ -101,7 +83,6 @@ void protopirate_views_free(ProtoPirateApp* app) { if(app->variable_item_list) { FURI_LOG_D(TAG, "Removing variable_item_list view"); view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList); - variable_item_list_free(app->variable_item_list); app->variable_item_list = NULL; } diff --git a/helpers/variable_item_list.c b/helpers/variable_item_list.c new file mode 100644 index 0000000..bb18805 --- /dev/null +++ b/helpers/variable_item_list.c @@ -0,0 +1,688 @@ +#include "helpers/variable_item_list.h" +#include +#include +#include +#include +#include + +/**************************************************************************************************/ +/* ICON ASSETS */ +/**************************************************************************************************/ +struct Icon { + const uint16_t width; + const uint16_t height; + const uint8_t frame_count; + const uint8_t frame_rate; + const uint8_t* const* frames; +}; + +const uint8_t _I_WarningDolphin_45x42_0[] = { + 0x01, 0x00, 0xc6, 0x00, 0x00, 0x1c, 0x22, 0x04, 0x05, 0x7f, 0xfc, 0x1e, 0x20, 0x05, 0x1e, 0x04, + 0x02, 0x30, 0x05, 0x29, 0x84, 0x02, 0xc1, 0x20, 0x02, 0x8c, 0x22, 0x01, 0x80, 0x02, 0x94, 0x10, + 0x32, 0x30, 0x10, 0x10, 0x87, 0xca, 0x84, 0x03, 0x10, 0x42, 0x81, 0x48, 0x28, 0x38, 0x08, 0x04, + 0x3e, 0x01, 0x84, 0x83, 0xe0, 0x30, 0x11, 0x08, 0x05, 0xa2, 0x11, 0x40, 0xa0, 0x4b, 0xc6, 0xc5, + 0x40, 0xd0, 0x56, 0xe0, 0x10, 0x60, 0x29, 0x54, 0xf0, 0x10, 0x18, 0xf0, 0x14, 0x6b, 0xf6, 0x0c, + 0x04, 0x3e, 0x40, 0x05, 0x12, 0x80, 0xc1, 0xe4, 0x01, 0xd2, 0xf8, 0x40, 0xe4, 0x18, 0x09, 0xf4, + 0x03, 0xf1, 0x01, 0x90, 0x40, 0x28, 0x30, 0x0f, 0xe4, 0x00, 0x16, 0x24, 0x11, 0xbf, 0x01, 0x44, + 0xee, 0x53, 0xf0, 0x29, 0xf0, 0x3e, 0x02, 0x91, 0x3b, 0x8c, 0xc3, 0x81, 0x13, 0x90, 0x48, 0x20, + 0x3f, 0xf9, 0xfc, 0x42, 0x60, 0x05, 0x10, 0x98, 0x81, 0x56, 0x11, 0x38, 0x02, 0x9c, 0x1a, 0x31, + 0x1e, 0x02, 0x8f, 0x02, 0x03, 0x1c, 0x90, 0xc0, 0x7c, 0x02, 0xf1, 0xce, 0x02, 0x07, 0x01, 0x1f, + 0x80, 0x63, 0xa8, 0x08, 0x71, 0x3c, 0x8e, 0x39, 0x24, 0x40, 0x51, 0xc7, 0x81, 0x53, 0x0f, 0x3c, + 0x02, 0x9d, 0x1e, 0x38, 0x29, 0x10, 0x29, 0x17, 0xc8, 0x0a, 0x32, 0x3a, 0x00, 0x14, 0x4b, 0xa2, + 0x05, 0x58, 0x98, 0x15, 0x22, 0x20, 0x54, 0x84, 0x81, 0x50, +}; +const uint8_t* const _I_WarningDolphin_45x42[] = {_I_WarningDolphin_45x42_0}; + +const Icon I_WarningDolphin_45x42 = { + .width = 45, + .height = 42, + .frame_count = 1, + .frame_rate = 0, + .frames = _I_WarningDolphin_45x42}; + +const uint8_t _I_Lock_7x8_0[] = { + 0x00, + 0x1c, + 0x22, + 0x22, + 0x7f, + 0x7f, + 0x77, + 0x7f, + 0x3e, +}; +const uint8_t* const _I_Lock_7x8[] = {_I_Lock_7x8_0}; + +const Icon I_Lock_7x8 = + {.width = 7, .height = 8, .frame_count = 1, .frame_rate = 0, .frames = _I_Lock_7x8}; + +/**************************************************************************************************/ + +struct VariableItem { + FuriString* label; + uint8_t current_value_index; + FuriString* current_value_text; + uint8_t values_count; + VariableItemChangeCallback change_callback; + void* context; + + bool locked; + FuriString* locked_message; +}; + +ARRAY_DEF(VariableItemArray, VariableItem, M_POD_OPLIST); //-V658 + +struct VariableItemList { + View* view; + VariableItemListEnterCallback callback; + void* context; + + FuriTimer* scroll_timer; + FuriTimer* locked_timer; +}; + +typedef struct { + VariableItemArray_t items; + uint8_t position; + uint8_t window_position; + + FuriString* header; + size_t scroll_counter; + bool locked_message_visible; +} VariableItemListModel; + +static void variable_item_list_process_up(VariableItemList* variable_item_list); +static void variable_item_list_process_down(VariableItemList* variable_item_list); +static void variable_item_list_process_left(VariableItemList* variable_item_list); +static void variable_item_list_process_right(VariableItemList* variable_item_list); +static void variable_item_list_process_ok(VariableItemList* variable_item_list); + +static size_t variable_item_list_items_on_screen(VariableItemListModel* model) { + size_t res = 4; + return (furi_string_empty(model->header)) ? res : res - 1; +} + +static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { + VariableItemListModel* model = _model; + + const uint8_t item_height = 16; + uint8_t item_width = canvas_width(canvas) - 5; + + canvas_clear(canvas); + + if(!furi_string_empty(model->header)) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 4, 11, furi_string_get_cstr(model->header)); + } + + uint8_t position = 0; + VariableItemArray_it_t it; + + canvas_set_font(canvas, FontSecondary); + for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); + VariableItemArray_next(it)) { + uint8_t item_position = position - model->window_position; + uint8_t items_on_screen = variable_item_list_items_on_screen(model); + uint8_t y_offset = furi_string_empty(model->header) ? 0 : item_height; + + if(item_position < items_on_screen) { + const VariableItem* item = VariableItemArray_cref(it); + uint8_t item_y = y_offset + (item_position * item_height); + uint8_t item_text_y = item_y + item_height - 4; + size_t scroll_counter = 0; + + if(position == model->position) { + canvas_set_color(canvas, ColorBlack); + elements_slightly_rounded_box(canvas, 0, item_y + 1, item_width, item_height - 2); + canvas_set_color(canvas, ColorWhite); + scroll_counter = model->scroll_counter; + if(scroll_counter < 1) { // Show text beginning a little longer + scroll_counter = 0; + } else { + scroll_counter -= 1; + } + } else { + canvas_set_color(canvas, ColorBlack); + } + + uint8_t value_pos_x = 73; + uint8_t label_width = 66; + if(item->locked) { + // Span label up to lock icon + value_pos_x = 110; + label_width = 100; + } else if(item->current_value_index == 0 && furi_string_empty(item->current_value_text)) { + // Only label text, no value text, show longer label + label_width = 109; + } else if(furi_string_size(item->current_value_text) < 4U) { + // Smaller value section for short values + value_pos_x = 80; + label_width = 71; + } + + elements_scrollable_text_line( + canvas, + 6, + item_text_y, + label_width, + item->label, + scroll_counter, + (position != model->position)); + + if(item->locked) { + canvas_draw_icon(canvas, value_pos_x, item_text_y - 8, &I_Lock_7x8); + } else { + if(item->current_value_index > 0) { + canvas_draw_str(canvas, value_pos_x, item_text_y, "<"); + } + + elements_scrollable_text_line( + canvas, + value_pos_x + 6, + item_text_y, + 109 - (value_pos_x), + item->current_value_text, + scroll_counter, + false); + + if(item->current_value_index < (item->values_count - 1)) { + canvas_draw_str(canvas, 115, item_text_y, ">"); + } + } + } + + position++; + } + + elements_scrollbar(canvas, model->position, VariableItemArray_size(model->items)); + + if(model->locked_message_visible) { + //if(momentum_settings.popup_overlay) { + // canvas_draw_overlay(canvas); + //} + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 8, 10, 110, 48); + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); + canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); + canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); + elements_multiline_text_aligned( + canvas, + 84, + 32, + AlignCenter, + AlignCenter, + furi_string_get_cstr( + VariableItemArray_get(model->items, model->position)->locked_message)); + } +} + +void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index) { + furi_check(variable_item_list); + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + uint8_t position = index; + const size_t items_count = VariableItemArray_size(model->items); + uint8_t items_on_screen = variable_item_list_items_on_screen(model); + + if(position >= items_count) { + position = 0; + } + + model->position = position; + model->window_position = position; + + if(model->window_position > 0) { + model->window_position -= 1; + } + + if(items_count <= items_on_screen) { + model->window_position = 0; + } else { + const size_t pos = items_count - items_on_screen; + if(model->window_position > pos) { + model->window_position = pos; + } + } + }, + true); +} + +/* uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list) { + furi_check(variable_item_list); + VariableItemListModel* model = view_get_model(variable_item_list->view); + uint8_t idx = model->position; + view_commit_model(variable_item_list->view, false); + return idx; +}*/ + +/* void variable_item_list_set_header(VariableItemList* variable_item_list, const char* header) { + furi_check(variable_item_list); + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + if(header == NULL) { + furi_string_reset(model->header); + } else { + furi_string_set_str(model->header, header); + } + }, + true); +}*/ + +static bool variable_item_list_input_callback(InputEvent* event, void* context) { + VariableItemList* variable_item_list = context; + furi_assert(variable_item_list); + bool consumed = false; + + bool locked_message_visible = false; + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { locked_message_visible = model->locked_message_visible; }, + false); + + if((event->type != InputTypePress && event->type != InputTypeRelease) && + locked_message_visible) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->locked_message_visible = false; }, + true); + consumed = true; + } else if(event->type == InputTypeShort) { + switch(event->key) { + case InputKeyUp: + consumed = true; + variable_item_list_process_up(variable_item_list); + break; + case InputKeyDown: + consumed = true; + variable_item_list_process_down(variable_item_list); + break; + case InputKeyLeft: + consumed = true; + variable_item_list_process_left(variable_item_list); + break; + case InputKeyRight: + consumed = true; + variable_item_list_process_right(variable_item_list); + break; + case InputKeyOk: + variable_item_list_process_ok(variable_item_list); + break; + default: + break; + } + } else if(event->type == InputTypeRepeat) { + switch(event->key) { + case InputKeyUp: + consumed = true; + variable_item_list_process_up(variable_item_list); + break; + case InputKeyDown: + consumed = true; + variable_item_list_process_down(variable_item_list); + break; + case InputKeyLeft: + consumed = true; + variable_item_list_process_left(variable_item_list); + break; + case InputKeyRight: + consumed = true; + variable_item_list_process_right(variable_item_list); + break; + default: + break; + } + } + + return consumed; +} + +void variable_item_list_process_up(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + uint8_t items_on_screen = variable_item_list_items_on_screen(model); + if(model->position > 0) { + model->position--; + + if((model->position == model->window_position) && (model->window_position > 0)) { + model->window_position--; + } + } else { + model->position = VariableItemArray_size(model->items) - 1; + if(model->position > (items_on_screen - 1)) { + model->window_position = model->position - (items_on_screen - 1); + } + } + model->scroll_counter = 0; + }, + true); +} + +void variable_item_list_process_down(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + uint8_t items_on_screen = variable_item_list_items_on_screen(model); + if(model->position < (VariableItemArray_size(model->items) - 1)) { + model->position++; + if((model->position - model->window_position) > (items_on_screen - 2) && + model->window_position < + (VariableItemArray_size(model->items) - items_on_screen)) { + model->window_position++; + } + } else { + model->position = 0; + model->window_position = 0; + } + model->scroll_counter = 0; + }, + true); +} + +VariableItem* variable_item_list_get_selected_item(VariableItemListModel* model) { + VariableItem* item = NULL; + + VariableItemArray_it_t it; + uint8_t position = 0; + for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); + VariableItemArray_next(it)) { + if(position == model->position) { + break; + } + position++; + } + + item = VariableItemArray_ref(it); + + furi_assert(item); + return item; +} + +void variable_item_list_process_left(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + VariableItem* item = variable_item_list_get_selected_item(model); + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(!item->values_count) { + //No List to change. Send VariableItemListEventCycleLeft to the handler as current value index + //This way no breaking API changes are made. + item->current_value_index = VariableItemListEventCycleLeft; + if(item->change_callback) { + item->change_callback(item); + } + } else if(item->current_value_index > 0) { + item->current_value_index--; + model->scroll_counter = 0; + if(item->change_callback) { + item->change_callback(item); + } + } + }, + true); +} + +void variable_item_list_process_right(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + VariableItem* item = variable_item_list_get_selected_item(model); + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(!item->values_count) { + //No List to change. Send VariableItemListEventCycleLeft to the handler as current value index + //This way no breaking API changes are made. + item->current_value_index = VariableItemListEventCycleRight; + if(item->change_callback) { + item->change_callback(item); + } + } else if(item->current_value_index < (item->values_count - 1)) { + item->current_value_index++; + model->scroll_counter = 0; + if(item->change_callback) { + item->change_callback(item); + } + } + }, + true); +} + +void variable_item_list_process_ok(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + VariableItem* item = variable_item_list_get_selected_item(model); + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(variable_item_list->callback) { + variable_item_list->callback(variable_item_list->context, model->position); + } + }, + true); +} + +static void variable_item_list_scroll_timer_callback(void* context) { + furi_assert(context); + VariableItemList* variable_item_list = context; + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->scroll_counter++; }, + true); +} + +void variable_item_list_locked_timer_callback(void* context) { + furi_assert(context); + VariableItemList* variable_item_list = context; + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->locked_message_visible = false; }, + true); +} + +VariableItemList* variable_item_list_alloc(void) { + VariableItemList* variable_item_list = malloc(sizeof(VariableItemList)); + variable_item_list->view = view_alloc(); + view_set_context(variable_item_list->view, variable_item_list); + view_allocate_model( + variable_item_list->view, ViewModelTypeLocking, sizeof(VariableItemListModel)); + view_set_draw_callback(variable_item_list->view, variable_item_list_draw_callback); + view_set_input_callback(variable_item_list->view, variable_item_list_input_callback); + + variable_item_list->locked_timer = furi_timer_alloc( + variable_item_list_locked_timer_callback, FuriTimerTypeOnce, variable_item_list); + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + VariableItemArray_init(model->items); + model->position = 0; + model->window_position = 0; + model->header = furi_string_alloc(); + model->scroll_counter = 0; + }, + true); + variable_item_list->scroll_timer = furi_timer_alloc( + variable_item_list_scroll_timer_callback, FuriTimerTypePeriodic, variable_item_list); + furi_timer_start(variable_item_list->scroll_timer, 333); + + return variable_item_list; +} + +void variable_item_list_free(VariableItemList* variable_item_list) { + furi_check(variable_item_list); + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + furi_string_free(model->header); + VariableItemArray_it_t it; + for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); + VariableItemArray_next(it)) { + furi_string_free(VariableItemArray_ref(it)->label); + furi_string_free(VariableItemArray_ref(it)->current_value_text); + furi_string_free(VariableItemArray_ref(it)->locked_message); + } + VariableItemArray_clear(model->items); + }, + false); + furi_timer_stop(variable_item_list->scroll_timer); + furi_timer_free(variable_item_list->scroll_timer); + furi_timer_stop(variable_item_list->locked_timer); + furi_timer_free(variable_item_list->locked_timer); + view_free(variable_item_list->view); + free(variable_item_list); + variable_item_list = NULL; +} + +void variable_item_list_reset(VariableItemList* variable_item_list) { + furi_check(variable_item_list); + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + VariableItemArray_it_t it; + for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); + VariableItemArray_next(it)) { + furi_string_free(VariableItemArray_ref(it)->label); + furi_string_free(VariableItemArray_ref(it)->current_value_text); + furi_string_free(VariableItemArray_ref(it)->locked_message); + } + VariableItemArray_reset(model->items); + furi_string_reset(model->header); + }, + false); +} + +View* variable_item_list_get_view(VariableItemList* variable_item_list) { + furi_check(variable_item_list); + return variable_item_list->view; +} + +VariableItem* variable_item_list_add( + VariableItemList* variable_item_list, + const char* label, + uint8_t values_count, + VariableItemChangeCallback change_callback, + void* context) { + VariableItem* item = NULL; + furi_check(label); + furi_check(variable_item_list); + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + item = VariableItemArray_push_new(model->items); + item->label = furi_string_alloc_set(label); + item->values_count = values_count; + item->change_callback = change_callback; + item->context = context; + item->current_value_index = 0; + item->current_value_text = furi_string_alloc(); + item->locked = false; + item->locked_message = furi_string_alloc(); + }, + true); + + return item; +} + +VariableItem* variable_item_list_get(VariableItemList* variable_item_list, uint8_t position) { + furi_check(variable_item_list); + VariableItem* item = NULL; + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + if(position < VariableItemArray_size(model->items)) { + item = VariableItemArray_get(model->items, position); + } + }, + true); + + return item; +} + +void variable_item_list_set_enter_callback( + VariableItemList* variable_item_list, + VariableItemListEnterCallback callback, + void* context) { + furi_check(callback); + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + UNUSED(model); + variable_item_list->callback = callback; + variable_item_list->context = context; + }, + false); +} + +void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index) { + furi_check(item); + item->current_value_index = current_value_index; +} + +/*void variable_item_set_values_count(VariableItem* item, uint8_t values_count) { + furi_check(item); + item->values_count = values_count; +}*/ + +void variable_item_set_item_label(VariableItem* item, const char* label) { + furi_check(item); + furi_check(label); + furi_string_set(item->label, label); +} + +void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text) { + furi_check(item); + furi_string_set(item->current_value_text, current_value_text); +} + +void variable_item_set_locked(VariableItem* item, bool locked, const char* locked_message) { + furi_check(item); + item->locked = locked; + if(locked_message) { + furi_string_set(item->locked_message, locked_message); + } else if(locked && furi_string_empty(item->locked_message)) { + furi_string_set(item->locked_message, "Locked!"); + } +} + +uint8_t variable_item_get_current_value_index(VariableItem* item) { + furi_check(item); + return item->current_value_index; +} + +void* variable_item_get_context(VariableItem* item) { + furi_check(item); + return item->context; +} diff --git a/helpers/variable_item_list.h b/helpers/variable_item_list.h new file mode 100644 index 0000000..6fa4423 --- /dev/null +++ b/helpers/variable_item_list.h @@ -0,0 +1,159 @@ +/** + * @file variable_item_list.h + * GUI: VariableItemList view module API + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//For sending Left and Right events up to app +//When there are ZERO values (no cycling used by list itself) +//This allows easy use of lists that cycle the whole line, +//And use more than 255 values. +typedef enum { + VariableItemListEventCycleReset, + VariableItemListEventCycleLeft, + VariableItemListEventCycleRight, +} VariableItemListEvents; + +typedef struct VariableItemList VariableItemList; +typedef struct VariableItem VariableItem; +typedef void (*VariableItemChangeCallback)(VariableItem* item); +typedef void (*VariableItemListEnterCallback)(void* context, uint32_t index); + +/** Allocate and initialize VariableItemList + * + * @return VariableItemList* + */ +VariableItemList* variable_item_list_alloc(void); + +/** Deinitialize and free VariableItemList + * + * @param variable_item_list VariableItemList instance + */ +void variable_item_list_free(VariableItemList* variable_item_list); + +/** Clear all elements from list + * + * @param variable_item_list VariableItemList instance + */ +void variable_item_list_reset(VariableItemList* variable_item_list); + +/** Get VariableItemList View instance + * + * @param variable_item_list VariableItemList instance + * + * @return View instance + */ +View* variable_item_list_get_view(VariableItemList* variable_item_list); + +/** Add item to VariableItemList + * + * @param variable_item_list VariableItemList instance + * @param label item name + * @param values_count item values count + * @param change_callback called on value change in gui + * @param context item context + * + * @return VariableItem* item instance + */ +VariableItem* variable_item_list_add( + VariableItemList* variable_item_list, + const char* label, + uint8_t values_count, + VariableItemChangeCallback change_callback, + void* context); + +/** Get pre-existing item instance in VariableItemList + * + * @param variable_item_list VariableItemList instance + * @param position index of the item instance to get + * + * @return VariableItem* item instance + */ +VariableItem* variable_item_list_get(VariableItemList* variable_item_list, uint8_t position); + +/** Set enter callback + * + * @param variable_item_list VariableItemList instance + * @param callback VariableItemListEnterCallback instance + * @param context pointer to context + */ +void variable_item_list_set_enter_callback( + VariableItemList* variable_item_list, + VariableItemListEnterCallback callback, + void* context); + +void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index); + +//uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list); + +/** Set optional header for variable item list + * Must be called before adding items OR after adding items and before set_selected_item() + * + * @param variable_item_list VariableItemList instance + * @param header header to set + */ +// void variable_item_list_set_header(VariableItemList* variable_item_list, const char* header); + +/** Set item current selected index + * + * @param item VariableItem* instance + * @param current_value_index The current value index + */ +void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index); + +/** Set number of values for item + * + * @param item VariableItem* instance + * @param values_count The new values count + */ +//void variable_item_set_values_count(VariableItem* item, uint8_t values_count); + +/** Set new label for item + * + * @param item VariableItem* instance + * @param label The new label text + */ +void variable_item_set_item_label(VariableItem* item, const char* label); + +/** Set item current selected text + * + * @param item VariableItem* instance + * @param current_value_text The current value text + */ +void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text); + +/** Set item locked state and text + * Locked message is saved, you can set_locked(item, false, "Message") and then set_locked(item, true, NULL), it will use previous locked message + * + * @param item VariableItem* instance + * @param locked Is item locked boolean + * @param locked_message The locked message text + */ +void variable_item_set_locked(VariableItem* item, bool locked, const char* locked_message); + +/** Get item current selected index + * + * @param item VariableItem* instance + * + * @return uint8_t current selected index + */ +uint8_t variable_item_get_current_value_index(VariableItem* item); + +/** Get item context + * + * @param item VariableItem* instance + * + * @return void* item context + */ +void* variable_item_get_context(VariableItem* item); + +#ifdef __cplusplus +} +#endif diff --git a/keystore/models.txt b/keystore/models.txt new file mode 100644 index 0000000..c1afeec --- /dev/null +++ b/keystore/models.txt @@ -0,0 +1,28 @@ +Filetype: ProtoPirate Car Models Database +Version: 2 + +ModelCount: 6 + +ModelName1: 433.92 Mhz AM650 +Frequency1: 433920000 +PresetName1: FuriHalSubGhzPresetOok650Async + +ModelName2: 433.92 Mhz FM476 +Frequency2: 433920000 +PresetName2: FuriHalSubGhzPreset2FSKDev476Async + +ModelName3: 315 Mhz AM650 +Frequency3: 315000000 +PresetName3: FuriHalSubGhzPresetOok650Async + +ModelName4: 315 Mhz FM476 +Frequency4: 315000000 +PresetName4: FuriHalSubGhzPreset2FSKDev476Async + +ModelName5: Ford Falcon BA-FGX +Frequency5: 433920000 +PresetName5: FuriHalSubGhzPresetOok650Async + +ModelName6: Ford Territory SX-SYII +Frequency6: 433920000 +PresetName6: FuriHalSubGhzPresetOok650Async diff --git a/protopirate_app.c b/protopirate_app.c index bf2b0dd..56eb459 100644 --- a/protopirate_app.c +++ b/protopirate_app.c @@ -12,6 +12,73 @@ #define TAG "ProtoPirateApp" +// ----------------------------------------------------------------------------- +// Plugin load / unload +// ----------------------------------------------------------------------------- +void config_plugin_unload(ProtoPirateApp* app) { + furi_check(app); + + app->config_plugin = NULL; + + if(app->plugin_manager) { + plugin_manager_free(app->plugin_manager); + app->plugin_manager = NULL; + } + + if(app->plugin_resolver) { + composite_api_resolver_free(app->plugin_resolver); + app->plugin_resolver = NULL; + } +} + +bool config_plugin_load(ProtoPirateApp* app) { + furi_check(app); + + if(app->emulate_plugin) return true; + + if(app->plugin_manager || app->plugin_resolver) { + config_plugin_unload(app); + } + + CompositeApiResolver* resolver = composite_api_resolver_alloc(); + if(!resolver) { + FURI_LOG_E(TAG, "Failed to allocate config plugin resolver"); + return false; + } + composite_api_resolver_add(resolver, firmware_api_interface); + + PluginManager* manager = plugin_manager_alloc( + PROTOPIRATE_CONFIG_PLUGIN_APP_ID, + PROTOPIRATE_CONFIG_PLUGIN_API_VERSION, + composite_api_resolver_get(resolver)); + if(!manager) { + FURI_LOG_E(TAG, "Failed to allocate config plugin manager"); + composite_api_resolver_free(resolver); + return false; + } + + PluginManagerError error = plugin_manager_load_single(manager, CONFIG_PLUGIN_PATH); + if(error != PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load config plugin %s: %d", CONFIG_PLUGIN_PATH, (int)error); + plugin_manager_free(manager); + composite_api_resolver_free(resolver); + return false; + } + + const ProtoPirateConfigPlugin* plugin = plugin_manager_get_ep(manager, 0U); + if(!plugin || !plugin->on_enter || !plugin->on_exit) { + FURI_LOG_E(TAG, "Config plugin entry point is invalid"); + plugin_manager_free(manager); + composite_api_resolver_free(resolver); + return false; + } + + app->plugin_resolver = resolver; + app->plugin_manager = manager; + app->config_plugin = plugin; + return true; +} + static bool protopirate_app_custom_event_callback(void* context, uint32_t event) { furi_check(context); ProtoPirateApp* app = context; @@ -154,7 +221,33 @@ ProtoPirateApp* protopirate_app_alloc() { settings.auto_save, settings.hopping_enabled); - protopirate_preset_init(app, preset_name, frequency, preset_data, preset_data_size); + config_plugin_load(app); + app->car_models_count = app->config_plugin->car_model_get_count(); + app->selected_model = malloc(sizeof(ProtoPirateCarModel)); + app->selected_model->name = furi_string_alloc(); + app->selected_model->preset = NULL; // important initialization + app->selected_model->index = 0; // optional but clean + app->variable_item_list = NULL; + + //Grab selected car model. + if(settings.car_model_index) { + app->config_plugin->car_model_get_by_index( + app->selected_model, settings.car_model_index, app->car_models_count, app->setting); + app->selected_model->last_preset_index = settings.preset_index; + + protopirate_preset_init( + app, + furi_string_get_cstr(app->selected_model->preset->name), + app->selected_model->preset->frequency, + app->selected_model->preset->data, + app->selected_model->preset->data_size); + } else { + app->config_plugin->car_model_get_by_index( + app->selected_model, 0, app->car_models_count, app->setting); + + protopirate_preset_init(app, preset_name, frequency, preset_data, preset_data_size); + } + config_plugin_unload(app); // Apply hopping state from settings app->txrx->hopper_state = settings.hopping_enabled ? ProtoPirateHopperStateRunning : @@ -189,16 +282,48 @@ void protopirate_app_free(ProtoPirateApp* app) { settings.emulate_feature_enabled = false; #endif - // Find current preset index - settings.preset_index = 0; - const char* current_preset = furi_string_get_cstr(app->txrx->preset->name); - for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { - if(strcmp(subghz_setting_get_preset_name(app->setting, i), current_preset) == 0) { - settings.preset_index = i; - break; + //Get the selected Model, and get the preset to save. + if(app->selected_model && app->selected_model->index) { + //Get Preset Index before model was selected. + settings.car_model_index = app->selected_model->index; + settings.preset_index = app->selected_model->last_preset_index; + } else { + // Find current preset index + settings.car_model_index = 0; //Clear the selected model. + settings.preset_index = 0; + const char* current_preset = furi_string_get_cstr(app->txrx->preset->name); + for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { + if(strcmp(subghz_setting_get_preset_name(app->setting, i), current_preset) == 0) { + settings.preset_index = i; + break; + } } } + //Free the Model Name + furi_string_free(app->selected_model->name); + app->selected_model->index = 0; + + //Free the preset information. + if(app->selected_model->preset) { + //Free the preset data + if((app->selected_model)->preset->data) { + free(app->selected_model->preset->data); + app->selected_model->preset->data = NULL; + } + + //Free the Preset name + furi_string_free(app->selected_model->preset->name); + + //Free the preset. + free(app->selected_model->preset); + app->selected_model->preset = NULL; + } + + //Free the Model. + free(app->selected_model); + app->selected_model = NULL; + FURI_LOG_I( TAG, "Saving settings: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d", diff --git a/protopirate_app_i.h b/protopirate_app_i.h index 5dd71f1..d63afcd 100644 --- a/protopirate_app_i.h +++ b/protopirate_app_i.h @@ -3,7 +3,6 @@ #include #include "helpers/protopirate_types.h" -#include "helpers/protopirate_settings.h" #include "scenes/protopirate_scene.h" #include "views/protopirate_receiver.h" #include "protopirate_history.h" @@ -13,7 +12,7 @@ #include #include #include -#include + #include #include #include @@ -33,16 +32,22 @@ #ifdef ENABLE_EMULATE_FEATURE #include "scenes/plugins/protopirate_emulate_plugin.h" #endif +#include "scenes/plugins/protopirate_config_plugin.h" #include "scenes/plugins/protopirate_psa_bf_plugin.h" #include "scenes/plugins/protopirate_tool_scene_plugin.h" #include "helpers/protopirate_views.h" #include "helpers/protopirate_radio.h" #include "helpers/protopirate_protocol_plugin_host.h" #include "helpers/protopirate_txrx.h" +#include "helpers/protopirate_models.h" +#include +#include "helpers/protopirate_settings.h" + +#define CONFIG_PLUGIN_PATH APP_ASSETS_PATH("plugins/protopirate_config_plugin.fal") #define PROTOPIRATE_KEYSTORE_DIR_NAME APP_ASSETS_PATH("encrypted") -typedef struct ProtoPirateApp ProtoPirateApp; +typedef struct VariableItemList VariableItemList; typedef struct ProtoPirateTxRx { SubGhzWorker* worker; @@ -94,29 +99,28 @@ struct ProtoPirateApp { bool save_from_saved_info; bool emulate_disabled_for_loaded; bool emulate_feature_enabled; + CompositeApiResolver* plugin_resolver; + PluginManager* plugin_manager; #ifdef ENABLE_EMULATE_FEATURE #define EMULATE_NAV_NONE 0U #define EMULATE_NAV_POP 1U #define EMULATE_NAV_STOP_APP 2U - CompositeApiResolver* emulate_plugin_resolver; - PluginManager* emulate_plugin_manager; const ProtoPirateEmulatePlugin* emulate_plugin; uint8_t emulate_nav_pending; #endif - CompositeApiResolver* psa_bf_plugin_resolver; - PluginManager* psa_bf_plugin_manager; + const ProtoPirateConfigPlugin* config_plugin; const ProtoPiratePsaBfPlugin* psa_bf_plugin; - CompositeApiResolver* tool_scene_plugin_resolver; - PluginManager* tool_scene_plugin_manager; const ProtoPirateToolScenePlugin* tool_scene_plugin; ProtoPirateToolScenePluginKind tool_scene_plugin_kind; - #define TOOL_SCENE_NAV_NONE 0U #define TOOL_SCENE_NAV_POP 1U #define TOOL_SCENE_NAV_NEXT 2U #define TOOL_SCENE_NAV_SEARCH_PREVIOUS 3U uint8_t tool_scene_nav_pending; uint32_t tool_scene_nav_target; + + ProtoPirateCarModel* selected_model; + uint32_t car_models_count; }; #ifdef ENABLE_EMULATE_FEATURE @@ -133,6 +137,9 @@ bool protopirate_tool_scene_on_event(void* app, SceneManagerEvent event); void protopirate_tool_scene_on_exit(void* app); void protopirate_tool_scene_plugin_release(ProtoPirateApp* app); +bool config_plugin_load(ProtoPirateApp* app); +void config_plugin_unload(ProtoPirateApp* app); + void protopirate_app_free(ProtoPirateApp* app); static const NotificationSequence sequence_tx = { diff --git a/scenes/plugins/protopirate_config_plugin.c b/scenes/plugins/protopirate_config_plugin.c new file mode 100644 index 0000000..38b7b67 --- /dev/null +++ b/scenes/plugins/protopirate_config_plugin.c @@ -0,0 +1,499 @@ +#include "protopirate_config_plugin.h" +#include "../../protopirate_app_i.h" +#include "../../helpers/protopirate_models.h" + +#define ON_OFF_COUNT 2 +const char* const on_off_text[ON_OFF_COUNT] = { + "OFF", + "ON", +}; + +const uint32_t hopping_value[ON_OFF_COUNT] = { + ProtoPirateHopperStateOFF, + ProtoPirateHopperStateRunning, +}; + +const char* const sequence_time_text[ON_OFF_COUNT] = { + "Sequential", + "Time", +}; + +#ifdef ENABLE_EMULATE_FEATURE +#define TX_POWER_COUNT 9 +const char* const tx_power_text[TX_POWER_COUNT] = { + "Preset", + "10dBm +", + "7dBm", + "5dBm", + "0dBm", + "-10dBm", + "-15dBm", + "-20dBm", + "-30dBm", +}; +#endif + +bool protopirate_ensure_variable_item_list(ProtoPirateApp* app) { + furi_check(app); + if(app->variable_item_list) { + view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList); + } + + app->variable_item_list = variable_item_list_alloc(); + if(!app->variable_item_list) { + return false; + } + + view_dispatcher_add_view( + app->view_dispatcher, + ProtoPirateViewVariableItemList, + variable_item_list_get_view(app->variable_item_list)); + return true; +} + +static void protopirate_scene_receiver_config_set_frequency(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + if(app->txrx->hopper_state == ProtoPirateHopperStateOFF) { + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_frequency(app->setting, index) / 1000000, + (subghz_setting_get_frequency(app->setting, index) % 1000000) / 10000); + variable_item_set_current_value_text(item, text_buf); + app->txrx->preset->frequency = subghz_setting_get_frequency(app->setting, index); + } else { + variable_item_set_current_value_index( + item, subghz_setting_get_frequency_default_index(app->setting)); + } +} + +static void protopirate_scene_receiver_config_set_preset(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text( + item, subghz_setting_get_preset_name(app->setting, index)); + protopirate_preset_init( + app, + subghz_setting_get_preset_name(app->setting, index), + app->txrx->preset->frequency, + subghz_setting_get_preset_data(app->setting, index), + subghz_setting_get_preset_data_size(app->setting, index)); + + if(!protopirate_refresh_protocol_registry(app, false)) { + notification_message(app->notifications, &sequence_error); + } +} + +static void protopirate_scene_receiver_config_set_hopping_running(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, on_off_text[index]); + if(hopping_value[index] == ProtoPirateHopperStateOFF) { + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_default_frequency(app->setting) / 1000000, + (subghz_setting_get_default_frequency(app->setting) % 1000000) / 10000); + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig), + text_buf); + app->txrx->preset->frequency = subghz_setting_get_default_frequency(app->setting); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig), + " -----"); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } + + app->txrx->hopper_state = hopping_value[index]; +} + +static void protopirate_scene_receiver_config_set_model(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t direction = variable_item_get_current_value_index(item); + + //Get current selection + uint16_t model_index; + model_index = + (app->selected_model && app->selected_model->index) ? app->selected_model->index : 0; + uint16_t old_model_index = model_index; + + //Cycle the index. + switch(direction) { + case VariableItemListEventCycleReset: { + model_index = 0; + break; + } + case VariableItemListEventCycleLeft: { + if(model_index > 0) + model_index--; + else + model_index = app->car_models_count; + break; + } + case VariableItemListEventCycleRight: { + if(model_index < app->car_models_count) + model_index++; + else + model_index = 0; + } + } + + //Get a Car Model object, and dont forget to shut down on app free! + car_model_get_by_index( + (ProtoPirateCarModel*)app->selected_model, + model_index, + app->car_models_count, + app->setting); + + //Add he car model the list, with the correct selection and text. + variable_item_set_item_label(item, furi_string_get_cstr(app->selected_model->name)); + variable_item_set_current_value_text(item, ""); + variable_item_set_current_value_index(item, 0); + + //Restore Original Preset. + VariableItem* freq_menu = + variable_item_list_get(app->variable_item_list, ProtoPirateSettingIndexFrequency); + VariableItem* hop_menu = + variable_item_list_get(app->variable_item_list, ProtoPirateSettingIndexHopping); + VariableItem* preset_menu = + variable_item_list_get(app->variable_item_list, ProtoPirateSettingIndexModulation); + + //set the Preset, Frequency and Hopper off or restore. + if(model_index) { + variable_item_set_current_value_text(freq_menu, "Locked"); + variable_item_set_current_value_text(hop_menu, "Locked"); + app->txrx->hopper_state = ProtoPirateHopperStateOFF; + app->txrx->preset->frequency = app->selected_model->preset->frequency; + + //Save Original Preset. + if(old_model_index) { + app->selected_model->last_preset_index = subghz_setting_get_inx_preset_by_name( + app->setting, furi_string_get_cstr(app->txrx->preset->name)); + } + + protopirate_preset_init( + app, + furi_string_get_cstr(app->selected_model->preset->name), + app->selected_model->preset->frequency, + app->selected_model->preset->data, + app->selected_model->preset->data_size); + + } else { + //Restore Original Preset. + protopirate_scene_receiver_config_set_frequency(freq_menu); + protopirate_scene_receiver_config_set_hopping_running(hop_menu); + + protopirate_preset_init( + app, + subghz_setting_get_preset_name(app->setting, app->selected_model->last_preset_index), + app->txrx->preset->frequency, + subghz_setting_get_preset_data(app->setting, app->selected_model->last_preset_index), + subghz_setting_get_preset_data_size( + app->setting, app->selected_model->last_preset_index)); + + variable_item_set_current_value_text( + preset_menu, + subghz_setting_get_preset_name(app->setting, app->selected_model->last_preset_index)); + } + + //Refresh the protocol registry now that we have a new Modulation Type. + if(!protopirate_refresh_protocol_registry(app, false)) { + notification_message(app->notifications, &sequence_error); + } + + //Lock or Unlock the menus. + bool lock = (app->selected_model->index != 0); + variable_item_set_locked(freq_menu, lock, "Turn off\nCar Model\nto do that!"); + variable_item_set_locked(hop_menu, lock, "Turn off\nCar Model\nto do that!"); + variable_item_set_locked(preset_menu, lock, "Turn off\nCar Model\nto do that!"); +} + +static uint8_t + protopirate_scene_receiver_config_next_frequency(const uint32_t value, void* context) { + furi_check(context); + ProtoPirateApp* app = context; + uint8_t index = 0; + for(uint8_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) { + if(value == subghz_setting_get_frequency(app->setting, i)) { + index = i; + break; + } else { + index = subghz_setting_get_frequency_default_index(app->setting); + } + } + return index; +} + +static uint8_t + protopirate_scene_receiver_config_next_preset(const char* preset_name, void* context) { + furi_check(context); + ProtoPirateApp* app = context; + uint8_t index = 0; + for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { + if(!strcmp(subghz_setting_get_preset_name(app->setting, i), preset_name)) { + index = i; + break; + } + } + return index; +} + +static uint8_t protopirate_scene_receiver_config_hopper_value_index( + const uint32_t value, + const uint32_t values[], + uint8_t values_count, + void* context) { + furi_check(context); + UNUSED(values_count); + ProtoPirateApp* app = context; + + if(value == values[0]) { + return 0; + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig), + " -----"); + return 1; + } +} + +static void protopirate_scene_receiver_config_set_auto_save(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + app->auto_save = (index == 1); + variable_item_set_current_value_text(item, on_off_text[index]); +} + +static void protopirate_scene_receiver_config_set_datetime_filenames(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + app->datetime_filenames = (index == 1); + variable_item_set_current_value_text(item, sequence_time_text[index]); +} + +static void protopirate_scene_receiver_config_set_check_saved(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + app->check_saved = (index == 1); + variable_item_set_current_value_text(item, on_off_text[index]); +} + +static void protopirate_scene_receiver_config_set_sound(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + app->sound = (index == 1); + variable_item_set_current_value_text(item, on_off_text[index]); +} + +#ifdef ENABLE_EMULATE_FEATURE +static void protopirate_scene_receiver_config_set_tx_power(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + app->tx_power = index; + variable_item_set_current_value_text(item, tx_power_text[index]); +} +#endif + +static void + protopirate_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { + furi_check(context); + ProtoPirateApp* app = context; + + switch(index) { + case ProtoPirateSettingIndexCarModel: { + //Reset the Models Menu + VariableItem* model_menu = + variable_item_list_get(app->variable_item_list, ProtoPirateSettingIndexCarModel); + variable_item_set_current_value_index(model_menu, 0); + protopirate_scene_receiver_config_set_model(model_menu); + break; + } + case ProtoPirateSettingIndexLock: { + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventSceneSettingLock); + break; + } + } +} + +static void plugin_on_enter(void* context) { + ProtoPirateApp* app = context; + VariableItem* item; + uint8_t value_index; + + if(!protopirate_ensure_variable_item_list(app)) { + notification_message(app->notifications, &sequence_error); + scene_manager_previous_scene(app->scene_manager); + return; + } + + //Add he car model the list, with the correct selection and text. + item = variable_item_list_add( + app->variable_item_list, + furi_string_get_cstr(app->selected_model->name), + 0, //Plus NONE + protopirate_scene_receiver_config_set_model, + app); + + item = variable_item_list_add( + app->variable_item_list, + "Frequency:", + subghz_setting_get_frequency_count(app->setting), + protopirate_scene_receiver_config_set_frequency, + app); + value_index = + protopirate_scene_receiver_config_next_frequency(app->txrx->preset->frequency, app); + scene_manager_set_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig, (uint32_t)item); + variable_item_set_current_value_index(item, value_index); + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_frequency(app->setting, value_index) / 1000000, + (subghz_setting_get_frequency(app->setting, value_index) % 1000000) / 10000); + variable_item_set_current_value_text(item, text_buf); + variable_item_set_locked( + item, + app->selected_model && (app->selected_model->index), + "Turn off\nCar Model\nto do that!"); + + item = variable_item_list_add( + app->variable_item_list, + "Hopping:", + ON_OFF_COUNT, + protopirate_scene_receiver_config_set_hopping_running, + app); + value_index = protopirate_scene_receiver_config_hopper_value_index( + app->txrx->hopper_state, hopping_value, ON_OFF_COUNT, app); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, on_off_text[value_index]); + variable_item_set_locked( + item, + app->selected_model && (app->selected_model->index), + "Turn off\nCar Model\nto do that!"); + + item = variable_item_list_add( + app->variable_item_list, + "Modulation:", + subghz_setting_get_preset_count(app->setting), + protopirate_scene_receiver_config_set_preset, + app); + value_index = protopirate_scene_receiver_config_next_preset( + furi_string_get_cstr(app->txrx->preset->name), app); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text( + item, subghz_setting_get_preset_name(app->setting, value_index)); + variable_item_set_locked( + item, + app->selected_model && (app->selected_model->index), + "Turn off\nCar Model\nto do that!"); + +#ifdef ENABLE_EMULATE_FEATURE + // TX power option + item = variable_item_list_add( + app->variable_item_list, + "TX Power:", + TX_POWER_COUNT, + protopirate_scene_receiver_config_set_tx_power, + app); + variable_item_set_current_value_index(item, app->tx_power); + variable_item_set_current_value_text(item, tx_power_text[app->tx_power]); +#endif + // Auto-save option + item = variable_item_list_add( + app->variable_item_list, + "Auto-Save:", + ON_OFF_COUNT, + protopirate_scene_receiver_config_set_auto_save, + app); + variable_item_set_current_value_index(item, app->auto_save ? 1 : 0); + variable_item_set_current_value_text(item, on_off_text[app->auto_save ? 1 : 0]); + // Date/time filenames option + item = variable_item_list_add( + app->variable_item_list, + "Filenames:", + 2, + protopirate_scene_receiver_config_set_datetime_filenames, + app); + variable_item_set_current_value_index(item, app->datetime_filenames ? 1 : 0); + variable_item_set_current_value_text( + item, sequence_time_text[app->datetime_filenames ? 1 : 0]); + + item = variable_item_list_add( + app->variable_item_list, + "Check Saved:", + ON_OFF_COUNT, + protopirate_scene_receiver_config_set_check_saved, + app); + variable_item_set_current_value_index(item, app->check_saved ? 1 : 0); + variable_item_set_current_value_text(item, on_off_text[app->check_saved ? 1 : 0]); + + // Sound option + item = variable_item_list_add( + app->variable_item_list, + "Sound:", + ON_OFF_COUNT, + protopirate_scene_receiver_config_set_sound, + app); + variable_item_set_current_value_index(item, app->sound); + variable_item_set_current_value_text(item, on_off_text[app->sound ? 1 : 0]); + + variable_item_list_add(app->variable_item_list, "Lock Keyboard", 1, NULL, NULL); + variable_item_list_set_enter_callback( + app->variable_item_list, protopirate_scene_receiver_config_var_list_enter_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewVariableItemList); +} + +static void plugin_on_exit(void* context) { + ProtoPirateApp* app = context; + + //Reset the variable item list. + variable_item_list_set_selected_item(app->variable_item_list, 0); + variable_item_list_reset(app->variable_item_list); + variable_item_list_free(app->variable_item_list); + //app->variable_item_list = NULL; +} + +static const ProtoPirateConfigPlugin protopirate_config_plugin = { + .plugin_name = "ProtoPirate Config", + .car_model_get_by_index = car_model_get_by_index, + .car_model_get_count = car_model_get_count, + .on_enter = plugin_on_enter, + .on_exit = plugin_on_exit, +}; + +static const FlipperAppPluginDescriptor protopirate_config_plugin_descriptor = { + .appid = PROTOPIRATE_CONFIG_PLUGIN_APP_ID, + .ep_api_version = PROTOPIRATE_CONFIG_PLUGIN_API_VERSION, + .entry_point = &protopirate_config_plugin, +}; + +const FlipperAppPluginDescriptor* protopirate_config_plugin_ep(void) { + return &protopirate_config_plugin_descriptor; +} diff --git a/scenes/plugins/protopirate_config_plugin.h b/scenes/plugins/protopirate_config_plugin.h new file mode 100644 index 0000000..1507e1f --- /dev/null +++ b/scenes/plugins/protopirate_config_plugin.h @@ -0,0 +1,35 @@ +#pragma once + +#include "helpers/protopirate_models.h" +#include +#include "helpers/variable_item_list.h" + +#define PROTOPIRATE_CONFIG_PLUGIN_APP_ID "protopirate_config_plugin" +#define PROTOPIRATE_CONFIG_PLUGIN_API_VERSION 2U + +enum ProtoPirateSettingIndex { + ProtoPirateSettingIndexCarModel, + ProtoPirateSettingIndexFrequency, + ProtoPirateSettingIndexHopping, + ProtoPirateSettingIndexModulation, +#ifdef ENABLE_EMULATE_FEATURE + ProtoPirateSettingIndexTXPower, +#endif + ProtoPirateSettingIndexAutoSave, + ProtoPirateSettingIndexDateTimeFilenames, + ProtoPirateSettingIndexSound, + ProtoPirateSettingIndexCheckSaved, + ProtoPirateSettingIndexLock, +}; + +typedef struct ProtoPirateConfigPlugin { + const char* plugin_name; + bool (*car_model_get_by_index)( + ProtoPirateCarModel* car_model, + uint16_t index, + uint16_t model_count, + SubGhzSetting* app_settings); + uint16_t (*car_model_get_count)(void); + void (*on_enter)(void* app); + void (*on_exit)(void* app); +} ProtoPirateConfigPlugin; diff --git a/scenes/protopirate_scene_emulate.c b/scenes/protopirate_scene_emulate.c index f763e36..eb7e7e8 100644 --- a/scenes/protopirate_scene_emulate.c +++ b/scenes/protopirate_scene_emulate.c @@ -94,14 +94,14 @@ static void emulate_plugin_unload(ProtoPirateApp* app) { app->emulate_plugin = NULL; - if(app->emulate_plugin_manager) { - plugin_manager_free(app->emulate_plugin_manager); - app->emulate_plugin_manager = NULL; + if(app->plugin_manager) { + plugin_manager_free(app->plugin_manager); + app->plugin_manager = NULL; } - if(app->emulate_plugin_resolver) { - composite_api_resolver_free(app->emulate_plugin_resolver); - app->emulate_plugin_resolver = NULL; + if(app->plugin_resolver) { + composite_api_resolver_free(app->plugin_resolver); + app->plugin_resolver = NULL; } } @@ -110,7 +110,7 @@ static bool emulate_plugin_load(ProtoPirateApp* app) { if(app->emulate_plugin) return true; - if(app->emulate_plugin_manager || app->emulate_plugin_resolver) { + if(app->plugin_manager || app->plugin_resolver) { emulate_plugin_unload(app); } @@ -148,8 +148,8 @@ static bool emulate_plugin_load(ProtoPirateApp* app) { return false; } - app->emulate_plugin_resolver = resolver; - app->emulate_plugin_manager = manager; + app->plugin_resolver = resolver; + app->plugin_manager = manager; app->emulate_plugin = plugin; plugin->set_host_api(&protopirate_emulate_host_api); diff --git a/scenes/protopirate_scene_receiver_config.c b/scenes/protopirate_scene_receiver_config.c index 616fb80..77265b0 100644 --- a/scenes/protopirate_scene_receiver_config.c +++ b/scenes/protopirate_scene_receiver_config.c @@ -1,334 +1,16 @@ // scenes/protopirate_scene_receiver_config.c #include "../protopirate_app_i.h" -enum ProtoPirateSettingIndex { - ProtoPirateSettingIndexFrequency, - ProtoPirateSettingIndexHopping, - ProtoPirateSettingIndexModulation, -#ifdef ENABLE_EMULATE_FEATURE - ProtoPirateSettingIndexTXPower, -#endif - ProtoPirateSettingIndexAutoSave, - ProtoPirateSettingIndexDateTimeFilenames, - ProtoPirateSettingIndexSound, - ProtoPirateSettingIndexCheckSaved, - ProtoPirateSettingIndexLock, -}; - -#define ON_OFF_COUNT 2 -const char* const on_off_text[ON_OFF_COUNT] = { - "OFF", - "ON", -}; - -const uint32_t hopping_value[ON_OFF_COUNT] = { - ProtoPirateHopperStateOFF, - ProtoPirateHopperStateRunning, -}; - -const char* const sequence_time_text[ON_OFF_COUNT] = { - "Sequential", - "Time", -}; - -#ifdef ENABLE_EMULATE_FEATURE -#define TX_POWER_COUNT 9 -const char* const tx_power_text[TX_POWER_COUNT] = { - "Preset", - "10dBm +", - "7dBm", - "5dBm", - "0dBm", - "-10dBm", - "-15dBm", - "-20dBm", - "-30dBm", -}; -#endif - -uint8_t protopirate_scene_receiver_config_next_frequency(const uint32_t value, void* context) { - furi_check(context); - ProtoPirateApp* app = context; - uint8_t index = 0; - for(uint8_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) { - if(value == subghz_setting_get_frequency(app->setting, i)) { - index = i; - break; - } else { - index = subghz_setting_get_frequency_default_index(app->setting); - } - } - return index; -} - -uint8_t protopirate_scene_receiver_config_next_preset(const char* preset_name, void* context) { - furi_check(context); - ProtoPirateApp* app = context; - uint8_t index = 0; - for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { - if(!strcmp(subghz_setting_get_preset_name(app->setting, i), preset_name)) { - index = i; - break; - } - } - return index; -} - -uint8_t protopirate_scene_receiver_config_hopper_value_index( - const uint32_t value, - const uint32_t values[], - uint8_t values_count, - void* context) { - furi_check(context); - UNUSED(values_count); - ProtoPirateApp* app = context; - - if(value == values[0]) { - return 0; - } else { - variable_item_set_current_value_text( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig), - " -----"); - return 1; - } -} - -static void protopirate_scene_receiver_config_set_frequency(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - if(app->txrx->hopper_state == ProtoPirateHopperStateOFF) { - char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_frequency(app->setting, index) / 1000000, - (subghz_setting_get_frequency(app->setting, index) % 1000000) / 10000); - variable_item_set_current_value_text(item, text_buf); - app->txrx->preset->frequency = subghz_setting_get_frequency(app->setting, index); - } else { - variable_item_set_current_value_index( - item, subghz_setting_get_frequency_default_index(app->setting)); - } -} - -static void protopirate_scene_receiver_config_set_preset(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text( - item, subghz_setting_get_preset_name(app->setting, index)); - protopirate_preset_init( - app, - subghz_setting_get_preset_name(app->setting, index), - app->txrx->preset->frequency, - subghz_setting_get_preset_data(app->setting, index), - subghz_setting_get_preset_data_size(app->setting, index)); - - if(!protopirate_refresh_protocol_registry(app, false)) { - notification_message(app->notifications, &sequence_error); - } -} - -static void protopirate_scene_receiver_config_set_hopping_running(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, on_off_text[index]); - if(hopping_value[index] == ProtoPirateHopperStateOFF) { - char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_default_frequency(app->setting) / 1000000, - (subghz_setting_get_default_frequency(app->setting) % 1000000) / 10000); - variable_item_set_current_value_text( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig), - text_buf); - app->txrx->preset->frequency = subghz_setting_get_default_frequency(app->setting); - variable_item_set_current_value_index( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig), - subghz_setting_get_frequency_default_index(app->setting)); - } else { - variable_item_set_current_value_text( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig), - " -----"); - variable_item_set_current_value_index( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig), - subghz_setting_get_frequency_default_index(app->setting)); - } - - app->txrx->hopper_state = hopping_value[index]; -} - -static void protopirate_scene_receiver_config_set_auto_save(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - app->auto_save = (index == 1); - variable_item_set_current_value_text(item, on_off_text[index]); -} - -static void protopirate_scene_receiver_config_set_datetime_filenames(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - app->datetime_filenames = (index == 1); - variable_item_set_current_value_text(item, sequence_time_text[index]); -} - -static void protopirate_scene_receiver_config_set_check_saved(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - app->check_saved = (index == 1); - variable_item_set_current_value_text(item, on_off_text[index]); -} - -static void protopirate_scene_receiver_config_set_sound(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - app->sound = (index == 1); - - variable_item_set_current_value_text(item, on_off_text[index]); -} - -#ifdef ENABLE_EMULATE_FEATURE -static void protopirate_scene_receiver_config_set_tx_power(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - app->tx_power = index; - variable_item_set_current_value_text(item, tx_power_text[index]); -} -#endif - -static void - protopirate_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { - furi_check(context); - ProtoPirateApp* app = context; - if(index == ProtoPirateSettingIndexLock) { - view_dispatcher_send_custom_event( - app->view_dispatcher, ProtoPirateCustomEventSceneSettingLock); - } -} - void protopirate_scene_receiver_config_on_enter(void* context) { ProtoPirateApp* app = context; - VariableItem* item; - uint8_t value_index; - if(!protopirate_ensure_variable_item_list(app)) { + if(!config_plugin_load(app)) { notification_message(app->notifications, &sequence_error); scene_manager_previous_scene(app->scene_manager); return; } - item = variable_item_list_add( - app->variable_item_list, - "Frequency:", - subghz_setting_get_frequency_count(app->setting), - protopirate_scene_receiver_config_set_frequency, - app); - value_index = - protopirate_scene_receiver_config_next_frequency(app->txrx->preset->frequency, app); - scene_manager_set_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig, (uint32_t)item); - variable_item_set_current_value_index(item, value_index); - char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_frequency(app->setting, value_index) / 1000000, - (subghz_setting_get_frequency(app->setting, value_index) % 1000000) / 10000); - variable_item_set_current_value_text(item, text_buf); - - item = variable_item_list_add( - app->variable_item_list, - "Hopping:", - ON_OFF_COUNT, - protopirate_scene_receiver_config_set_hopping_running, - app); - value_index = protopirate_scene_receiver_config_hopper_value_index( - app->txrx->hopper_state, hopping_value, ON_OFF_COUNT, app); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, on_off_text[value_index]); - - item = variable_item_list_add( - app->variable_item_list, - "Modulation:", - subghz_setting_get_preset_count(app->setting), - protopirate_scene_receiver_config_set_preset, - app); - value_index = protopirate_scene_receiver_config_next_preset( - furi_string_get_cstr(app->txrx->preset->name), app); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text( - item, subghz_setting_get_preset_name(app->setting, value_index)); - -#ifdef ENABLE_EMULATE_FEATURE - // TX power option - item = variable_item_list_add( - app->variable_item_list, - "TX Power:", - TX_POWER_COUNT, - protopirate_scene_receiver_config_set_tx_power, - app); - variable_item_set_current_value_index(item, app->tx_power); - variable_item_set_current_value_text(item, tx_power_text[app->tx_power]); -#endif - // Auto-save option - item = variable_item_list_add( - app->variable_item_list, - "Auto-Save:", - ON_OFF_COUNT, - protopirate_scene_receiver_config_set_auto_save, - app); - variable_item_set_current_value_index(item, app->auto_save ? 1 : 0); - variable_item_set_current_value_text(item, on_off_text[app->auto_save ? 1 : 0]); - - // Date/time filenames option - item = variable_item_list_add( - app->variable_item_list, - "Filenames:", - 2, - protopirate_scene_receiver_config_set_datetime_filenames, - app); - variable_item_set_current_value_index(item, app->datetime_filenames ? 1 : 0); - variable_item_set_current_value_text( - item, sequence_time_text[app->datetime_filenames ? 1 : 0]); - - item = variable_item_list_add( - app->variable_item_list, - "Check Saved:", - ON_OFF_COUNT, - protopirate_scene_receiver_config_set_check_saved, - app); - variable_item_set_current_value_index(item, app->check_saved ? 1 : 0); - variable_item_set_current_value_text(item, on_off_text[app->check_saved ? 1 : 0]); - - // Sound option - item = variable_item_list_add( - app->variable_item_list, - "Sound:", - ON_OFF_COUNT, - protopirate_scene_receiver_config_set_sound, - app); - variable_item_set_current_value_index(item, app->sound); - variable_item_set_current_value_text(item, on_off_text[(!app->sound) ? 0 : 1]); - - variable_item_list_add(app->variable_item_list, "Lock Keyboard", 1, NULL, NULL); - variable_item_list_set_enter_callback( - app->variable_item_list, protopirate_scene_receiver_config_var_list_enter_callback, app); - - view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewVariableItemList); + app->config_plugin->on_enter(app); } bool protopirate_scene_receiver_config_on_event(void* context, SceneManagerEvent event) { @@ -347,6 +29,9 @@ bool protopirate_scene_receiver_config_on_event(void* context, SceneManagerEvent void protopirate_scene_receiver_config_on_exit(void* context) { ProtoPirateApp* app = context; - variable_item_list_set_selected_item(app->variable_item_list, 0); - variable_item_list_reset(app->variable_item_list); + + if(app->config_plugin && app->config_plugin->on_exit) { + app->config_plugin->on_exit(app); + } + config_plugin_unload(app); }