diff --git a/defines.h b/defines.h index e3dd48b..4744959 100644 --- a/defines.h +++ b/defines.h @@ -1,7 +1,7 @@ #pragma once //#define ENABLE_TIMING_TUNER_SCENE -#define ENABLE_SUB_DECODE_SCENE +//#define ENABLE_SUB_DECODE_SCENE // #define ENABLE_EMULATE_FEATURE diff --git a/helpers/protopirate_storage.c b/helpers/protopirate_storage.c index b679d49..271f93b 100644 --- a/helpers/protopirate_storage.c +++ b/helpers/protopirate_storage.c @@ -10,6 +10,57 @@ bool protopirate_storage_init(void) { return result; } +void protopirate_storage_wipe_history_cache(void) { + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) { + storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER); + FURI_LOG_I(TAG, "Wiped history cache"); + } + furi_record_close(RECORD_STORAGE); +} + +void protopirate_storage_purge_temp_history_at_startup(void) { + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) { + storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER); + } + furi_record_close(RECORD_STORAGE); +} + +bool protopirate_storage_ensure_history_folder(void) { + if(!protopirate_storage_init()) { + return false; + } + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_mkdir(storage, PROTOPIRATE_CACHE_FOLDER); + bool ok = storage_simply_mkdir(storage, PROTOPIRATE_HISTORY_FOLDER); + furi_record_close(RECORD_STORAGE); + return ok; +} + +bool protopirate_storage_save_history_capture( + FlipperFormat* flipper_format, + uint32_t seq, + FuriString* out_path) { + furi_check(flipper_format); + furi_check(out_path); + + if(!protopirate_storage_ensure_history_folder()) { + FURI_LOG_E(TAG, "History folder missing"); + return false; + } + + furi_string_printf( + out_path, + "%s/hist_%08lu%s", + PROTOPIRATE_HISTORY_FOLDER, + (unsigned long)seq, + PROTOPIRATE_APP_EXTENSION); + + return protopirate_storage_save_capture_to_path( + flipper_format, furi_string_get_cstr(out_path)); +} + static void sanitize_filename(const char* input, char* output, size_t output_size) { if(!output || output_size == 0) return; if(!input) { diff --git a/helpers/protopirate_storage.h b/helpers/protopirate_storage.h index dd4db22..2020253 100644 --- a/helpers/protopirate_storage.h +++ b/helpers/protopirate_storage.h @@ -9,6 +9,8 @@ #define PROTOPIRATE_APP_EXTENSION ".psf" #define PROTOPIRATE_APP_FILE_VERSION 1 #define PROTOPIRATE_TEMP_FILE APP_DATA_PATH("saved/.temp.psf") +#define PROTOPIRATE_CACHE_FOLDER APP_DATA_PATH("cache") +#define PROTOPIRATE_HISTORY_FOLDER APP_DATA_PATH("cache/history") // Helper: failed read #define PROTOPIRATE_FAIL_READ(_k) \ @@ -107,4 +109,16 @@ FlipperFormat* protopirate_storage_load_file(const char* file_path); void protopirate_storage_close_file(FlipperFormat* flipper_format); // Check if file exists -bool protopirate_storage_file_exists(const char* file_path); \ No newline at end of file +bool protopirate_storage_file_exists(const char* file_path); + +bool protopirate_storage_ensure_history_folder(void); + +void protopirate_storage_purge_temp_history_at_startup(void); + +void protopirate_storage_wipe_history_cache(void); + +bool protopirate_storage_save_history_capture( + FlipperFormat* flipper_format, + uint32_t seq, + FuriString* out_path); + \ No newline at end of file diff --git a/helpers/protopirate_types.h b/helpers/protopirate_types.h index 2b77fe9..7fe3b75 100644 --- a/helpers/protopirate_types.h +++ b/helpers/protopirate_types.h @@ -23,6 +23,7 @@ typedef enum { ProtoPirateCustomEventViewReceiverUnlock, // Custom events for scenes ProtoPirateCustomEventSceneReceiverUpdate, + ProtoPirateCustomEventReceiverDeferredRxStart, ProtoPirateCustomEventSceneSettingLock, // File management ProtoPirateCustomEventReceiverInfoSave, diff --git a/protopirate_app.c b/protopirate_app.c index d11b23e..f94a77b 100644 --- a/protopirate_app.c +++ b/protopirate_app.c @@ -32,6 +32,7 @@ static void protopirate_app_tick_event_callback(void* context) { ProtoPirateApp* protopirate_app_alloc() { LOG_HEAP("Pre alloc"); + protopirate_storage_purge_temp_history_at_startup(); ProtoPirateApp* app = malloc(sizeof(ProtoPirateApp)); if(!app) { FURI_LOG_E(TAG, "Failed to allocate ProtoPirateApp app !"); diff --git a/protopirate_app_i.c b/protopirate_app_i.c index 426294f..958d5bf 100644 --- a/protopirate_app_i.c +++ b/protopirate_app_i.c @@ -114,6 +114,48 @@ void protopirate_sleep(ProtoPirateApp* app) { app->txrx->txrx_state = ProtoPirateTxRxStateSleep; } +void protopirate_rx_stack_suspend_for_tx(ProtoPirateApp* app) { + if(!app || !app->radio_initialized) { + return; + } + + if(app->txrx->txrx_state == ProtoPirateTxRxStateRx) { + protopirate_rx_end(app); + } + + if(app->txrx->receiver) { + subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, NULL); + subghz_receiver_free(app->txrx->receiver); + app->txrx->receiver = NULL; + } + + if(app->txrx->worker) { + subghz_worker_free(app->txrx->worker); + app->txrx->worker = NULL; + } + + if(app->txrx->radio_device && app->txrx->txrx_state != ProtoPirateTxRxStateTx) { + subghz_devices_idle(app->txrx->radio_device); + app->txrx->txrx_state = ProtoPirateTxRxStateIDLE; + } +} + +void protopirate_rx_stack_resume_after_tx(ProtoPirateApp* app) { + if(!app || !app->radio_initialized || !app->txrx->environment) { + return; + } + if(app->txrx->receiver) { + return; + } + + app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment); + if(!app->txrx->receiver) { + FURI_LOG_E(TAG, "rx_stack_resume: subghz_receiver_alloc_init failed"); + return; + } + subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable); +} + void protopirate_hopper_update(ProtoPirateApp* app) { furi_check(app); @@ -153,7 +195,7 @@ void protopirate_hopper_update(ProtoPirateApp* app) { if(app->txrx->txrx_state == ProtoPirateTxRxStateRx) { protopirate_rx_end(app); } - if(app->txrx->txrx_state == ProtoPirateTxRxStateIDLE) { + if(app->txrx->txrx_state == ProtoPirateTxRxStateIDLE && app->txrx->receiver) { subghz_receiver_reset(app->txrx->receiver); app->txrx->preset->frequency = subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency); diff --git a/protopirate_app_i.h b/protopirate_app_i.h index 21a6ff0..22515f4 100644 --- a/protopirate_app_i.h +++ b/protopirate_app_i.h @@ -107,6 +107,9 @@ void protopirate_tx_stop(ProtoPirateApp* app); bool protopirate_radio_init(ProtoPirateApp* app); void protopirate_radio_deinit(ProtoPirateApp* app); +void protopirate_rx_stack_suspend_for_tx(ProtoPirateApp* app); +void protopirate_rx_stack_resume_after_tx(ProtoPirateApp* app); + void protopirate_app_free(ProtoPirateApp* app); static const NotificationSequence sequence_tx = { diff --git a/protopirate_history.c b/protopirate_history.c index db00f08..e478d8a 100644 --- a/protopirate_history.c +++ b/protopirate_history.c @@ -1,15 +1,49 @@ // protopirate_history.c #include "protopirate_history.h" +#include "defines.h" +#include "helpers/protopirate_storage.h" +#include "protocols/protocol_items.h" +#include #include -#include +#include +#include #define TAG "ProtoPirateHistory" +static void protopirate_history_shrink_to_first_line(FuriString* s) { + const char* c = furi_string_get_cstr(s); + size_t n = 0; + while(c[n] && c[n] != '\r' && c[n] != '\n') { + n++; + } + furi_string_left(s, n); +} + +static const SubGhzProtocol* protopirate_history_find_protocol_by_name(const char* pname) { + if(!pname) return NULL; + const char* lookup = pname; + if(strcmp(pname, "Kia V3") == 0 || strcmp(pname, "Kia V4") == 0) { + lookup = "Kia V3/V4"; + } + for(size_t i = 0; i < protopirate_protocol_registry.size; i++) { + if(strcmp(protopirate_protocol_registry.items[i]->name, lookup) == 0) { + return protopirate_protocol_registry.items[i]; + } + } + if(lookup != pname) { + for(size_t i = 0; i < protopirate_protocol_registry.size; i++) { + if(strcmp(protopirate_protocol_registry.items[i]->name, pname) == 0) { + return protopirate_protocol_registry.items[i]; + } + } + } + return NULL; +} + typedef struct { FuriString* item_str; - FlipperFormat* flipper_format; + FuriString* capture_path; uint8_t type; - SubGhzRadioPreset* preset; } ProtoPirateHistoryItem; ARRAY_DEF(ProtoPirateHistoryItemArray, ProtoPirateHistoryItem, M_POD_OPLIST) @@ -19,8 +53,35 @@ struct ProtoPirateHistory { uint16_t last_index; uint32_t last_update_timestamp; uint8_t code_last_hash_data; + uint32_t next_capture_seq; + Storage* storage; + FlipperFormat* loaded_ff; + int16_t loaded_idx; }; +void protopirate_history_release_scratch(ProtoPirateHistory* instance) { + furi_check(instance); + if(instance->loaded_ff) { + flipper_format_free(instance->loaded_ff); + instance->loaded_ff = NULL; + } + instance->loaded_idx = -1; +} + +static void protopirate_history_item_free(ProtoPirateHistoryItem* item, bool delete_file) { + if(item->item_str) { + furi_string_free(item->item_str); + item->item_str = NULL; + } + if(item->capture_path) { + if(delete_file) { + protopirate_storage_delete_file(furi_string_get_cstr(item->capture_path)); + } + furi_string_free(item->capture_path); + item->capture_path = NULL; + } +} + ProtoPirateHistory* protopirate_history_alloc(void) { ProtoPirateHistory* instance = malloc(sizeof(ProtoPirateHistory)); furi_check(instance); @@ -28,41 +89,42 @@ ProtoPirateHistory* protopirate_history_alloc(void) { instance->last_index = 0; instance->last_update_timestamp = 0; instance->code_last_hash_data = 0; + instance->next_capture_seq = (uint32_t)(furi_get_tick() & 0x0FFFFFFF); + if(instance->next_capture_seq == 0) { + instance->next_capture_seq = 1; + } + instance->storage = furi_record_open(RECORD_STORAGE); + instance->loaded_ff = NULL; + instance->loaded_idx = -1; return instance; } void protopirate_history_free(ProtoPirateHistory* instance) { furi_check(instance); + protopirate_history_release_scratch(instance); for(size_t i = 0; i < ProtoPirateHistoryItemArray_size(instance->data); i++) { ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, i); - furi_string_free(item->item_str); - flipper_format_free(item->flipper_format); - if(item->preset) { - if(item->preset->name) { - furi_string_free(item->preset->name); - } - free(item->preset); - } + protopirate_history_item_free(item, false); } ProtoPirateHistoryItemArray_clear(instance->data); + protopirate_storage_wipe_history_cache(); + if(instance->storage) { + furi_record_close(RECORD_STORAGE); + instance->storage = NULL; + } free(instance); } void protopirate_history_reset(ProtoPirateHistory* instance) { furi_check(instance); + protopirate_history_release_scratch(instance); for(size_t i = 0; i < ProtoPirateHistoryItemArray_size(instance->data); i++) { ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, i); - furi_string_free(item->item_str); - flipper_format_free(item->flipper_format); - if(item->preset) { - if(item->preset->name) { - furi_string_free(item->preset->name); - } - free(item->preset); - } + protopirate_history_item_free(item, false); } ProtoPirateHistoryItemArray_reset(instance->data); instance->last_index = 0; + protopirate_storage_wipe_history_cache(); } uint16_t protopirate_history_get_item(ProtoPirateHistory* instance) { @@ -75,23 +137,22 @@ uint16_t protopirate_history_get_last_index(ProtoPirateHistory* instance) { return instance->last_index; } -// Helper function to free a single history item's resources -static void protopirate_history_item_free(ProtoPirateHistoryItem* item) { - if(item->item_str) { - furi_string_free(item->item_str); - item->item_str = NULL; +bool protopirate_history_get_capture_path( + ProtoPirateHistory* instance, + uint16_t idx, + FuriString* out_path) { + furi_check(instance); + furi_check(out_path); + + if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) { + return false; } - if(item->flipper_format) { - flipper_format_free(item->flipper_format); - item->flipper_format = NULL; - } - if(item->preset) { - if(item->preset->name) { - furi_string_free(item->preset->name); - } - free(item->preset); - item->preset = NULL; + ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx); + if(!item->capture_path || furi_string_size(item->capture_path) == 0) { + return false; } + furi_string_set(out_path, item->capture_path); + return true; } bool protopirate_history_add_to_history( @@ -103,7 +164,6 @@ bool protopirate_history_add_to_history( SubGhzProtocolDecoderBase* decoder_base = context; - // Check for duplicate (same hash within 500ms) if((instance->code_last_hash_data == subghz_protocol_decoder_base_get_hash_data(decoder_base)) && ((furi_get_tick() - instance->last_update_timestamp) < 500)) { @@ -111,11 +171,12 @@ bool protopirate_history_add_to_history( return false; } - // If history is full, remove the oldest entry + protopirate_history_release_scratch(instance); + if(ProtoPirateHistoryItemArray_size(instance->data) >= PROTOPIRATE_HISTORY_MAX) { ProtoPirateHistoryItem* oldest = ProtoPirateHistoryItemArray_get(instance->data, 0); if(oldest) { - protopirate_history_item_free(oldest); + protopirate_history_item_free(oldest, true); } ProtoPirateHistoryItemArray_pop_at(NULL, instance->data, 0); FURI_LOG_D(TAG, "History full, removed oldest entry"); @@ -124,54 +185,64 @@ bool protopirate_history_add_to_history( instance->code_last_hash_data = subghz_protocol_decoder_base_get_hash_data(decoder_base); instance->last_update_timestamp = furi_get_tick(); - // Create a new history item ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_push_raw(instance->data); item->item_str = furi_string_alloc(); - item->flipper_format = flipper_format_string_alloc(); + item->capture_path = furi_string_alloc(); item->type = 0; - // Copy preset - item->preset = malloc(sizeof(SubGhzRadioPreset)); - item->preset->frequency = preset->frequency; - item->preset->name = furi_string_alloc(); - if(preset->name) { - furi_string_set(item->preset->name, preset->name); - } else { - furi_string_set(item->preset->name, "UNKNOWN"); - } - item->preset->data = preset->data; - item->preset->data_size = preset->data_size; - - // Get string representation FuriString* text = furi_string_alloc(); subghz_protocol_decoder_base_get_string(decoder_base, text); furi_string_set(item->item_str, text); - - // Serialize to flipper format - subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_format, preset); - - // Debug: Log what we're adding to history - flipper_format_rewind(item->flipper_format); - uint32_t debug_bit_count; - FuriString* debug_protocol = furi_string_alloc(); - if(flipper_format_read_string(item->flipper_format, "Protocol", debug_protocol)) { - FURI_LOG_I(TAG, "History add - Protocol: %s", furi_string_get_cstr(debug_protocol)); - } - flipper_format_rewind(item->flipper_format); - if(flipper_format_read_uint32(item->flipper_format, "Bit", &debug_bit_count, 1)) { - FURI_LOG_I(TAG, "History add - Bit count: %lu", debug_bit_count); - } - furi_string_free(debug_protocol); - + protopirate_history_shrink_to_first_line(item->item_str); furi_string_free(text); + FlipperFormat* temp_ff = flipper_format_string_alloc(); + furi_check(temp_ff); + SubGhzProtocolStatus ser = + subghz_protocol_decoder_base_serialize(decoder_base, temp_ff, preset); + if(ser != SubGhzProtocolStatusOk) { + FURI_LOG_E(TAG, "Serialize failed"); + flipper_format_free(temp_ff); + furi_string_free(item->item_str); + furi_string_free(item->capture_path); + ProtoPirateHistoryItemArray_pop_at(NULL, instance->data, + ProtoPirateHistoryItemArray_size(instance->data) - 1); + return false; + } + + uint32_t seq = instance->next_capture_seq++; + if(!protopirate_storage_save_history_capture(temp_ff, seq, item->capture_path)) { + FURI_LOG_E(TAG, "Failed to save history file"); + flipper_format_free(temp_ff); + furi_string_free(item->item_str); + furi_string_free(item->capture_path); + ProtoPirateHistoryItemArray_pop_at(NULL, instance->data, + ProtoPirateHistoryItemArray_size(instance->data) - 1); + return false; + } + flipper_format_rewind(temp_ff); + { + uint32_t debug_bit_count; + FuriString* debug_protocol = furi_string_alloc(); + if(flipper_format_read_string(temp_ff, "Protocol", debug_protocol)) { + FURI_LOG_I(TAG, "History add - Protocol: %s", furi_string_get_cstr(debug_protocol)); + } + flipper_format_rewind(temp_ff); + if(flipper_format_read_uint32(temp_ff, "Bit", &debug_bit_count, 1)) { + FURI_LOG_I(TAG, "History add - Bit count: %lu", (unsigned long)debug_bit_count); + } + furi_string_free(debug_protocol); + } + flipper_format_free(temp_ff); + instance->last_index++; FURI_LOG_I( TAG, - "Added item %u to history (size: %zu)", + "Added item %u to history (size: %zu) path %s", instance->last_index, - ProtoPirateHistoryItemArray_size(instance->data)); + ProtoPirateHistoryItemArray_size(instance->data), + furi_string_get_cstr(item->capture_path)); return true; } @@ -189,31 +260,16 @@ void protopirate_history_get_text_item_menu( } ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx); - - // Get just the first line for the menu - const char* str = furi_string_get_cstr(item->item_str); - const char* newline = strchr(str, '\r'); - size_t len = 0; - if(newline) { - len = newline - str; - } else { - newline = strchr(str, '\n'); - if(newline) { - len = newline - str; - } else { - len = furi_string_size(item->item_str); - } - } - - // Add index prefix uint16_t display_idx = idx + 1; - furi_string_printf(output, "%u. %.*s", display_idx, (int)len, str); + furi_string_printf( + output, "%u. %s", display_idx, furi_string_get_cstr(item->item_str)); } -void protopirate_history_get_text_item( +void protopirate_history_get_text_item_detail( ProtoPirateHistory* instance, + uint16_t idx, FuriString* output, - uint16_t idx) { + SubGhzEnvironment* environment) { furi_check(instance); furi_check(output); @@ -223,7 +279,53 @@ void protopirate_history_get_text_item( } ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx); - furi_string_set(output, item->item_str); + + if(!environment) { + furi_string_set(output, item->item_str); + return; + } + + FlipperFormat* ff = protopirate_history_get_raw_data(instance, idx); + if(!ff) { + furi_string_set(output, item->item_str); + return; + } + + FuriString* proto = furi_string_alloc(); + flipper_format_rewind(ff); + if(!flipper_format_read_string(ff, "Protocol", proto)) { + furi_string_free(proto); + furi_string_set(output, item->item_str); + return; + } + + const SubGhzProtocol* protocol = + protopirate_history_find_protocol_by_name(furi_string_get_cstr(proto)); + furi_string_free(proto); + + if(!protocol || !protocol->decoder || !protocol->decoder->alloc || !protocol->decoder->free) { + furi_string_set(output, item->item_str); + return; + } + + void* dec = protocol->decoder->alloc(environment); + if(!dec) { + furi_string_set(output, item->item_str); + return; + } + + SubGhzProtocolDecoderBase* base = dec; + flipper_format_rewind(ff); + SubGhzProtocolStatus st = subghz_protocol_decoder_base_deserialize(base, ff); + if(st != SubGhzProtocolStatusOk) { + protocol->decoder->free(dec); + furi_string_set(output, item->item_str); + return; + } + + furi_string_reset(output); + subghz_protocol_decoder_base_get_string(base, output); + protocol->decoder->free(dec); } SubGhzProtocolDecoderBase* @@ -240,8 +342,47 @@ FlipperFormat* protopirate_history_get_raw_data(ProtoPirateHistory* instance, ui return NULL; } + if(instance->loaded_idx == (int16_t)idx && instance->loaded_ff) { + return instance->loaded_ff; + } + + protopirate_history_release_scratch(instance); + ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx); - return item->flipper_format; + if(!item->capture_path || furi_string_size(item->capture_path) == 0) { + return NULL; + } + + instance->loaded_ff = flipper_format_file_alloc(instance->storage); + furi_check(instance->loaded_ff); + if(!flipper_format_file_open_existing( + instance->loaded_ff, furi_string_get_cstr(item->capture_path))) { + FURI_LOG_E(TAG, "Failed open history capture %s", furi_string_get_cstr(item->capture_path)); + flipper_format_free(instance->loaded_ff); + instance->loaded_ff = NULL; + return NULL; + } + instance->loaded_idx = (int16_t)idx; + return instance->loaded_ff; +} + +void protopirate_history_commit_loaded(ProtoPirateHistory* instance) { + furi_check(instance); + if(instance->loaded_idx < 0 || !instance->loaded_ff) { + return; + } + if((size_t)instance->loaded_idx >= ProtoPirateHistoryItemArray_size(instance->data)) { + return; + } + ProtoPirateHistoryItem* item = + ProtoPirateHistoryItemArray_get(instance->data, (size_t)instance->loaded_idx); + if(!item->capture_path) { + return; + } + if(!protopirate_storage_save_capture_to_path( + instance->loaded_ff, furi_string_get_cstr(item->capture_path))) { + FURI_LOG_E(TAG, "commit_loaded failed for %s", furi_string_get_cstr(item->capture_path)); + } } void protopirate_history_set_item_str( @@ -257,4 +398,5 @@ void protopirate_history_set_item_str( ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx); furi_string_set(item->item_str, str); + protopirate_history_shrink_to_first_line(item->item_str); } diff --git a/protopirate_history.h b/protopirate_history.h index fcaa72d..6d2e486 100644 --- a/protopirate_history.h +++ b/protopirate_history.h @@ -6,6 +6,7 @@ #define PROTOPIRATE_HISTORY_MAX 20 +typedef struct SubGhzEnvironment SubGhzEnvironment; typedef struct ProtoPirateHistory ProtoPirateHistory; ProtoPirateHistory* protopirate_history_alloc(void); @@ -13,6 +14,12 @@ void protopirate_history_free(ProtoPirateHistory* instance); void protopirate_history_reset(ProtoPirateHistory* instance); uint16_t protopirate_history_get_item(ProtoPirateHistory* instance); uint16_t protopirate_history_get_last_index(ProtoPirateHistory* instance); + +bool protopirate_history_get_capture_path( + ProtoPirateHistory* instance, + uint16_t idx, + FuriString* out_path); + bool protopirate_history_add_to_history( ProtoPirateHistory* instance, void* context, @@ -21,14 +28,20 @@ void protopirate_history_get_text_item_menu( ProtoPirateHistory* instance, FuriString* output, uint16_t idx); -void protopirate_history_get_text_item( +void protopirate_history_get_text_item_detail( ProtoPirateHistory* instance, + uint16_t idx, FuriString* output, - uint16_t idx); + SubGhzEnvironment* environment); SubGhzProtocolDecoderBase* protopirate_history_get_decoder_base(ProtoPirateHistory* instance, uint16_t idx); + FlipperFormat* protopirate_history_get_raw_data(ProtoPirateHistory* instance, uint16_t idx); +void protopirate_history_commit_loaded(ProtoPirateHistory* instance); + +void protopirate_history_release_scratch(ProtoPirateHistory* instance); + void protopirate_history_set_item_str( ProtoPirateHistory* instance, uint16_t idx, diff --git a/scenes/protopirate_scene_emulate.c b/scenes/protopirate_scene_emulate.c index 901a0dd..f1c821a 100644 --- a/scenes/protopirate_scene_emulate.c +++ b/scenes/protopirate_scene_emulate.c @@ -100,6 +100,58 @@ static void emulate_context_free(void) { emulate_context = NULL; } +static bool emulate_context_try_init_transmitter(ProtoPirateApp* app, EmulateContext* ctx) { + if(ctx->transmitter) { + return true; + } + if(!ctx->flipper_format || !ctx->protocol_name) { + return false; + } + + const char* proto_name = furi_string_get_cstr(ctx->protocol_name); + const char* registry_name = proto_name; + if(strcmp(proto_name, "Kia V3") == 0) { + registry_name = "Kia V3/V4"; + FURI_LOG_I(TAG, "Protocol name KiaV3 fixed to Kia V3/V4 for registry"); + } else if(strcmp(proto_name, "Kia V4") == 0) { + registry_name = "Kia V3/V4"; + FURI_LOG_I(TAG, "Protocol name KiaV4 fixed to Kia V3/V4 for registry"); + } + + const SubGhzProtocol* protocol = NULL; + for(size_t i = 0; i < protopirate_protocol_registry.size; i++) { + if(strcmp(protopirate_protocol_registry.items[i]->name, registry_name) == 0) { + protocol = protopirate_protocol_registry.items[i]; + FURI_LOG_I(TAG, "Found protocol %s in registry at index %zu", registry_name, i); + break; + } + } + + if(!protocol || !protocol->encoder || !protocol->encoder->alloc) { + FURI_LOG_E(TAG, "Protocol %s has no encoder or not in registry", registry_name); + return false; + } + + ctx->transmitter = subghz_transmitter_alloc_init(app->txrx->environment, registry_name); + if(!ctx->transmitter) { + FURI_LOG_E(TAG, "Failed to allocate transmitter for %s", registry_name); + return false; + } + + flipper_format_rewind(ctx->flipper_format); + SubGhzProtocolStatus status = + subghz_transmitter_deserialize(ctx->transmitter, ctx->flipper_format); + if(status != SubGhzProtocolStatusOk) { + FURI_LOG_E(TAG, "Failed to deserialize transmitter, status: %d", status); + subghz_transmitter_free(ctx->transmitter); + ctx->transmitter = NULL; + return false; + } + + FURI_LOG_I(TAG, "Transmitter ready (lazy init)"); + return true; +} + static uint8_t protopirate_get_button_for_protocol( const char* protocol, InputKey key, @@ -456,6 +508,12 @@ void protopirate_scene_emulate_on_enter(void* context) { emulate_context_free(); } + if(app->txrx && app->txrx->history) { + protopirate_history_release_scratch(app->txrx->history); + } + + protopirate_rx_stack_suspend_for_tx(app); + // Create emulate context emulate_context = malloc(sizeof(EmulateContext)); if(!emulate_context) { @@ -572,60 +630,6 @@ void protopirate_scene_emulate_on_enter(void* context) { emulate_context->current_counter = emulate_context->original_counter; } - // Set up transmitter based on protocol - const char* proto_name = furi_string_get_cstr(emulate_context->protocol_name); - FURI_LOG_I(TAG, "Setting up transmitter for protocol: %s", proto_name); - - if(strcmp(proto_name, "Kia V3") == 0) { - proto_name = "Kia V3/V4"; - FURI_LOG_I(TAG, "Protocol name KiaV3 fixed to Kia V3/V4 for registry"); - } else if(strcmp(proto_name, "Kia V4") == 0) { - proto_name = "Kia V3/V4"; - FURI_LOG_I(TAG, "Protocol name KiaV4 fixed to Kia V3/V4 for registry"); - } - - // Find the protocol in the registry - const SubGhzProtocol* protocol = NULL; - for(size_t i = 0; i < protopirate_protocol_registry.size; i++) { - if(strcmp(protopirate_protocol_registry.items[i]->name, proto_name) == 0) { - protocol = protopirate_protocol_registry.items[i]; - FURI_LOG_I(TAG, "Found protocol %s in registry at index %zu", proto_name, i); - break; - } - } - - if(protocol) { - if(protocol->encoder && protocol->encoder->alloc) { - FURI_LOG_I(TAG, "Protocol has encoder support"); - - // Try to create transmitter - emulate_context->transmitter = - subghz_transmitter_alloc_init(app->txrx->environment, proto_name); - - if(emulate_context->transmitter) { - FURI_LOG_I(TAG, "Transmitter allocated successfully"); - - // Deserialize for transmission - flipper_format_rewind(emulate_context->flipper_format); - SubGhzProtocolStatus status = subghz_transmitter_deserialize( - emulate_context->transmitter, emulate_context->flipper_format); - - if(status != SubGhzProtocolStatusOk) { - FURI_LOG_E(TAG, "Failed to deserialize transmitter, status: %d", status); - subghz_transmitter_free(emulate_context->transmitter); - emulate_context->transmitter = NULL; - } else { - FURI_LOG_I(TAG, "Transmitter deserialized successfully"); - } - } else { - FURI_LOG_E(TAG, "Failed to allocate transmitter for %s", proto_name); - } - } else { - FURI_LOG_E(TAG, "Protocol %s has no encoder", proto_name); - } - } else { - FURI_LOG_E(TAG, "Protocol %s not found in registry", proto_name); - } } else { FURI_LOG_E(TAG, "No file path set"); emulate_context_free(); @@ -661,8 +665,13 @@ bool protopirate_scene_emulate_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case ProtoPirateCustomEventEmulateTransmit: - if(emulate_context && emulate_context->transmitter && - emulate_context->flipper_format) { + if(emulate_context && emulate_context->flipper_format) { + if(!emulate_context_try_init_transmitter(app, emulate_context)) { + FURI_LOG_E(TAG, "No transmitter available"); + notification_message(app->notifications, &sequence_error); + consumed = true; + break; + } // Stop any ongoing transmission FIRST if(app->txrx->txrx_state == ProtoPirateTxRxStateTx) { FURI_LOG_W(TAG, "Previous transmission still active, stopping it"); @@ -792,9 +801,6 @@ bool protopirate_scene_emulate_on_event(void* context, SceneManagerEvent event) if(free_custom_data) free(preset_data); //We have used the preset, I alloced it I have to free. - } else { - FURI_LOG_E(TAG, "No transmitter available"); - notification_message(app->notifications, &sequence_error); } consumed = true; break; diff --git a/scenes/protopirate_scene_receiver.c b/scenes/protopirate_scene_receiver.c index 25b0ddf..cc2b87e 100644 --- a/scenes/protopirate_scene_receiver.c +++ b/scenes/protopirate_scene_receiver.c @@ -5,8 +5,7 @@ #include #include "proto_pirate_icons.h" -#define TAG "ProtoPirateSceneRx" -#define PROTOPIRATE_DISPLAY_HISTORY_MAX 20 // Reduced from 50 to save memory +#define TAG "ProtoPirateSceneRx" // Forward declaration void protopirate_scene_receiver_view_callback(ProtoPirateCustomEvent event, void* context); @@ -46,7 +45,7 @@ static void protopirate_scene_receiver_update_statusbar(void* context) { history_stat_str, "%u/%u", protopirate_history_get_item(app->txrx->history), - PROTOPIRATE_DISPLAY_HISTORY_MAX); + PROTOPIRATE_HISTORY_MAX); // Pass actual external radio status protopirate_view_receiver_add_data_statusbar( app->protopirate_receiver, @@ -81,6 +80,7 @@ static void protopirate_scene_receiver_callback( FURI_LOG_I(TAG, "%s", furi_string_get_cstr(str_buff)); // Add to history + uint16_t count_before = protopirate_history_get_item(app->txrx->history); if(protopirate_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset)) { notification_message(app->notifications, &sequence_semi_success); @@ -89,20 +89,16 @@ static void protopirate_scene_receiver_callback( "Added to history, total items: %u", protopirate_history_get_item(app->txrx->history)); - FuriString* item_name = furi_string_alloc(); - if(!item_name) { - FURI_LOG_E(TAG, "item_name allocation failed"); - return; + uint16_t count_after = protopirate_history_get_item(app->txrx->history); + + if(count_after == PROTOPIRATE_HISTORY_MAX) { + protopirate_view_receiver_sync_menu_from_history( + app->protopirate_receiver, app->txrx->history); + } else if(count_after > count_before) { + protopirate_view_receiver_append_menu_row_from_history( + app->protopirate_receiver, app->txrx->history, count_after - 1); } - protopirate_history_get_text_item_menu( - app->txrx->history, item_name, protopirate_history_get_item(app->txrx->history) - 1); - - protopirate_view_receiver_add_item_to_menu( - app->protopirate_receiver, furi_string_get_cstr(item_name), 0); - - furi_string_free(item_name); - // Auto-scroll to the last detected signal uint16_t last_index = protopirate_history_get_item(app->txrx->history) - 1; protopirate_view_receiver_set_idx_menu(app->protopirate_receiver, last_index); @@ -165,42 +161,26 @@ static void protopirate_scene_receiver_callback( } } -void protopirate_scene_receiver_on_enter(void* context) { - furi_check(context); - ProtoPirateApp* app = context; - - FURI_LOG_I(TAG, "=== ENTERING RECEIVER SCENE ==="); - -// Now safe to access radio device -#ifndef REMOVE_LOGS - bool is_external = - app->txrx->radio_device ? radio_device_loader_is_external(app->txrx->radio_device) : false; - const char* device_name = - app->txrx->radio_device ? subghz_devices_get_name(app->txrx->radio_device) : NULL; - FURI_LOG_I(TAG, "Radio device: %s", device_name ? device_name : "NULL"); - FURI_LOG_I(TAG, "Is External: %s", is_external ? "YES" : "NO"); - FURI_LOG_I(TAG, "Frequency: %lu Hz", app->txrx->preset->frequency); - FURI_LOG_I(TAG, "Modulation: %s", furi_string_get_cstr(app->txrx->preset->name)); - FURI_LOG_I(TAG, "Auto-save: %s", app->auto_save ? "ON" : "OFF"); -#endif - - // Allocate history - if(!app->txrx->history) { - app->txrx->history = protopirate_history_alloc(); - if(!app->txrx->history) { - FURI_LOG_E(TAG, "Failed to allocate history!"); - return; - } +static void protopirate_scene_receiver_start_rx_stack(ProtoPirateApp* app) { + furi_check(app); + if(!app->radio_initialized) { + return; + } + + protopirate_rx_stack_resume_after_tx(app); + if(!app->txrx->receiver) { + FURI_LOG_E(TAG, "SubGhz receiver unavailable — cannot start RX"); + notification_message(app->notifications, &sequence_error); + scene_manager_previous_scene(app->scene_manager); + return; } - // Allocate worker if(!app->txrx->worker) { app->txrx->worker = subghz_worker_alloc(); if(!app->txrx->worker) { FURI_LOG_E(TAG, "Failed to allocate worker!"); return; } - // Set up worker callbacks subghz_worker_set_overrun_callback( app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); subghz_worker_set_pair_callback( @@ -208,22 +188,12 @@ void protopirate_scene_receiver_on_enter(void* context) { subghz_worker_set_context(app->txrx->worker, app->txrx->receiver); } - // Set up the receiver callback subghz_receiver_set_rx_callback(app->txrx->receiver, protopirate_scene_receiver_callback, app); - // Set up view callback - protopirate_view_receiver_set_callback( - app->protopirate_receiver, protopirate_scene_receiver_view_callback, app); - - // Update status bar - protopirate_scene_receiver_update_statusbar(app); - - // Start hopper if enabled if(app->txrx->hopper_state != ProtoPirateHopperStateOFF) { app->txrx->hopper_state = ProtoPirateHopperStateRunning; } - // Get preset data const char* preset_name = furi_string_get_cstr(app->txrx->preset->name); uint8_t* preset_data = subghz_setting_get_preset_data_by_name(app->setting, preset_name); @@ -232,7 +202,6 @@ void protopirate_scene_receiver_on_enter(void* context) { preset_data = subghz_setting_get_preset_data_by_name(app->setting, "AM650"); } - // Begin receiving protopirate_begin(app, preset_data); uint32_t frequency = app->txrx->preset->frequency; @@ -244,17 +213,57 @@ void protopirate_scene_receiver_on_enter(void* context) { FURI_LOG_I(TAG, "Starting RX on %lu Hz", frequency); protopirate_rx(app, frequency); FURI_LOG_I(TAG, "RX started, state: %d", app->txrx->txrx_state); +} + +void protopirate_scene_receiver_on_enter(void* context) { + furi_check(context); + ProtoPirateApp* app = context; + + if(app->txrx->history) { + protopirate_history_release_scratch(app->txrx->history); + } + + if(!app->txrx->history) { + app->txrx->history = protopirate_history_alloc(); + if(!app->txrx->history) { + FURI_LOG_E(TAG, "Failed to allocate history!"); + return; + } + } + + protopirate_view_receiver_set_callback( + app->protopirate_receiver, protopirate_scene_receiver_view_callback, app); + + protopirate_scene_receiver_update_statusbar(app); - // Update lock state in view protopirate_view_receiver_set_lock(app->protopirate_receiver, app->lock); - - // Update auto-save state in view protopirate_view_receiver_set_autosave(app->protopirate_receiver, app->auto_save); - - //Not in Sub Decode Mode protopirate_view_receiver_set_sub_decode_mode(app->protopirate_receiver, false); - // Switch to receiver view + if(app->radio_initialized && !app->txrx->receiver) { + view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewReceiver); + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventReceiverDeferredRxStart); + return; + } + +#ifndef REMOVE_LOGS + bool is_external = + app->txrx->radio_device ? radio_device_loader_is_external(app->txrx->radio_device) : false; + const char* device_name = + app->txrx->radio_device ? subghz_devices_get_name(app->txrx->radio_device) : NULL; + FURI_LOG_I(TAG, "=== ENTERING RECEIVER SCENE ==="); + FURI_LOG_I(TAG, "Radio device: %s", device_name ? device_name : "NULL"); + FURI_LOG_I(TAG, "Is External: %s", is_external ? "YES" : "NO"); + FURI_LOG_I(TAG, "Frequency: %lu Hz", app->txrx->preset->frequency); + FURI_LOG_I(TAG, "Modulation: %s", furi_string_get_cstr(app->txrx->preset->name)); + FURI_LOG_I(TAG, "Auto-save: %s", app->auto_save ? "ON" : "OFF"); +#endif + + if(app->radio_initialized) { + protopirate_scene_receiver_start_rx_stack(app); + } + view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewReceiver); } @@ -277,6 +286,14 @@ bool protopirate_scene_receiver_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { + case ProtoPirateCustomEventReceiverDeferredRxStart: +#ifndef REMOVE_LOGS + FURI_LOG_I(TAG, "Deferred RX start (post-emulate path)"); +#endif + protopirate_scene_receiver_start_rx_stack(app); + consumed = true; + break; + case ProtoPirateCustomEventSceneReceiverUpdate: protopirate_scene_receiver_update_statusbar(app); consumed = true; @@ -315,7 +332,11 @@ bool protopirate_scene_receiver_on_event(void* context, SceneManagerEvent event) // Update hopper if(app->txrx->hopper_state != ProtoPirateHopperStateOFF) { protopirate_hopper_update(app); - protopirate_scene_receiver_update_statusbar(app); + static uint8_t hopper_statusbar_tick = 0; + if(++hopper_statusbar_tick >= 8) { + hopper_statusbar_tick = 0; + protopirate_scene_receiver_update_statusbar(app); + } } // Update RSSI from the correct radio device (only if initialized) @@ -352,10 +373,19 @@ void protopirate_scene_receiver_on_exit(void* context) { FURI_LOG_I(TAG, "=== EXITING RECEIVER SCENE ==="); + const bool leaving_for_subscene = + (scene_manager_get_scene_state(app->scene_manager, ProtoPirateSceneReceiver) == 1); + // Only try to stop RX if radio is initialized if(app->radio_initialized && app->txrx->txrx_state == ProtoPirateTxRxStateRx) { protopirate_rx_end(app); } + + if(leaving_for_subscene) { + scene_manager_set_scene_state(app->scene_manager, ProtoPirateSceneReceiver, 0); + return; + } + if(app->txrx->worker) { FURI_LOG_D(TAG, "Freeing worker %p", app->txrx->worker); subghz_worker_free(app->txrx->worker); @@ -364,11 +394,6 @@ void protopirate_scene_receiver_on_exit(void* context) { FURI_LOG_D(TAG, "Worker was NULL, skipping free"); } - if(scene_manager_get_scene_state(app->scene_manager, ProtoPirateSceneReceiver) == 1) { - scene_manager_set_scene_state(app->scene_manager, ProtoPirateSceneReceiver, 0); - return; - } - // Full teardown: put radio to sleep, free worker and history protopirate_sleep(app); diff --git a/scenes/protopirate_scene_receiver_info.c b/scenes/protopirate_scene_receiver_info.c index ff8700e..879a267 100644 --- a/scenes/protopirate_scene_receiver_info.c +++ b/scenes/protopirate_scene_receiver_info.c @@ -97,7 +97,8 @@ static void protopirate_receiver_info_build_normal_widget(ProtoPirateApp* app) { app->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(text)); furi_string_reset(text); - protopirate_history_get_text_item(app->txrx->history, text, app->txrx->idx_menu_chosen); + protopirate_history_get_text_item_detail( + app->txrx->history, app->txrx->idx_menu_chosen, text, app->txrx->environment); bool is_psa = false; FlipperFormat* ff = @@ -309,6 +310,7 @@ static void psa_bf_finish_and_show_result(ProtoPirateApp* app) { protopirate_history_set_item_str( app->txrx->history, app->txrx->idx_menu_chosen, furi_string_get_cstr(new_str)); furi_string_free(new_str); + protopirate_history_commit_loaded(app->txrx->history); } if(status == PSA_BF_STATUS_FOUND) { @@ -514,19 +516,18 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e #ifdef ENABLE_EMULATE_FEATURE if(event.event == ProtoPirateCustomEventReceiverInfoEmulate && !is_emu_off) { - FlipperFormat* ff = - protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen); - if(ff) { - if(protopirate_storage_save_temp(ff)) { - FURI_LOG_I(TAG, "Saved temp for emulate"); - if(app->loaded_file_path) furi_string_free(app->loaded_file_path); - app->loaded_file_path = furi_string_alloc_set_str(PROTOPIRATE_TEMP_FILE); - scene_manager_next_scene(app->scene_manager, ProtoPirateSceneEmulate); - } else { - notification_message(app->notifications, &sequence_error); - } + FuriString* hist_path = furi_string_alloc(); + if(protopirate_history_get_capture_path( + app->txrx->history, app->txrx->idx_menu_chosen, hist_path)) { + protopirate_history_release_scratch(app->txrx->history); + if(app->loaded_file_path) furi_string_free(app->loaded_file_path); + app->loaded_file_path = furi_string_alloc_set(hist_path); + furi_string_free(hist_path); + FURI_LOG_I(TAG, "Emulate from history file: %s", furi_string_get_cstr(app->loaded_file_path)); + scene_manager_next_scene(app->scene_manager, ProtoPirateSceneEmulate); } else { - FURI_LOG_E(TAG, "No flipper format data for index %d", app->txrx->idx_menu_chosen); + furi_string_free(hist_path); + FURI_LOG_E(TAG, "No capture path for index %d", app->txrx->idx_menu_chosen); notification_message(app->notifications, &sequence_error); } consumed = true; diff --git a/scenes/protopirate_scene_sub_decode.c b/scenes/protopirate_scene_sub_decode.c index 805c2bd..d313993 100644 --- a/scenes/protopirate_scene_sub_decode.c +++ b/scenes/protopirate_scene_sub_decode.c @@ -481,6 +481,10 @@ static void protopirate_scene_sub_decode_widget_callback( void protopirate_scene_sub_decode_on_enter(void* context) { ProtoPirateApp* app = context; + if(app->radio_initialized) { + protopirate_rx_stack_resume_after_tx(app); + } + FURI_LOG_I(TAG, "Sub decode scene enter - Free heap: %zu", memmgr_get_free_heap()); g_decode_ctx = malloc(sizeof(SubDecodeContext)); @@ -567,15 +571,8 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even // Rebuild history view uint16_t history_count = protopirate_history_get_item(ctx->history); if(history_count > 0) { - protopirate_view_receiver_reset_menu(app->protopirate_receiver); - - FuriString* item_text = furi_string_alloc(); - for(uint16_t i = 0; i < history_count; i++) { - protopirate_history_get_text_item_menu(ctx->history, item_text, i); - protopirate_view_receiver_add_item_to_menu( - app->protopirate_receiver, furi_string_get_cstr(item_text), 0); - } - furi_string_free(item_text); + protopirate_view_receiver_sync_menu_from_history( + app->protopirate_receiver, ctx->history); protopirate_view_receiver_set_idx_menu( app->protopirate_receiver, ctx->selected_history_index); @@ -732,6 +729,7 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even ctx->selected_history_index, furi_string_get_cstr(new_str)); furi_string_free(new_str); + protopirate_history_commit_loaded(ctx->history); notification_message(app->notifications, &sequence_success); } free(app->psa_bf_state); @@ -1147,16 +1145,8 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even // Show history list using receiver view (same as receive mode) uint16_t history_count = protopirate_history_get_item(ctx->history); if(history_count > 0) { - // Reset and populate receiver view menu - protopirate_view_receiver_reset_menu(app->protopirate_receiver); - - FuriString* item_text = furi_string_alloc(); - for(uint16_t i = 0; i < history_count; i++) { - protopirate_history_get_text_item_menu(ctx->history, item_text, i); - protopirate_view_receiver_add_item_to_menu( - app->protopirate_receiver, furi_string_get_cstr(item_text), 0); - } - furi_string_free(item_text); + protopirate_view_receiver_sync_menu_from_history( + app->protopirate_receiver, ctx->history); // Set initial selection protopirate_view_receiver_set_idx_menu( @@ -1221,7 +1211,11 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even // Get full text for body furi_string_reset(text); - protopirate_history_get_text_item(ctx->history, text, ctx->selected_history_index); + protopirate_history_get_text_item_detail( + ctx->history, + ctx->selected_history_index, + text, + app->txrx->environment); widget_add_text_scroll_element( app->widget, 0, 0, 128, 50, furi_string_get_cstr(text)); diff --git a/scenes/protopirate_scene_timing_tuner.c b/scenes/protopirate_scene_timing_tuner.c index 87c6265..ffaad98 100644 --- a/scenes/protopirate_scene_timing_tuner.c +++ b/scenes/protopirate_scene_timing_tuner.c @@ -631,6 +631,10 @@ void protopirate_scene_timing_tuner_on_enter(void* context) { g_timing_ctx->buffer_wrapped = false; g_timing_ctx->app = app; + if(app->radio_initialized) { + protopirate_rx_stack_resume_after_tx(app); + } + view_set_draw_callback(app->view_about, timing_tuner_draw_callback); view_set_input_callback(app->view_about, timing_tuner_input_callback); view_set_context(app->view_about, app); diff --git a/views/protopirate_receiver.c b/views/protopirate_receiver.c index e8a9998..711aebe 100644 --- a/views/protopirate_receiver.c +++ b/views/protopirate_receiver.c @@ -1,5 +1,6 @@ // views/protopirate_receiver.c #include "protopirate_receiver.h" +#include "../protopirate_history.h" #include "../protopirate_app_i.h" #include #include @@ -35,6 +36,7 @@ typedef struct { FuriString* frequency_str; FuriString* preset_str; FuriString* history_stat_str; + FuriString* draw_scratch; bool external_radio; ProtoPirateLock lock; uint8_t lock_count; @@ -199,9 +201,6 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo size_t item_count = ProtoPirateReceiverMenuItemArray_size(model->history_item_arr); bool scrollbar = item_count > MENU_ITEMS; - FuriString* str_buff; - str_buff = furi_string_alloc(); - if(!model->sub_decode_mode) { //Config button. (Do it at the top so we dont get Inversion problems from the list view part.) elements_button_left(canvas, "Config"); @@ -244,8 +243,9 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo ProtoPirateReceiverMenuItem* item = ProtoPirateReceiverMenuItemArray_get(model->history_item_arr, idx); - furi_string_set(str_buff, item->item_str); - elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); + furi_string_set(model->draw_scratch, item->item_str); + elements_string_fit_width( + canvas, model->draw_scratch, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); if(model->history_item == idx) { protopirate_view_receiver_draw_frame(canvas, i, scrollbar); @@ -253,7 +253,8 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo canvas_set_color(canvas, ColorBlack); } - canvas_draw_str(canvas, 4, 9 + (i * FRAME_HEIGHT), furi_string_get_cstr(str_buff)); + canvas_draw_str( + canvas, 4, 9 + (i * FRAME_HEIGHT), furi_string_get_cstr(model->draw_scratch)); } //Draw scrollbar if needed @@ -395,8 +396,6 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo canvas, 110 - canvas_string_width(canvas, auto_save_text), 7, auto_save_text); } } - - furi_string_free(str_buff); } bool protopirate_view_receiver_input(InputEvent* event, void* context) { @@ -555,6 +554,8 @@ ProtoPirateReceiver* protopirate_view_receiver_alloc(bool auto_save) { model->frequency_str = furi_string_alloc(); model->preset_str = furi_string_alloc(); model->history_stat_str = furi_string_alloc(); + model->draw_scratch = furi_string_alloc(); + furi_check(model->draw_scratch); model->list_offset = 0; model->history_item = 0; model->rssi = -127.0f; @@ -588,6 +589,7 @@ void protopirate_view_receiver_free(ProtoPirateReceiver* receiver) { furi_string_free(model->frequency_str); furi_string_free(model->preset_str); furi_string_free(model->history_stat_str); + furi_string_free(model->draw_scratch); }, false); @@ -614,6 +616,72 @@ void protopirate_view_receiver_reset_menu(ProtoPirateReceiver* receiver) { false); } +void protopirate_view_receiver_sync_menu_from_history( + ProtoPirateReceiver* receiver, + ProtoPirateHistory* history) { + furi_check(receiver); + furi_check(history); + + protopirate_view_receiver_reset_menu(receiver); + + uint16_t count = protopirate_history_get_item(history); + if(count == 0) { + return; + } + + FuriString* line = furi_string_alloc(); + furi_check(line); + for(uint16_t i = 0; i < count; i++) { + protopirate_history_get_text_item_menu(history, line, i); + protopirate_view_receiver_add_item_to_menu( + receiver, furi_string_get_cstr(line), 0); + } + furi_string_free(line); +} + +void protopirate_view_receiver_pop_first_menu_item(ProtoPirateReceiver* receiver) { + furi_check(receiver); + with_view_model( + receiver->view, + ProtoPirateReceiverModel * model, + { + if(ProtoPirateReceiverMenuItemArray_size(model->history_item_arr) > 0) { + ProtoPirateReceiverMenuItem* first = + ProtoPirateReceiverMenuItemArray_get(model->history_item_arr, 0); + furi_string_free(first->item_str); + ProtoPirateReceiverMenuItemArray_pop_at(NULL, model->history_item_arr, 0); + if(model->history_item > 0) { + model->history_item--; + } + size_t item_count = + ProtoPirateReceiverMenuItemArray_size(model->history_item_arr); + if(model->list_offset > 0 && model->list_offset >= item_count) { + model->list_offset = item_count > 0 ? item_count - 1 : 0; + } + } + }, + true); + protopirate_view_receiver_update_offset(receiver); +} + +void protopirate_view_receiver_append_menu_row_from_history( + ProtoPirateReceiver* receiver, + ProtoPirateHistory* history, + uint16_t idx) { + furi_check(receiver); + furi_check(history); + + FuriString* line = furi_string_alloc(); + if(!line) { + protopirate_view_receiver_sync_menu_from_history(receiver, history); + return; + } + protopirate_history_get_text_item_menu(history, line, idx); + protopirate_view_receiver_add_item_to_menu( + receiver, furi_string_get_cstr(line), 0); + furi_string_free(line); +} + View* protopirate_view_receiver_get_view(ProtoPirateReceiver* receiver) { furi_check(receiver); return receiver->view; diff --git a/views/protopirate_receiver.h b/views/protopirate_receiver.h index ecdb807..87a6711 100644 --- a/views/protopirate_receiver.h +++ b/views/protopirate_receiver.h @@ -5,6 +5,7 @@ #include "../helpers/protopirate_types.h" typedef struct ProtoPirateReceiver ProtoPirateReceiver; +typedef struct ProtoPirateHistory ProtoPirateHistory; typedef void (*ProtoPirateReceiverCallback)(ProtoPirateCustomEvent event, void* context); @@ -38,3 +39,14 @@ void protopirate_view_receiver_set_sub_decode_mode( ProtoPirateReceiver* receiver, bool sub_decode_mode); void protopirate_view_receiver_reset_menu(ProtoPirateReceiver* receiver); + +void protopirate_view_receiver_sync_menu_from_history( + ProtoPirateReceiver* receiver, + ProtoPirateHistory* history); + +void protopirate_view_receiver_pop_first_menu_item(ProtoPirateReceiver* receiver); + +void protopirate_view_receiver_append_menu_row_from_history( + ProtoPirateReceiver* receiver, + ProtoPirateHistory* history, + uint16_t idx);