diff --git a/app_state.h b/app_state.h index b98eb7f0de9..4f951ffdd7e 100644 --- a/app_state.h +++ b/app_state.h @@ -46,6 +46,9 @@ struct AppState { uint32_t current_index; uint8_t current_view; uint8_t previous_view; + uint32_t last_wifi_index; + uint32_t last_ble_index; + uint32_t last_gps_index; char* input_buffer; const char* uart_command; char* textBoxBuffer; diff --git a/application.fam b/application.fam index 71381af0b67..623c34e1b9e 100644 --- a/application.fam +++ b/application.fam @@ -9,7 +9,7 @@ App( fap_category="GPIO/ESP", # Optional values icon="A_GhostESP_14", - fap_version="1.0.9", + fap_version="1.1.0", fap_icon="ghost_esp.png", # 10x10 1-bit PNG fap_icon_assets="images", # Image assets to compile for this application ) diff --git a/confirmation_view.c b/confirmation_view.c index 4cff46d4d08..d4920f08933 100644 --- a/confirmation_view.c +++ b/confirmation_view.c @@ -40,7 +40,7 @@ static void confirmation_view_draw_callback(Canvas* canvas, void* _model) { if(model->text) { // Create a temporary buffer for visible text - const size_t max_visible_chars = 128; // Reduced size + const size_t max_visible_chars = 128; char visible_text[max_visible_chars]; uint8_t current_line = 0; uint8_t visible_lines = 0; diff --git a/docs/changelog.md b/docs/changelog.md index 745a43881a0..e458d9fb497 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,90 +1,101 @@ # Changelog -## v1.0.1 -- Revamped menu structure with logical grouping (scanning, beacon spam, attacks, etc.) -- Simplified command addition and cleaned up documentation in `menu.c` -- Centralized and enum-based settings metadata for improved validation and extensibility -- Enhanced settings with Stop-on-Back feature and ESP reboot command -- Added ESP connection verification and clearer error messaging -- Enabled automatic connectivity check and error recovery for ESP issues -- Unified UI with metadata-driven consistency and better type safety -- Simplified UI view switching and improved error display -- Refined code organization, separating concerns, removing redundancy, and standardizing error handling +## TODO +- Added Easter Egg (Probably on App Info, shows like anim or image or simple game?) +- Improve directory organisation!!!!!!!!!!!!!!!!!!!!!!!!!!!! -## v1.0.2 -- Added confirmation dialogs for WebUI-dependent features in the UI -- Improved settings menu with actions submenu, NVS clearing, and log clearing -- Enhanced memory management and improved settings storage/loading robustness -- Added contextual help for WebUI configuration and confirmation dialogs for command safety -- Improved view navigation, state management, and memory cleanup processes -- Added safeguards against `furi_check` failures with NULL checks and memory initialization -## v1.0.3 -- Enhanced confirmation view structure and readability with better text alignment -- Added confirmation for "Clear Log Files" with a permanent action warning -- Enabled back press exit on confirmation views with callback context handling -- Improved memory management with context cleanup, view state tracking, and transition fixes -- Added NULL checks, fixed memory leaks, and added state tracking for dialogs +## v1.1.0 πŸ•ΈοΈπŸ‘» +- Ring buffer implementation for text handling +- New view buffer management +- Added proper locking mechanisms +- **Remove Filtering due to Firmware updates!** +- Made exiting views more consistent for UE +- **Replaced select a utility text with prompt to show NEW Help Menu** +- Refactored and simplified uart_utils +- Made PCAP file handling more robust +- **Add GPS Menu and commands with saving to .csv** +- Miscellaneous bug fixes -## v1.0.4 -- Refined confirmation view line breaks for readability -- Improved ESP Connectity check to decrease false negatives -- Added optional filtering to UART output to improve readability (BETA) -- Added 'App Info' Button in Settings -- Misc Changes (mostly to UI) +## v1.0.9 +- Fixed log file corruption when stopping captures +- Added proper bounds checking for oversized messages +- Improved text display buffer management +- **Added automatic prefix tagging for WiFi, BLE and system messages** +- Improved storage init speed -## v1.0.5 -- Commands will silently fail if UART isn't working rather than crashing -- Fixed double-free memory issue by removing stream buffer cleanup from the worker thread -- Reorganized initialization order -- UART initialization happens in background -- Serial operations don't block app startup -- Optimized storage initialization by deferring file operations until needed -- Improved directory creation efficiency in storage handling +## v1.0.8 -## v1.0.6 -- Replaced 'Info' command in ESP Check with 'Stop' -- Slightly improved optional UART filtering -- Memory safety improvements. -- Improved Clear Logs to be faster and more efficient -- Added details view to each command accessable with hold of center button. (Like BLE Spam) -- Made ESP Not Connected screen more helpful with prompts to reboot/reflash if issues persist. -- Renamed CONF menu option to SET to better align with actual Settings menu since it's header is "Settings" and there is a configuration submenu -- Replaced textbox for ESP Connection Check with scrollable Confirmation View +### πŸ”΄ CRITICAL FIX - PCAP capture +- **Fixed PCAP file handling and storage system** +- Resolved PCAP file stream corruption issues +- Added proper storage system initialization +- Removed the line buffering logic for PCAP data + +### Improvements +- Added error checking for storage operations +- Filtering majorly improved +- Improved stop on back to be much more reliable by added type-specific stop commands with delays between operations + +## v1.0.7a +- Disable the expansion interface before trying to use UART ## v1.0.7 -- Increased buffers and stacks: MAX_BUFFER_SIZE to 8KB, INITIAL_BUFFER_SIZE to 4KB, BUFFER_CLEAR_SIZE to 128B, uart/app stacks to 4KB/6KB +- **Increased buffers and stacks: MAX_BUFFER_SIZE to 8KB, INITIAL_BUFFER_SIZE to 4KB, BUFFER_CLEAR_SIZE to 128B, uart/app stacks to 4KB/6KB** - Added buffer_mutex with proper timeout handling -- Added Marauder-style data handling +- **Added Marauder-style data handling** - Improved ESP connection reliability - Added view log from start/end configuration setting -- Added line buffering with overflow detection, boundary protection and pre-flush on mode switches +- **Added line buffering with overflow detection, boundary protection and pre-flush on mode switches** -## v1.0.7a -- Disable the expansion interface before trying to use UART +## v1.0.6 +- Replaced 'Info' command in ESP Check with 'Stop' +- Slightly improved optional UART filtering +- Memory safety improvements. +- Improved Clear Logs to be faster and more efficient +- **Added details view to each command accessible with hold of center button. (Like BLE Spam)** +- **Made ESP Not Connected screen more helpful with prompts to reboot/reflash if issues persist.** +- Renamed CONF menu option to SET to better align with actual Settings menu since its header is "Settings" and there is a configuration submenu +- Replaced textbox for ESP Connection Check with scrollable Confirmation View -## v1.0.8 +## v1.0.5 +- **Commands will silently fail if UART isn't working rather than crashing** +- **Fixed double-free memory issue by removing stream buffer cleanup from the worker thread** +- Reorganized initialization order +- **UART initialization happens in background** +- **Serial operations don't block app startup** +- Optimized storage initialization by deferring file operations until needed +- Improved directory creation efficiency in storage handling -### πŸ”΄ CRITICAL FIX - PCAP capture - - Fixed PCAP file handling and storage system - - Resolved PCAP file stream corruption issues - - Added proper storage system initialization - - Removed the line buffering logic for PCAP data +## v1.0.4 +- Refined confirmation view line breaks for readability +- Improved ESP Connectivity check to decrease false negatives +- **Added optional filtering to UART output to improve readability (BETA)** +- **Added 'App Info' Button in Settings** +- Misc Changes (mostly to UI) -### Improvements -- Added error checking for storage operations -- Filtering majorly improved -- Improved stop on back to be much more reliable by added type-specific stop commands with delays between operations +## v1.0.3 +- Enhanced confirmation view structure and readability with better text alignment +- **Added confirmation for "Clear Log Files" with a permanent action warning** +- **Enabled back press exit on confirmation views with callback context handling** +- Improved memory management with context cleanup, view state tracking, and transition fixes +- Added NULL checks, fixed memory leaks, and added state tracking for dialogs +## v1.0.2 +- **Added confirmation dialogs for WebUI-dependent features in the UI** +- Improved settings menu with actions submenu, NVS clearing, and log clearing +- Enhanced memory management and improved settings storage/loading robustness +- **Added contextual help for WebUI configuration and confirmation dialogs for command safety** +- Improved view navigation, state management, and memory cleanup processes +- **Added safeguards against `furi_check` failures with NULL checks and memory initialization** -## v1.0.9 -- Fixed log file corruption when stopping captures -- Added proper bounds checking for oversized messages -- Improved text display buffer management -- Added automatic prefix tagging for WiFi, BLE and system messages -- Improved storage init speed +## v1.0.1 +- **Revamped menu structure with logical grouping (scanning, beacon spam, attacks, etc.)** +- Simplified command addition and cleaned up documentation in `menu.c` +- **Centralized and enum-based settings metadata for improved validation and extensibility** +- **Enhanced settings with Stop-on-Back feature and ESP reboot command** +- **Enabled automatic connectivity check and error recovery for ESP issues** +- **Unified UI with metadata-driven consistency and better type safety** +- Simplified UI view switching and improved error display +- Refined code organization, separating concerns, removing redundancy, and standardizing error handling -## TODO -- Replaced select a utility text with prompt to show NEW Help Menu -- FINALISE optional filtering to UART output -- Improve directory organisation!!!!!!!!!!!!!!!!!!!!!!!!!!!! \ No newline at end of file diff --git a/ghost_esp_icons.h b/ghost_esp_icons.h new file mode 100644 index 00000000000..df73bf8bbbf --- /dev/null +++ b/ghost_esp_icons.h @@ -0,0 +1,8 @@ +#pragma once +#include + +extern const Icon I_Wifi_icon; +extern const Icon I_BLE_icon; +extern const Icon I_GPS_icon; +extern const Icon I_Cog; +extern const Icon I_ButtonDown_7x4; \ No newline at end of file diff --git a/gui_modules/mainmenu.c b/gui_modules/mainmenu.c index 1f182c8b223..d3a7ef419f9 100644 --- a/gui_modules/mainmenu.c +++ b/gui_modules/mainmenu.c @@ -25,6 +25,8 @@ typedef struct { struct MainMenu { View* view; FuriTimer* locked_timer; + MainMenuItemCallback help_callback; + void* help_context; }; typedef struct { @@ -96,26 +98,17 @@ static size_t main_menu_items_on_screen(MainMenuModel* model) { } static bool is_valid_icon_index(size_t position) { - return position <= 3; + return position <= 4; } -static void draw_header(Canvas* canvas, const FuriString* header, uint8_t y_position) { - if(!furi_string_empty(header)) { - canvas_set_font(canvas, FontSecondary); - - const char* header_text = furi_string_get_cstr(header); - int width = canvas_string_width(canvas, header_text); - int x_pos = (canvas_width(canvas) - width) / 2; - - // Draw shadow - canvas_set_color(canvas, ColorWhite); - canvas_draw_str(canvas, x_pos + 1, y_position + 1, header_text); - - // Draw main text - canvas_set_color(canvas, ColorBlack); - canvas_draw_str(canvas, x_pos, y_position, header_text); - } +// Add the new helper function: +void main_menu_set_help_callback(MainMenu* main_menu, MainMenuItemCallback callback, void* context) { + furi_assert(main_menu); + main_menu->help_callback = callback; + main_menu->help_context = context; } + + static CardLayout calculate_card_layout( Canvas* canvas, size_t total_cards, @@ -212,7 +205,6 @@ static void draw_card_label( layout->y + layout->height - TEXT_BOTTOM_MARGIN, furi_string_get_cstr(label)); } - static void main_menu_view_draw_callback(Canvas* canvas, void* _model) { MainMenuModel* model = _model; const size_t total_cards = MainMenuItemArray_size(model->items); @@ -220,10 +212,7 @@ static void main_menu_view_draw_callback(Canvas* canvas, void* _model) { canvas_clear(canvas); - if(!furi_string_empty(model->header)) { - draw_header(canvas, model->header, BASE_Y_POSITION + CARD_HEIGHT + 10); - } - + // Draw all menu items first size_t position = 0; MainMenuItemArray_it_t it; for(MainMenuItemArray_it(it, model->items); !MainMenuItemArray_end_p(it); @@ -245,8 +234,8 @@ static void main_menu_view_draw_callback(Canvas* canvas, void* _model) { switch(position) { case 0: icon = &I_Wifi_icon; break; case 1: icon = &I_BLE_icon; break; - // case 2: icon = &I_GPS; break; - case 2: icon = &I_Cog; break; + case 2: icon = &I_GPS; break; // Use GPS icon for position 2 + case 3: icon = &I_Cog; break; // Use Settings icon for position 3 } } @@ -260,8 +249,30 @@ static void main_menu_view_draw_callback(Canvas* canvas, void* _model) { position++; } -} + // Draw help button last, so it's on top of everything + canvas_set_font(canvas, FontSecondary); + canvas_set_color(canvas, ColorBlack); + + const size_t vertical_offset = 3; + const size_t horizontal_offset = 3; + const size_t string_width = canvas_string_width(canvas, "Help"); + const Icon* icon = &I_ButtonDown_7x4; + const int32_t icon_h_offset = 3; + const int32_t icon_width_with_offset = icon_get_width(icon) + icon_h_offset; + const int32_t icon_v_offset = icon_get_height(icon) + vertical_offset + 1; + const size_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const int32_t x = (canvas_width(canvas) - button_width) / 2; + const int32_t y = canvas_height(canvas); + + // Position at bottom with proper margins + canvas_draw_str(canvas, x + horizontal_offset, y - vertical_offset, "Help"); + canvas_draw_icon( + canvas, x + horizontal_offset + string_width + icon_h_offset, y - icon_v_offset, icon); + // Reset color + canvas_set_color(canvas, ColorBlack); +} static bool main_menu_view_input_callback(InputEvent* event, void* context) { MainMenu* main_menu = context; furi_assert(main_menu); @@ -296,6 +307,13 @@ static bool main_menu_view_input_callback(InputEvent* event, void* context) { consumed = true; main_menu_process_ok(main_menu); break; + case InputKeyDown: + // Handle help button press + if(main_menu->help_callback) { + main_menu->help_callback(main_menu->help_context, 0); + consumed = true; + } + break; default: break; } @@ -312,6 +330,7 @@ static bool main_menu_view_input_callback(InputEvent* event, void* context) { return consumed; } + void main_menu_timer_callback(void* context) { furi_assert(context); MainMenu* main_menu = context; diff --git a/gui_modules/mainmenu.h b/gui_modules/mainmenu.h index 36441bba281..0277f0f3286 100644 --- a/gui_modules/mainmenu.h +++ b/gui_modules/mainmenu.h @@ -117,6 +117,9 @@ void main_menu_set_header(MainMenu* main_menu, const char* header); */ void main_menu_set_orientation(MainMenu* main_menu, ViewOrientation orientation); +void main_menu_set_help_callback(MainMenu* main_menu, MainMenuItemCallback callback, void* context); + + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/main.c b/main.c index 680074b9567..5f2bec5b221 100644 --- a/main.c +++ b/main.c @@ -64,6 +64,14 @@ int32_t ghost_esp_app(void* p) { if (!state) return -1; memset(state, 0, sizeof(AppState)); // Zero all memory first + // Initialize menu selection indices + state->last_wifi_index = 0; + state->last_ble_index = 0; + state->last_gps_index = 0; + state->current_index = 0; + state->current_view = 0; + state->previous_view = 0; + // Initialize essential text buffers with minimal size state->textBoxBuffer = malloc(1); if (state->textBoxBuffer) { diff --git a/menu.c b/menu.c index f6d20e13677..b2853387e3f 100644 --- a/menu.c +++ b/menu.c @@ -552,31 +552,13 @@ static const MenuCommand ble_commands[] = { }, }; -// GPS menu command definitions +// GPS menu command definitions static const MenuCommand gps_commands[] = { { - .label = "Street Detector", - .command = "streetdetector", - .capture_prefix = NULL, - .file_ext = NULL, - .folder = NULL, - .needs_input = false, - .input_text = NULL, - .needs_confirmation = false, - .confirm_header = NULL, - .confirm_text = NULL, - .details_header = "Street Detector", - .details_text = "Scans for GPS coords\n" - "and displays nearby:\n" - "- Street names\n" - "- Intersections\n" - "- Points of interest\n", - }, - { - .label = "WarDrive", - .command = "wardrive", + .label = "WarDrive Mode", + .command = "startwd\n", .capture_prefix = "wardrive_scan", - .file_ext = "csv", + .file_ext = "csv", .folder = GHOST_ESP_APP_FOLDER_WARDRIVE, .needs_input = false, .input_text = NULL, @@ -591,9 +573,24 @@ static const MenuCommand gps_commands[] = { "- GPS coordinates\n" "- Signal strength\n", }, + { + .label = "Stop WarDrive", + .command = "startwd -s\n", + .capture_prefix = NULL, + .file_ext = NULL, + .folder = NULL, + .needs_input = false, + .input_text = NULL, + .needs_confirmation = false, + .confirm_header = NULL, + .confirm_text = NULL, + .details_header = "Stop WarDrive", + .details_text = "Stops wardriving\n" + "mode and saves any\n" + "remaining data.\n", + } }; - void send_uart_command(const char* command, void* state) { AppState* app_state = (AppState*)state; uart_send(app_state->uart_context, (uint8_t*)command, strlen(command)); @@ -617,18 +614,34 @@ void send_uart_command_with_bytes( static void confirmation_ok_callback(void* context) { MenuCommandContext* cmd_ctx = context; if(cmd_ctx && cmd_ctx->state && cmd_ctx->command) { + bool file_opened = false; + + // **Step 1: Open the PCAP File First** + if(cmd_ctx->command->capture_prefix || cmd_ctx->command->file_ext || cmd_ctx->command->folder) { + FURI_LOG_I("Capture", "Attempting to open PCAP file before sending capture command."); + file_opened = uart_receive_data( + cmd_ctx->state->uart_context, + cmd_ctx->state->view_dispatcher, + cmd_ctx->state, + cmd_ctx->command->capture_prefix ? cmd_ctx->command->capture_prefix : "", + cmd_ctx->command->file_ext ? cmd_ctx->command->file_ext : "", + cmd_ctx->command->folder ? cmd_ctx->command->folder : ""); + + if(!file_opened) { + FURI_LOG_E("Capture", "Failed to open PCAP file. Aborting capture command."); + free(cmd_ctx); + return; + } + } + + // **Step 2: Send the Capture Command After File is Opened** send_uart_command(cmd_ctx->command->command, cmd_ctx->state); - uart_receive_data( - cmd_ctx->state->uart_context, - cmd_ctx->state->view_dispatcher, - cmd_ctx->state, - cmd_ctx->command->capture_prefix ? cmd_ctx->command->capture_prefix : "", - cmd_ctx->command->file_ext ? cmd_ctx->command->file_ext : "", - cmd_ctx->command->folder ? cmd_ctx->command->folder : ""); + FURI_LOG_I("Capture", "Capture command sent to firmware."); } free(cmd_ctx); } + static void confirmation_cancel_callback(void* context) { MenuCommandContext* cmd_ctx = context; if(cmd_ctx && cmd_ctx->state) { @@ -685,38 +698,39 @@ static void error_callback(void* context) { state->current_view = state->previous_view; } + static void execute_menu_command(AppState* state, const MenuCommand* command) { // Check ESP connection first if(!uart_is_esp_connected(state->uart_context)) { // Save current view state->previous_view = state->current_view; - - // Use confirmation view to show error + + // Show error and return confirmation_view_set_header(state->confirmation_view, "Connection Error"); confirmation_view_set_text(state->confirmation_view, "ESP Not Connected!\nTry Rebooting ESP.\nReflash if issues persist."); confirmation_view_set_ok_callback(state->confirmation_view, error_callback, state); confirmation_view_set_cancel_callback(state->confirmation_view, error_callback, state); - + view_dispatcher_switch_to_view(state->view_dispatcher, 7); state->current_view = 7; - return; + return; // Important: Return here to prevent further execution } - // Rest of the function remains unchanged + // For commands needing confirmation if(command->needs_confirmation) { MenuCommandContext* cmd_ctx = malloc(sizeof(MenuCommandContext)); cmd_ctx->state = state; cmd_ctx->command = command; - confirmation_view_set_header(state->confirmation_view, command->confirm_header); confirmation_view_set_text(state->confirmation_view, command->confirm_text); confirmation_view_set_ok_callback(state->confirmation_view, confirmation_ok_callback, cmd_ctx); confirmation_view_set_cancel_callback(state->confirmation_view, confirmation_cancel_callback, cmd_ctx); - + view_dispatcher_switch_to_view(state->view_dispatcher, 7); - return; + return; // Important: Return here } + // For commands needing input if(command->needs_input) { state->uart_command = command->command; text_input_reset(state->text_input); @@ -729,20 +743,44 @@ static void execute_menu_command(AppState* state, const MenuCommand* command) { 32, true); view_dispatcher_switch_to_view(state->view_dispatcher, 6); - return; + return; // Important: Return here + } + + // Handle capture commands + if(command->capture_prefix || command->file_ext || command->folder) { + // First open the capture file and switch view + bool file_opened = uart_receive_data( + state->uart_context, + state->view_dispatcher, + state, + command->capture_prefix ? command->capture_prefix : "", + command->file_ext ? command->file_ext : "", + command->folder ? command->folder : ""); + + if(!file_opened) { + FURI_LOG_E("Capture", "Failed to open capture file"); + return; + } + + // Then send command to start capture only after file is ready + furi_delay_ms(10); // Small delay to ensure file is ready + send_uart_command(command->command, state); + return; // Important: Return here } + // For regular commands: + // 1. First send the command send_uart_command(command->command, state); + + // 2. Then switch to text view uart_receive_data( state->uart_context, state->view_dispatcher, state, - command->capture_prefix ? command->capture_prefix : "", - command->file_ext ? command->file_ext : "", - command->folder ? command->folder : ""); + "", // No capture prefix + "", // No file extension + ""); // No folder } - - // Menu display functions void show_wifi_menu(AppState* state) { show_menu(state, wifi_commands, COUNT_OF(wifi_commands), @@ -762,22 +800,26 @@ void show_gps_menu(AppState* state) { // Menu command handlers void handle_wifi_menu(AppState* state, uint32_t index) { if(index < COUNT_OF(wifi_commands)) { + state->last_wifi_index = index; // Save the selection execute_menu_command(state, &wifi_commands[index]); } } void handle_ble_menu(AppState* state, uint32_t index) { if(index < COUNT_OF(ble_commands)) { + state->last_ble_index = index; // Save the selection execute_menu_command(state, &ble_commands[index]); } } void handle_gps_menu(AppState* state, uint32_t index) { if(index < COUNT_OF(gps_commands)) { + state->last_gps_index = index; // Save the selection execute_menu_command(state, &gps_commands[index]); } } + // Callback for text input results static void text_input_result_callback(void* context) { AppState* input_state = (AppState*)context; @@ -814,8 +856,39 @@ void submenu_callback(void* context, uint32_t index) { } } + + +static void show_menu_help(void* context, uint32_t index) { + UNUSED(index); + AppState* state = context; + + // Save current view + state->previous_view = state->current_view; + + // Define help text with essential actions only + const char* help_text = + "Hold [Ok]\n Show details for commands\n\n" + "[OPTIONAL]\nBack in output view\nstops all operations.\n\n\n"; + + // Set header and help text in the confirmation view + confirmation_view_set_header(state->confirmation_view, "Quick Help"); + confirmation_view_set_text(state->confirmation_view, help_text); + + // Set callbacks for user actions + confirmation_view_set_ok_callback(state->confirmation_view, app_info_ok_callback, state); + confirmation_view_set_cancel_callback(state->confirmation_view, app_info_ok_callback, state); + + // Switch to confirmation view to display help + view_dispatcher_switch_to_view(state->view_dispatcher, 7); + state->current_view = 7; +} + + + bool back_event_callback(void* context) { AppState* state = (AppState*)context; + if(!state) return false; + uint32_t current_view = state->current_view; // Allow confirmation view to handle its own back button @@ -823,8 +896,9 @@ bool back_event_callback(void* context) { return false; } - if(current_view == 5) { // Text box view - FURI_LOG_D("Ghost ESP", "Stopping Operations"); + // Handle text box view (view 5) + if(current_view == 5) { + FURI_LOG_D("Ghost ESP", "Handling text box view exit"); // Cleanup text buffer if(state->textBoxBuffer) { @@ -838,80 +912,127 @@ bool back_event_callback(void* context) { // Send stop commands if enabled in settings if(state->settings.stop_on_back_index) { + FURI_LOG_D("Ghost ESP", "Stopping active operations"); + // First stop any packet captures to ensure proper file saving send_uart_command("capture -stop\n", state); - furi_delay_ms(100); // Give time for file operation - - // Reset PCAP state if needed + + // Give more time for PCAP stop command to process + furi_delay_ms(200); + + // Wait for any pending PCAP data if(state->uart_context->pcap) { + // Wait for pcap buffer to empty + for(uint8_t i = 0; i < 10; i++) { // Try up to 10 times + if(state->uart_context->pcap_buf_len > 0) { + furi_delay_ms(50); + } else { + break; + } + } + + // Now safe to reset PCAP state state->uart_context->pcap = false; furi_stream_buffer_reset(state->uart_context->pcap_stream); - furi_delay_ms(50); // Allow buffer cleanup } - // Then stop various operations in a logical order - send_uart_command("stopscan\n", state); - furi_delay_ms(50); - - send_uart_command("stopspam\n", state); - furi_delay_ms(50); - - send_uart_command("stopdeauth\n", state); - furi_delay_ms(50); - - send_uart_command("stopportal\n", state); - furi_delay_ms(50); - - send_uart_command("blescan -s\n", state); - furi_delay_ms(50); - - // Final general stop - send_uart_command("stop\n", state); - furi_delay_ms(50); + // Stop operations in a logical order + const char* stop_commands[] = { + "stopscan\n", + "stopspam\n", + "stopdeauth\n", + "stopportal\n", + "blescan -s\n", + "stop\n" + }; + + for(size_t i = 0; i < COUNT_OF(stop_commands); i++) { + send_uart_command(stop_commands[i], state); + furi_delay_ms(50); + } } - // Close any open files with proper checking - if(state->uart_context->storageContext) { - if(state->uart_context->storageContext->current_file && - storage_file_is_open(state->uart_context->storageContext->current_file)) { - // Give time for final writes - furi_delay_ms(100); - storage_file_close(state->uart_context->storageContext->current_file); - state->uart_context->storageContext->HasOpenedFile = false; - FURI_LOG_D("DEBUG", "Storage File Closed"); - } + // Clean up files using the safe cleanup + if(state->uart_context && state->uart_context->storageContext) { + uart_storage_safe_cleanup(state->uart_context->storageContext); + FURI_LOG_D("Ghost ESP", "Performed safe storage cleanup"); } - // Return to appropriate previous view + // Return to previous menu with selection restored switch(state->previous_view) { - case 1: show_wifi_menu(state); break; - case 2: show_ble_menu(state); break; - case 3: show_gps_menu(state); break; - default: show_main_menu(state); break; + case 1: + show_wifi_menu(state); + submenu_set_selected_item(state->wifi_menu, state->last_wifi_index); + break; + case 2: + show_ble_menu(state); + submenu_set_selected_item(state->ble_menu, state->last_ble_index); + break; + case 3: + show_gps_menu(state); + submenu_set_selected_item(state->gps_menu, state->last_gps_index); + break; + case 8: + view_dispatcher_switch_to_view(state->view_dispatcher, 8); + break; + default: + show_main_menu(state); + break; } state->current_view = state->previous_view; - } else if(current_view == 8) { // Settings menu + } + // Handle settings menu (view 8) + else if(current_view == 8) { show_main_menu(state); state->current_view = 0; - } else if(current_view != 0) { + } + // Handle settings submenu (view 4) + else if(current_view == 4) { + view_dispatcher_switch_to_view(state->view_dispatcher, 8); + state->current_view = 8; + } + // Handle submenu views (1-3) + else if(current_view >= 1 && current_view <= 3) { show_main_menu(state); state->current_view = 0; - } else { + } + // Handle text input view (view 6) + else if(current_view == 6) { + // Return to previous menu with selection restored + switch(state->previous_view) { + case 1: + show_wifi_menu(state); + submenu_set_selected_item(state->wifi_menu, state->last_wifi_index); + break; + case 2: + show_ble_menu(state); + submenu_set_selected_item(state->ble_menu, state->last_ble_index); + break; + case 3: + show_gps_menu(state); + submenu_set_selected_item(state->gps_menu, state->last_gps_index); + break; + default: + show_main_menu(state); + break; + } + state->current_view = state->previous_view; + } + // Handle main menu (view 0) + else if(current_view == 0) { view_dispatcher_stop(state->view_dispatcher); } return true; } - static void show_menu(AppState* state, const MenuCommand* commands, size_t command_count, const char* header, Submenu* menu, uint8_t view_id) { submenu_reset(menu); submenu_set_header(menu, header); for(size_t i = 0; i < command_count; i++) { - // Add items without callback - submenu_add_item(menu, commands[i].label, i, NULL, NULL); + submenu_add_item(menu, commands[i].label, i, submenu_callback, state); } // Set up view with input handler @@ -919,21 +1040,37 @@ static void show_menu(AppState* state, const MenuCommand* commands, size_t comma view_set_context(menu_view, state); view_set_input_callback(menu_view, menu_input_handler); + // Restore last selection based on menu type + uint32_t last_index = 0; + switch(view_id) { + case 1: last_index = state->last_wifi_index; break; + case 2: last_index = state->last_ble_index; break; + case 3: last_index = state->last_gps_index; break; + } + if(last_index < command_count) { + submenu_set_selected_item(menu, last_index); + } + view_dispatcher_switch_to_view(state->view_dispatcher, view_id); state->current_view = view_id; state->previous_view = view_id; } + void show_main_menu(AppState* state) { main_menu_reset(state->main_menu); - main_menu_set_header(state->main_menu, "Select a Utility:"); + main_menu_set_header(state->main_menu, ""); // Empty header since we're drawing our own main_menu_add_item(state->main_menu, "WiFi", 0, submenu_callback, state); main_menu_add_item(state->main_menu, "BLE", 1, submenu_callback, state); - // GPS temporarily hidden - // main_menu_add_item(state->main_menu, "GPS", 2, submenu_callback, state); + main_menu_add_item(state->main_menu, "GPS", 2, submenu_callback, state); // GPS restored main_menu_add_item(state->main_menu, " SET", 3, submenu_callback, state); + + // Set up help callback + main_menu_set_help_callback(state->main_menu, show_menu_help, state); + view_dispatcher_switch_to_view(state->view_dispatcher, 0); state->current_view = 0; } + static bool menu_input_handler(InputEvent* event, void* context) { AppState* state = (AppState*)context; bool consumed = false; @@ -989,7 +1126,6 @@ static bool menu_input_handler(InputEvent* event, void* context) { case InputKeyOk: if(current_index < commands_count) { state->current_index = current_index; - // Execute command execute_menu_command(state, &commands[current_index]); consumed = true; } @@ -1004,20 +1140,29 @@ static bool menu_input_handler(InputEvent* event, void* context) { case InputKeyRight: case InputKeyLeft: case InputKeyMAX: - default: break; } break; case InputTypeLong: - if(event->key == InputKeyOk) { - if(current_index < commands_count) { - const MenuCommand* command = &commands[current_index]; - if(command->details_header && command->details_text) { - show_command_details(state, command); - consumed = true; + switch(event->key) { + case InputKeyUp: + case InputKeyDown: + case InputKeyRight: + case InputKeyLeft: + case InputKeyBack: + case InputKeyMAX: + break; + + case InputKeyOk: + if(current_index < commands_count) { + const MenuCommand* command = &commands[current_index]; + if(command->details_header && command->details_text) { + show_command_details(state, command); + consumed = true; + } } - } + break; } break; @@ -1042,14 +1187,13 @@ static bool menu_input_handler(InputEvent* event, void* context) { case InputKeyOk: case InputKeyBack: case InputKeyMAX: - default: break; } break; case InputTypePress: case InputTypeRelease: - default: + case InputTypeMAX: break; } diff --git a/sequential_file.c b/sequential_file.c index 67515d6be58..8c7a7193d09 100644 --- a/sequential_file.c +++ b/sequential_file.c @@ -1,4 +1,9 @@ #include "sequential_file.h" +#include +#include +#include +#include +#include char* sequential_file_resolve_path( @@ -7,24 +12,88 @@ char* sequential_file_resolve_path( const char* prefix, const char* extension) { if(storage == NULL || dir == NULL || prefix == NULL || extension == NULL) { + FURI_LOG_E("SequentialFile", "Invalid parameters passed to resolve_path"); return NULL; } - char file_path[256]; - int file_index = 0; + // Allocate a file handle for directory operations + File* dir_handle = storage_file_alloc(storage); + if(!dir_handle) { + FURI_LOG_E("SequentialFile", "Failed to allocate file handle for directory"); + return NULL; + } + + // Open the directory + if(!storage_dir_open(dir_handle, dir)) { + FURI_LOG_E("SequentialFile", "Failed to open directory: %s", dir); + storage_file_free(dir_handle); + return NULL; + } + + FileInfo file_info; + char filename[256]; + int highest_index = -1; + size_t prefix_len = strlen(prefix); + size_t extension_len = strlen(extension); + + // Iterate over files in the directory + while(storage_dir_read(dir_handle, &file_info, filename, sizeof(filename))) { + // Skip directories + if(file_info.flags & FSF_DIRECTORY) continue; + + // Check if filename matches the expected pattern + if(strncmp(filename, prefix, prefix_len) != 0) continue; + size_t filename_len = strlen(filename); + + // Verify extension + if(filename_len <= extension_len + 1) continue; // +1 for '.' + if(strcmp(&filename[filename_len - extension_len], extension) != 0) continue; + if(filename[filename_len - extension_len - 1] != '.') continue; + + // Extract the index part of the filename + const char* index_start = filename + prefix_len; + if(*index_start != '_') continue; + index_start++; // Skip the '_' + + // Calculate the length of the index string + size_t index_len = filename_len - prefix_len - extension_len - 2; // -2 for '_' and '.' + + // Copy the index string + char index_str[32]; + if(index_len >= sizeof(index_str)) continue; // Prevent buffer overflow + strncpy(index_str, index_start, index_len); + index_str[index_len] = '\0'; - do { - if(snprintf( - file_path, sizeof(file_path), "%s/%s_%d.%s", dir, prefix, file_index, extension) < - 0) { - return NULL; + // Convert index string to integer + char* endptr; + long index = strtol(index_str, &endptr, 10); + if(*endptr != '\0' || index < 0) continue; // Ensure full conversion and non-negative index + + if(index > highest_index) { + highest_index = (int)index; } - file_index++; - } while(storage_file_exists(storage, file_path)); + } + + storage_dir_close(dir_handle); + storage_file_free(dir_handle); + + // Determine the new index + int new_index = highest_index + 1; + + // Construct the new file path + char file_path[256]; + int snprintf_result = snprintf(file_path, sizeof(file_path), "%s/%s_%d.%s", dir, prefix, new_index, extension); + if(snprintf_result < 0 || (size_t)snprintf_result >= sizeof(file_path)) { + FURI_LOG_E("SequentialFile", "snprintf failed or output truncated in resolve_path"); + return NULL; + } + + FURI_LOG_I("SequentialFile", "Resolved new file path: %s", file_path); return strdup(file_path); } + bool sequential_file_open( Storage* storage, File* file, @@ -32,16 +101,24 @@ bool sequential_file_open( const char* prefix, const char* extension) { if(storage == NULL || file == NULL || dir == NULL || prefix == NULL || extension == NULL) { + FURI_LOG_E("SequentialFile", "Invalid parameters passed to open"); return false; } char* file_path = sequential_file_resolve_path(storage, dir, prefix, extension); if(file_path == NULL) { + FURI_LOG_E("SequentialFile", "Failed to resolve file path"); return false; } + // Open the file with the resolved path bool success = storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS); - free(file_path); + if(success) { + FURI_LOG_I("SequentialFile", "Opened log file: %s", file_path); + } else { + FURI_LOG_E("SequentialFile", "Failed to open log file: %s", file_path); + } + free(file_path); return success; } \ No newline at end of file diff --git a/settings_def.c b/settings_def.c index bb337742864..3832d8a4f90 100644 --- a/settings_def.c +++ b/settings_def.c @@ -2,14 +2,13 @@ #include "settings_def.h" #include #include "callbacks.h" -// Define the constant arrays const char* const SETTING_VALUE_NAMES_RGB_MODE[] = {"Stealth", "Normal", "Rainbow"}; const char* const SETTING_VALUE_NAMES_CHANNEL_HOP[] = {"500ms", "1000ms", "2000ms", "3000ms", "4000ms"}; const char* const SETTING_VALUE_NAMES_BOOL[] = {"False", "True"}; const char* const SETTING_VALUE_NAMES_ACTION[] = {"Press OK", "Press OK"}; const char* const SETTING_VALUE_NAMES_LOG_VIEW[] = {"End", "Start"}; -#include "settings_ui.h" // Add this include at the top +#include "settings_ui.h" const SettingMetadata SETTING_METADATA[SETTINGS_COUNT] = { [SETTING_RGB_MODE] = { @@ -62,7 +61,7 @@ const SettingMetadata SETTING_METADATA[SETTINGS_COUNT] = { .data.setting = { .max_value = 1, .value_names = SETTING_VALUE_NAMES_BOOL, - .uart_command = NULL // No UART command needed since this is handled locally + .uart_command = NULL }, .is_action = false }, @@ -106,13 +105,20 @@ const SettingMetadata SETTING_METADATA[SETTINGS_COUNT] = { .name = "View Logs From", .data.setting = { .max_value = 1, - .value_names = SETTING_VALUE_NAMES_LOG_VIEW, // We'll define this - .uart_command = NULL // No UART command needed since this is handled locally + .value_names = SETTING_VALUE_NAMES_LOG_VIEW, + .uart_command = NULL }, .is_action = false } }; -// Update the function signature to include the is_action flag + +bool setting_is_visible(SettingKey key) { + if(key == SETTING_ENABLE_FILTERING) { + return false; + } + return true; +} + const SettingMetadata* settings_get_metadata(SettingKey key) { if(key >= SETTINGS_COUNT) { return NULL; diff --git a/settings_def.h b/settings_def.h index d1a1fca8040..defe9d60e1c 100644 --- a/settings_def.h +++ b/settings_def.h @@ -10,7 +10,7 @@ typedef enum { SETTING_ENABLE_CHANNEL_HOPPING, SETTING_ENABLE_RANDOM_BLE_MAC, SETTING_STOP_ON_BACK, - SETTING_ENABLE_FILTERING, + SETTING_ENABLE_FILTERING, // Keep for settings file compatibility SETTING_VIEW_LOGS_FROM_START, SETTING_SHOW_INFO, SETTING_REBOOT_ESP, @@ -19,6 +19,7 @@ typedef enum { SETTINGS_COUNT } SettingKey; + // Settings operations result typedef enum { SETTINGS_OK, @@ -87,6 +88,7 @@ extern const char* const SETTING_VALUE_NAMES_ACTION[]; // Function declarations const SettingMetadata* settings_get_metadata(SettingKey key); +bool setting_is_visible(SettingKey key); // Common definitions #define GHOST_ESP_APP_FOLDER "/ext/apps_data/ghost_esp" @@ -98,6 +100,7 @@ const SettingMetadata* settings_get_metadata(SettingKey key); #define SETTINGS_HEADER_MAGIC 0xDEADBEEF #define SETTINGS_FILE_VERSION 1 + // SettingsHeader structure typedef struct { uint32_t magic; diff --git a/settings_storage.c b/settings_storage.c index 3c47bff16f1..d99dc7ff234 100644 --- a/settings_storage.c +++ b/settings_storage.c @@ -1,31 +1,43 @@ #include "settings_storage.h" #include // for logging +#include "uart_storage.h" +static Storage* storage = NULL; -static Storage* storage = NULL; // Forward declarations of static functions static bool write_header(File* file); static bool verify_header(File* file); bool settings_storage_init() { + uint32_t start_time = furi_get_tick(); + FURI_LOG_I("SettingsStorage", "Starting storage initialization"); + if(storage != NULL) { FURI_LOG_I("SettingsStorage", "Storage already initialized"); return true; } - FURI_LOG_I("SettingsStorage", "Starting storage initialization"); + storage = furi_record_open(RECORD_STORAGE); if(storage == NULL) { FURI_LOG_E("SettingsStorage", "Failed to open RECORD_STORAGE"); return false; } + // Attempt to create directory without checking if it exists storage_simply_mkdir(storage, GHOST_ESP_APP_FOLDER); - // Proceed without checking for settings file existence - FURI_LOG_I("SettingsStorage", "Storage initialization complete"); + + uint32_t duration = furi_get_tick() - start_time; + FURI_LOG_I("SettingsStorage", "Storage initialization complete (Time taken: %lu ms)", duration); + return true; } +void uart_storage_sync_file(UartStorageContext* ctx) { + if(ctx && ctx->current_file) { + storage_file_sync(ctx->current_file); + } +} static bool write_header(File* file) { SettingsHeader header = { diff --git a/settings_storage.h b/settings_storage.h index 71e55a57cd7..b80146ff2fc 100644 --- a/settings_storage.h +++ b/settings_storage.h @@ -4,10 +4,11 @@ #include #include - +// Forward declare the struct to avoid circular dependencies +struct UartStorageContext; // Initialize settings storage bool settings_storage_init(); - +void uart_storage_sync_file(struct UartStorageContext* ctx); __attribute__((used)) SettingsResult settings_storage_save(Settings* settings, const char* path); __attribute__((used)) SettingsResult settings_storage_load(Settings* settings, const char* path); \ No newline at end of file diff --git a/settings_ui.c b/settings_ui.c index f3c10b307bb..68f1ce404f8 100644 --- a/settings_ui.c +++ b/settings_ui.c @@ -318,6 +318,11 @@ void settings_setup_gui(VariableItemList* list, SettingsUIContext* context) { // Iterate over all settings for(SettingKey key = 0; key < SETTINGS_COUNT; key++) { + // Skip hidden settings + if(!setting_is_visible(key)) { + continue; + } + const SettingMetadata* metadata = settings_get_metadata(key); if(!metadata) continue; @@ -391,7 +396,7 @@ bool settings_custom_event_callback(void* context, uint32_t event_id) { "Updated by: Jay Candel\n" "Built with <3"; - confirmation_view_set_header(app_state->confirmation_view, "Ghost ESP v1.0.9"); + confirmation_view_set_header(app_state->confirmation_view, "Ghost ESP v1.1.0"); confirmation_view_set_text(app_state->confirmation_view, info_text); // Save current view before switching diff --git a/uart_storage.c b/uart_storage.c index 32a4530a372..116313ff060 100644 --- a/uart_storage.c +++ b/uart_storage.c @@ -27,57 +27,119 @@ static const char* GHOST_DIRECTORIES[] = { return retval; \ } while(0) +void uart_storage_safe_cleanup(UartStorageContext* ctx) { + if(!ctx) return; + + // Safely close current file if open + if(ctx->current_file) { + if(storage_file_is_open(ctx->current_file)) { + storage_file_sync(ctx->current_file); + storage_file_close(ctx->current_file); + } + ctx->HasOpenedFile = false; + } + + // Safely close log file if open + if(ctx->log_file) { + if(storage_file_is_open(ctx->log_file)) { + storage_file_sync(ctx->log_file); + storage_file_close(ctx->log_file); + } + } +} + UartStorageContext* uart_storage_init(UartContext* parentContext) { - uint32_t start_time = furi_get_tick(); + uint32_t func_start_time = furi_get_tick(); FURI_LOG_D("Storage", "Starting storage initialization"); + uint32_t step_start = furi_get_tick(); // Allocate and initialize context UartStorageContext* ctx = malloc(sizeof(UartStorageContext)); + uint32_t elapsed_step = furi_get_tick() - step_start; if(!ctx) { - FURI_LOG_E("Storage", "Failed to allocate context"); + FURI_LOG_E("Storage", "Failed to allocate UartStorageContext (Time taken: %lu ms)", elapsed_step); return NULL; } + FURI_LOG_I("Storage", "Allocated UartStorageContext (Time taken: %lu ms)", elapsed_step); + + // Initialize context fields + step_start = furi_get_tick(); memset(ctx, 0, sizeof(UartStorageContext)); ctx->parentContext = parentContext; + elapsed_step = furi_get_tick() - step_start; + FURI_LOG_I("Storage", "Initialized UartStorageContext fields (Time taken: %lu ms)", elapsed_step); // Open storage API + step_start = furi_get_tick(); ctx->storage_api = furi_record_open(RECORD_STORAGE); + elapsed_step = furi_get_tick() - step_start; if(!ctx->storage_api) { - CLEANUP_AND_RETURN(ctx, "Failed to open storage API", NULL); + FURI_LOG_E("Storage", "Failed to open RECORD_STORAGE (Time taken: %lu ms)", elapsed_step); + free(ctx); + return NULL; } + FURI_LOG_I("Storage", "Opened RECORD_STORAGE (Time taken: %lu ms)", elapsed_step); // Allocate file handles + step_start = furi_get_tick(); ctx->current_file = storage_file_alloc(ctx->storage_api); ctx->log_file = storage_file_alloc(ctx->storage_api); + elapsed_step = furi_get_tick() - step_start; if(!ctx->current_file || !ctx->log_file) { - CLEANUP_AND_RETURN(ctx, "Failed to allocate file handles", NULL); + FURI_LOG_E("Storage", "Failed to allocate file handles (Time taken: %lu ms)", elapsed_step); + uart_storage_free(ctx); + return NULL; } + FURI_LOG_I("Storage", "Allocated current_file and log_file (Time taken: %lu ms)", elapsed_step); - // Create directories using a loop + // Create directories + step_start = furi_get_tick(); size_t num_directories = sizeof(GHOST_DIRECTORIES) / sizeof(GHOST_DIRECTORIES[0]); for(size_t i = 0; i < num_directories; i++) { + uint32_t dir_step_start = furi_get_tick(); if(!storage_simply_mkdir(ctx->storage_api, GHOST_DIRECTORIES[i])) { - // Log warnings instead of errors to minimize critical failure states - FURI_LOG_W("Storage", "Failed to create directory: %s", GHOST_DIRECTORIES[i]); + FURI_LOG_W("Storage", "Failed to create or confirm directory: %s (Time taken: %lu ms)", GHOST_DIRECTORIES[i], furi_get_tick() - dir_step_start); + } else { + FURI_LOG_I("Storage", "Created/Confirmed directory: %s (Time taken: %lu ms)", GHOST_DIRECTORIES[i], furi_get_tick() - dir_step_start); } } + elapsed_step = furi_get_tick() - step_start; + FURI_LOG_I("Storage", "Directory creation/confirmation completed (Time taken: %lu ms)", elapsed_step); + + // Ensure all directories are accessible + step_start = furi_get_tick(); + bool dirs_ok = true; + if(!storage_dir_exists(ctx->storage_api, GHOST_ESP_APP_FOLDER_PCAPS)) { + FURI_LOG_E("Storage", "PCAP directory not accessible"); + dirs_ok = false; + } + if(!storage_dir_exists(ctx->storage_api, GHOST_ESP_APP_FOLDER_LOGS)) { + FURI_LOG_E("Storage", "Logs directory not accessible"); + dirs_ok = false; + } + if(!storage_dir_exists(ctx->storage_api, GHOST_ESP_APP_FOLDER_WARDRIVE)) { + FURI_LOG_E("Storage", "Wardrive directory not accessible"); + dirs_ok = false; + } + elapsed_step = furi_get_tick() - step_start; + FURI_LOG_I("Storage", "Checked directory accessibility (Time taken: %lu ms)", elapsed_step); - // Verify PCAP directory exists and is writable - File* test_dir = storage_file_alloc(ctx->storage_api); - if(test_dir) { - if(!storage_dir_open(test_dir, GHOST_ESP_APP_FOLDER_PCAPS)) { - FURI_LOG_E("Storage", "PCAP directory not accessible"); - // Optionally, decide whether to continue or cleanup - // Here, we choose to disable PCAP mode but continue initialization + if(!dirs_ok) { + step_start = furi_get_tick(); + FURI_LOG_E("Storage", "Critical directories missing, attempting cleanup"); + uart_storage_safe_cleanup(ctx); + // Retry directory creation + for(size_t i = 0; i < num_directories; i++) { + uint32_t retry_dir_start = furi_get_tick(); + storage_simply_mkdir(ctx->storage_api, GHOST_DIRECTORIES[i]); + FURI_LOG_I("Storage", "Retried creating directory: %s (Time taken: %lu ms)", GHOST_DIRECTORIES[i], furi_get_tick() - retry_dir_start); } - storage_dir_close(test_dir); - storage_file_free(test_dir); - } else { - FURI_LOG_W("Storage", "Failed to allocate test directory handle"); - // Decide whether to treat this as critical or not + elapsed_step = furi_get_tick() - step_start; + FURI_LOG_I("Storage", "Retry directory creation completed (Time taken: %lu ms)", elapsed_step); } // Initialize log file + step_start = furi_get_tick(); ctx->HasOpenedFile = sequential_file_open( ctx->storage_api, ctx->log_file, @@ -85,57 +147,98 @@ UartStorageContext* uart_storage_init(UartContext* parentContext) { "ghost_logs", "txt" ); - + elapsed_step = furi_get_tick() - step_start; if(!ctx->HasOpenedFile) { - FURI_LOG_W("Storage", "Failed to open log file"); - // Depending on requirements, decide to disable logging or treat as critical + FURI_LOG_W("Storage", "Failed to open log file, attempting cleanup (Time taken: %lu ms)", elapsed_step); + uart_storage_safe_cleanup(ctx); + // Retry log file creation + step_start = furi_get_tick(); + ctx->HasOpenedFile = sequential_file_open( + ctx->storage_api, + ctx->log_file, + GHOST_ESP_APP_FOLDER_LOGS, + "ghost_logs", + "txt" + ); + elapsed_step = furi_get_tick() - step_start; + if(!ctx->HasOpenedFile) { + FURI_LOG_W("Storage", "Log init failed after cleanup, continuing without logging (Time taken: %lu ms)", elapsed_step); + } else { + FURI_LOG_I("Storage", "Successfully opened log file after retry (Time taken: %lu ms)", elapsed_step); + } + } else { + FURI_LOG_I("Storage", "Opened log file successfully (Time taken: %lu ms)", elapsed_step); } + step_start = furi_get_tick(); ctx->IsWritingToFile = false; + elapsed_step = furi_get_tick() - step_start; + FURI_LOG_I("Storage", "Initialized IsWritingToFile flag (Time taken: %lu ms)", elapsed_step); - FURI_LOG_I("Storage", "Storage initialization completed in %lums", - furi_get_tick() - start_time); + uint32_t func_elapsed = furi_get_tick() - func_start_time; + FURI_LOG_I("Storage", "Storage initialization completed in %lu ms", func_elapsed); return ctx; } + void uart_storage_rx_callback(uint8_t *buf, size_t len, void *context) { - UartContext *app = context; + UartContext *app = (UartContext *)context; - if(!app || !app->storageContext || !app->storageContext->storage_api) { - FURI_LOG_E("Storage", "Invalid storage context"); + // Basic sanity checks with detailed logging + if(!app || !app->storageContext || !buf || len == 0) { + FURI_LOG_E("Storage", "Invalid parameters in storage callback: app=%p, storageContext=%p, buf=%p, len=%zu", + (void*)app, (void*)app->storageContext, (void*)buf, len); return; } - if(!app->storageContext->HasOpenedFile || - !app->storageContext->current_file || - !storage_file_is_open(app->storageContext->current_file)) { - FURI_LOG_W("Storage", "No file open for writing"); - app->pcap = false; // Disable PCAP mode if file isn't ready + // **Ensure PCAP File is Open** + if(!app->storageContext->current_file || !app->storageContext->HasOpenedFile) { + FURI_LOG_E("Storage", "PCAP file is not open. Data cannot be written."); return; } - // Write in chunks to avoid buffer overflow - size_t written = 0; - while(written < len) { - size_t remaining = len - written; - size_t chunk_size = (remaining < PCAP_WRITE_CHUNK_SIZE) ? - remaining : PCAP_WRITE_CHUNK_SIZE; - - uint16_t bytes_written = storage_file_write( - app->storageContext->current_file, - buf + written, - chunk_size - ); - - if(bytes_written != chunk_size) { - FURI_LOG_E("Storage", "Write failed: %u/%zu bytes", bytes_written, chunk_size); - app->pcap = false; // Disable PCAP mode on write failure - break; - } - written += bytes_written; + FURI_LOG_D("Storage", "Received %zu bytes for PCAP write", len); + + // Log the first few bytes for debugging + size_t bytes_to_log = (len < 16) ? len : 16; + FURI_LOG_D("Storage", "First %zu bytes of buffer:", bytes_to_log); + for(size_t i = 0; i < bytes_to_log; i++) { + FURI_LOG_D("Storage", "Byte %zu: 0x%02X", i, buf[i]); + } + if(len > bytes_to_log) { + FURI_LOG_D("Storage", "... (%zu more bytes)", len - bytes_to_log); + } + + // Write data and verify with detailed logging + size_t written = storage_file_write(app->storageContext->current_file, buf, len); + if(written != len) { + FURI_LOG_E("Storage", "Failed to write PCAP data: Expected %zu, Written %zu", len, written); + app->pcap = false; // Reset PCAP state on write failure + return; + } + + FURI_LOG_D("Storage", "Successfully wrote %zu bytes to PCAP file", written); + + // Optionally, calculate and log a checksum for data integrity + uint8_t checksum = 0; + for(size_t i = 0; i < len; i++) { + checksum ^= buf[i]; + } + FURI_LOG_D("Storage", "Data Checksum (XOR): 0x%02X", checksum); + + // Periodic sync every ~8KB to balance between safety and performance with detailed logging + static size_t bytes_since_sync = 0; + bytes_since_sync += len; + FURI_LOG_D("Storage", "Accumulated %zu bytes since last sync", bytes_since_sync); + if(bytes_since_sync >= 8192) { + storage_file_sync(app->storageContext->current_file); + FURI_LOG_D("Storage", "PCAP file synced to storage"); + bytes_since_sync = 0; } } + + void uart_storage_reset_logs(UartStorageContext *ctx) { if(!ctx || !ctx->storage_api) return; @@ -161,13 +264,15 @@ void uart_storage_reset_logs(UartStorageContext *ctx) { void uart_storage_free(UartStorageContext *ctx) { if(!ctx) return; + // Do safe cleanup first + uart_storage_safe_cleanup(ctx); + + // Free resources if(ctx->current_file) { - storage_file_close(ctx->current_file); storage_file_free(ctx->current_file); } if(ctx->log_file) { - storage_file_close(ctx->log_file); storage_file_free(ctx->log_file); } @@ -176,4 +281,4 @@ void uart_storage_free(UartStorageContext *ctx) { } free(ctx); -} +} \ No newline at end of file diff --git a/uart_utils.c b/uart_utils.c index 26def541592..611a28185c0 100644 --- a/uart_utils.c +++ b/uart_utils.c @@ -12,601 +12,346 @@ #define INITIAL_BUFFER_SIZE 2048 #define BUFFER_GROWTH_FACTOR 1.5 #define MAX_BUFFER_SIZE (8 * 1024) // 8KB max -static FuriMutex* buffer_mutex = NULL; -#define MUTEX_TIMEOUT_MS 1000 +#define MUTEX_TIMEOUT_MS 2500 #define BUFFER_CLEAR_SIZE 128 - - - -static void strip_ansi_codes(const char* input, char* output); -static bool process_chunk(LineProcessingState* state, const char* input, - char* output, size_t output_size, - size_t* output_len); -static bool format_line(const char* input, char* output, FilterConfig* config); - -static bool init_buffer_mutex() { - if(!buffer_mutex) { - buffer_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - if(!buffer_mutex) { - FURI_LOG_E("UART", "Failed to create buffer mutex"); - return false; - } - } - return true; -} - -static void uart_rx_callback(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) { - UartContext* uart = (UartContext*)context; +#define BUFFER_RESIZE_CHUNK 1024 +#define TEXT_SCROLL_GUARD_SIZE 64 + +typedef enum { + MARKER_STATE_IDLE, + MARKER_STATE_BEGIN, + MARKER_STATE_CLOSE +} MarkerState; + +static TextBufferManager* text_buffer_alloc(void) { + TextBufferManager* manager = malloc(sizeof(TextBufferManager)); + if(!manager) return NULL; - if(event == FuriHalSerialRxEventData) { - uint8_t data = furi_hal_serial_async_rx(handle); - const char* mark_begin = "[BUF/BEGIN]"; - const char* mark_close = "[BUF/CLOSE]"; - - if(uart->mark_test_idx != 0) { - if(data == mark_begin[uart->mark_test_idx] || - data == mark_close[uart->mark_test_idx]) { - uart->mark_test_buf[uart->mark_test_idx++] = data; - - if(uart->mark_test_idx == sizeof(uart->mark_test_buf)) { - if(!memcmp(uart->mark_test_buf, (void*)mark_begin, sizeof(uart->mark_test_buf))) { - uart->pcap = true; - } else if(!memcmp(uart->mark_test_buf, (void*)mark_close, sizeof(uart->mark_test_buf))) { - uart->pcap = false; - } - uart->mark_test_idx = 0; - } - return; - } else { - if(uart->pcap) { - furi_stream_buffer_send(uart->pcap_stream, uart->mark_test_buf, uart->mark_test_idx, 0); - furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtPcapDone); - } else { - furi_stream_buffer_send(uart->rx_stream, uart->mark_test_buf, uart->mark_test_idx, 0); - furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone); - } - uart->mark_test_idx = 0; - } - } - - if(data == mark_begin[0]) { - uart->mark_test_buf[uart->mark_test_idx++] = data; - } else { - if(uart->pcap) { - furi_stream_buffer_send(uart->pcap_stream, &data, 1, 0); - furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtPcapDone); - } else { - furi_stream_buffer_send(uart->rx_stream, &data, 1, 0); - furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone); - } - } - } -} - -static bool process_chunk(LineProcessingState* state, const char* input, - char* output, size_t output_size, - size_t* output_len) { - if(!state || !input || !output || !output_len) return false; + manager->ring_buffer = malloc(RING_BUFFER_SIZE); + manager->view_buffer = malloc(VIEW_BUFFER_SIZE); + manager->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - size_t input_len = strlen(input); - if(input_len == 0) return false; - - // Handle case where we already have a complete line - char* existing_newline = strchr(state->partial_buffer, '\n'); - if(existing_newline) { - size_t line_len = existing_newline - state->partial_buffer; - if(line_len >= output_size) line_len = output_size - 1; - - memcpy(output, state->partial_buffer, line_len); - output[line_len] = '\0'; - *output_len = line_len; - - size_t remaining = state->partial_len - (line_len + 1); - if(remaining > 0) { - memmove(state->partial_buffer, existing_newline + 1, remaining); - state->partial_len = remaining; - } else { - state->partial_len = 0; - } - state->partial_buffer[state->partial_len] = '\0'; - return true; + if(!manager->ring_buffer || !manager->view_buffer || !manager->mutex) { + free(manager->ring_buffer); + free(manager->view_buffer); + free(manager->mutex); + free(manager); + return NULL; } - - // Append new input if room - size_t space = RX_BUF_SIZE - state->partial_len - 1; - size_t to_copy = (input_len > space) ? space : input_len; - memcpy(state->partial_buffer + state->partial_len, input, to_copy); - state->partial_len += to_copy; - state->partial_buffer[state->partial_len] = '\0'; - - // Look for complete line in combined buffer - char* newline = strchr(state->partial_buffer, '\n'); - if(newline) { - size_t line_len = newline - state->partial_buffer; - if(line_len >= output_size) line_len = output_size - 1; - - memcpy(output, state->partial_buffer, line_len); - output[line_len] = '\0'; - *output_len = line_len; - - size_t remaining = state->partial_len - (line_len + 1); - if(remaining > 0) { - memmove(state->partial_buffer, newline + 1, remaining); - state->partial_len = remaining; - } else { - state->partial_len = 0; - } - state->partial_buffer[state->partial_len] = '\0'; - return true; - } - - // Force output if buffer nearly full - if(state->partial_len >= RX_BUF_SIZE - 2) { - size_t out_len = state->partial_len; - if(out_len >= output_size) out_len = output_size - 1; - - memcpy(output, state->partial_buffer, out_len); - output[out_len] = '\0'; - *output_len = out_len; - - state->partial_len = 0; - state->partial_buffer[0] = '\0'; - return true; - } - - return false; -} - - - - -static bool should_filter_line(const char* raw_line) { - if(!raw_line || !*raw_line) return true; // Filter empty lines - - // First strip ANSI and clean the line - char clean_line[RX_BUF_SIZE]; - strip_ansi_codes(raw_line, clean_line); + manager->ring_read_index = 0; + manager->ring_write_index = 0; + manager->view_buffer_len = 0; + manager->buffer_full = false; - // Debug messages always filtered - if(strstr(clean_line, "wifi:flush") || - strstr(clean_line, "wifi:stop") || - strstr(clean_line, "wifi:lmac") || - strstr(clean_line, "wifi:new:") || - strstr(clean_line, "wifi:station:") || - strstr(clean_line, "wifi:ring_buffer, 0, RING_BUFFER_SIZE); + memset(manager->view_buffer, 0, VIEW_BUFFER_SIZE); - return false; // Default to keeping line + return manager; } -static void clean_text(char* str) { - if(!str) return; +static void text_buffer_free(TextBufferManager* manager) { + if(!manager) return; + if(manager->mutex) furi_mutex_free(manager->mutex); + free(manager->ring_buffer); + free(manager->view_buffer); + free(manager); +} + +static void text_buffer_add(TextBufferManager* manager, const char* data, size_t len) { + if(!manager || !data || !len) return; - const char* read = str; - char* write = str; - bool in_escape = false; - bool last_space = true; + furi_mutex_acquire(manager->mutex, FuriWaitForever); - while(*read) { - unsigned char c = (unsigned char)*read; + for(size_t i = 0; i < len; i++) { + manager->ring_buffer[manager->ring_write_index] = data[i]; + manager->ring_write_index = (manager->ring_write_index + 1) % RING_BUFFER_SIZE; - if(c == '\x1b') { - in_escape = true; - read++; - continue; + if(manager->ring_write_index == manager->ring_read_index) { + manager->ring_read_index = (manager->ring_read_index + 1) % RING_BUFFER_SIZE; + manager->buffer_full = true; } - if(in_escape) { - if((c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - c == 'm') { - in_escape = false; - } - read++; - continue; - } - - if(isspace((unsigned char)c)) { - if(!last_space) { - *write++ = ' '; - last_space = true; - } - } else { - *write++ = c; - last_space = false; - } - read++; } - if(write > str && *(write-1) == ' ') write--; - *write = '\0'; + furi_mutex_release(manager->mutex); } -static void strip_ansi_codes(const char* input, char* output) { - if (!input || !output) return; +static void text_buffer_update_view(TextBufferManager* manager, bool view_from_start) { + if(!manager) return; - size_t j = 0; - bool in_escape = false; - bool in_timestamp = false; - char last_char = 0; + furi_mutex_acquire(manager->mutex, FuriWaitForever); - for (size_t i = 0; input[i]; i++) { - unsigned char c = input[i]; - char next_char = input[i + 1]; - - // Handle ANSI escape sequences - if (c == '\x1b' || (c == '[' && last_char == '\x1b')) { - in_escape = true; - last_char = c; - continue; - } - - if (in_escape) { - if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == 'm') { - in_escape = false; - } - last_char = c; - continue; - } - - // Handle timestamps - if (c == '(' && next_char && isdigit((unsigned char)next_char)) { - in_timestamp = true; - continue; - } - if (in_timestamp) { - if (c == ')') { - in_timestamp = false; - if (next_char && !isspace((unsigned char)next_char)) { - output[j++] = ' '; - } - } - continue; - } - - // Skip color code fragments - if (c == ';' && next_char && isdigit((unsigned char)next_char) && - input[i + 2] && input[i + 2] == 'm') { - i += 2; - continue; - } - - if (j < RX_BUF_SIZE - 1) { - output[j++] = c; - } - last_char = c; + // Calculate available data + size_t available; + if(manager->buffer_full) { + available = RING_BUFFER_SIZE; + } else if(manager->ring_write_index >= manager->ring_read_index) { + available = manager->ring_write_index - manager->ring_read_index; + } else { + available = RING_BUFFER_SIZE - manager->ring_read_index + manager->ring_write_index; } - output[j] = '\0'; - clean_text(output); -} - -static bool format_line(const char* input, char* output, FilterConfig* config) { - if (!input || !output || !config) return false; + // Limit to view buffer size + size_t copy_size = (available > VIEW_BUFFER_SIZE - 1) ? VIEW_BUFFER_SIZE - 1 : available; - if (!config->enabled) { - strncpy(output, input, RX_BUF_SIZE - 1); - output[RX_BUF_SIZE - 1] = '\0'; - return true; + // Choose starting point based on view preference + size_t start; + if(view_from_start) { + // Start from oldest data + start = manager->ring_read_index; + } else { + // Start from newest data that will fit in view + size_t data_to_skip = available > copy_size ? available - copy_size : 0; + start = (manager->ring_read_index + data_to_skip) % RING_BUFFER_SIZE; } - char* temp = malloc(RX_BUF_SIZE); - if (!temp) return false; - - // Do all processing on cleaned line - strip_ansi_codes(input, temp); - clean_text(temp); - - bool keep_line = !should_filter_line(temp); - - if (keep_line) { - // Add prefix if needed - const char* prefix = ""; - if (config->add_prefixes) { - if (strstr(temp, "WiFi") || strstr(temp, "AP_MANAGER") || - strstr(temp, "SSID:") || strstr(temp, "BSSID:")) { - prefix = "[WIFI] "; - } else if (strstr(temp, "BLE")) { - prefix = "[BLE] "; - } else if (strstr(temp, "Found Flipper")) { - prefix = "[FLIPPER] "; - } - } - - if (strlen(prefix) > 0) { - snprintf(output, RX_BUF_SIZE - 1, "%s%s", prefix, temp); - } else { - strncpy(output, temp, RX_BUF_SIZE - 1); - } - output[RX_BUF_SIZE - 1] = '\0'; - } - - free(temp); - return keep_line; -} -static bool format_line_with_state(LineProcessingState* state, - const char* input, - char* output, - FilterConfig* config) { - size_t output_len = 0; - if (!process_chunk(state, input, output, RX_BUF_SIZE, &output_len)) { - return false; + // Copy data to view buffer + size_t j = 0; + for(size_t i = 0; i < copy_size; i++) { + size_t idx = (start + i) % RING_BUFFER_SIZE; + manager->view_buffer[j++] = manager->ring_buffer[idx]; } - char* temp_buffer = malloc(RX_BUF_SIZE); - if (!temp_buffer) return false; - - strncpy(temp_buffer, output, RX_BUF_SIZE - 1); - temp_buffer[RX_BUF_SIZE - 1] = '\0'; + manager->view_buffer[j] = '\0'; + manager->view_buffer_len = j; - bool result = format_line(temp_buffer, output, config); - - free(temp_buffer); - return result; -} - -static UartLineState* get_line_state(UartContext* uart) { - if(!uart->line_state) { - uart->line_state = malloc(sizeof(UartLineState)); - if(uart->line_state) { - memset(uart->line_state, 0, sizeof(UartLineState)); - uart->line_state->initialized = true; - } - } - return uart->line_state; + furi_mutex_release(manager->mutex); } +static void uart_rx_callback(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) { + UartContext* uart = (UartContext*)context; + const char* mark_begin = "[BUF/BEGIN]"; + const char* mark_close = "[BUF/CLOSE]"; + size_t mark_len = 11; -void handle_uart_rx_data(uint8_t *buf, size_t len, void *context) { - AppState *state = (AppState *)context; - - if(!state || !state->uart_context || !state->uart_context->is_serial_active) return; - if(!buf || len == 0) return; - - // Initialize buffer mutex if needed - if(!init_buffer_mutex()) return; - - // Try to acquire mutex with timeout - if(furi_mutex_acquire(buffer_mutex, MUTEX_TIMEOUT_MS) != FuriStatusOk) { - FURI_LOG_W("UART", "Buffer mutex timeout"); + if(!uart || !uart->text_manager || event != FuriHalSerialRxEventData) { return; } - // Get instance-specific line state - UartLineState* uart_line_state = get_line_state(state->uart_context); - if(!uart_line_state) { - furi_mutex_release(buffer_mutex); - return; - } + uint8_t data = furi_hal_serial_async_rx(handle); + + // Check if we're collecting a marker + if(uart->mark_test_idx > 0) { + // Prevent buffer overflow + if(uart->mark_test_idx >= sizeof(uart->mark_test_buf)) { + uart->mark_test_idx = 0; + return; + } - // Handle logging to file first, before any buffer modifications - if(state->uart_context->storageContext && - state->uart_context->storageContext->log_file) { - // Ensure proper null termination for logging - uint8_t* log_buf = malloc(len + 1); - if(log_buf) { - memcpy(log_buf, buf, len); - log_buf[len] = '\0'; + if(uart->mark_test_idx < mark_len && + (data == mark_begin[uart->mark_test_idx] || data == mark_close[uart->mark_test_idx])) { + uart->mark_test_buf[uart->mark_test_idx++] = data; - uint16_t written = storage_file_write( - state->uart_context->storageContext->log_file, - log_buf, - len - ); + if(uart->mark_test_idx == mark_len) { + furi_mutex_acquire(uart->text_manager->mutex, FuriWaitForever); + + if(!memcmp(uart->mark_test_buf, mark_begin, mark_len)) { + uart->pcap = true; + FURI_LOG_I("UART", "Capture started"); + } else if(!memcmp(uart->mark_test_buf, mark_close, mark_len)) { + uart->pcap = false; + FURI_LOG_I("UART", "Capture ended"); + } + + furi_mutex_release(uart->text_manager->mutex); + uart->mark_test_idx = 0; + return; + } + // Don't process marker bytes + return; + } else { + // Mismatch occurred, handle buffered bytes atomically + furi_mutex_acquire(uart->text_manager->mutex, FuriWaitForever); - if(written != len) { - FURI_LOG_E("UART", "Log write failed: %d/%d bytes", written, len); - // Force a file sync to ensure data is written - storage_file_sync(state->uart_context->storageContext->log_file); + // Ensure valid thread and stream before sending + if(uart->rx_thread && (uart->pcap ? uart->pcap_stream : uart->rx_stream)) { + if(uart->pcap) { + if(furi_stream_buffer_send(uart->pcap_stream, uart->mark_test_buf, + uart->mark_test_idx, 0) == uart->mark_test_idx) { + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtPcapDone); + } + } else { + if(furi_stream_buffer_send(uart->rx_stream, uart->mark_test_buf, + uart->mark_test_idx, 0) == uart->mark_test_idx) { + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone); + } + } } - free(log_buf); + furi_mutex_release(uart->text_manager->mutex); + uart->mark_test_idx = 0; } } - // Ensure proper null termination of input for display - if(len >= RX_BUF_SIZE) len = RX_BUF_SIZE - 1; - buf[len] = '\0'; - - if(!state->filter_config || !state->filter_config->enabled) { - // Unfiltered handling - size_t new_total_len = state->buffer_length + len + 1; - - if(new_total_len > MAX_BUFFER_SIZE) { - size_t keep_size = MAX_BUFFER_SIZE / 2; - if(state->textBoxBuffer && state->buffer_length > keep_size) { - memmove(state->textBoxBuffer, - state->textBoxBuffer + state->buffer_length - keep_size, - keep_size); - state->buffer_length = keep_size; - new_total_len = keep_size + len + 1; - } else { - if(state->textBoxBuffer) free(state->textBoxBuffer); - state->textBoxBuffer = malloc(BUFFER_CLEAR_SIZE); - if(state->textBoxBuffer) state->textBoxBuffer[0] = '\0'; - state->buffer_length = 0; - new_total_len = len + 1; - } - } - - char* new_buffer = realloc(state->textBoxBuffer, new_total_len); - if(!new_buffer) { - FURI_LOG_E("UART", "Buffer realloc failed"); - furi_mutex_release(buffer_mutex); - return; - } - - memcpy(new_buffer + state->buffer_length, buf, len); - new_buffer[new_total_len - 1] = '\0'; - state->textBoxBuffer = new_buffer; - state->buffer_length = new_total_len - 1; + // Start of a potential marker + if(data == mark_begin[0] || data == mark_close[0]) { + uart->mark_test_buf[0] = data; + uart->mark_test_idx = 1; + return; + } - } else { - // Filtered handling with instance-specific state - char output[RX_BUF_SIZE]; - - if(format_line_with_state(&uart_line_state->line_state, (char*)buf, output, state->filter_config)) { - size_t output_len = strlen(output); - if(output_len > 0) { // Only process non-empty output - size_t new_total_len = state->buffer_length + output_len + 2; // +2 for newline and null - - if(new_total_len > MAX_BUFFER_SIZE) { - // Find last complete line before truncating - char* last_nl = strrchr(state->textBoxBuffer, '\n'); - if(last_nl) { - size_t keep_size = last_nl - state->textBoxBuffer + 1; - memmove(state->textBoxBuffer, state->textBoxBuffer, keep_size); - state->buffer_length = keep_size; - new_total_len = keep_size + output_len + 2; - } else { - state->buffer_length = 0; - new_total_len = output_len + 2; - } - } + // Handle regular data atomically + furi_mutex_acquire(uart->text_manager->mutex, FuriWaitForever); + + bool current_pcap = uart->pcap; + bool success = false; - char* new_buffer = realloc(state->textBoxBuffer, new_total_len); - if(new_buffer) { - if(state->buffer_length == 0) { - new_buffer[0] = '\0'; - } - state->textBoxBuffer = new_buffer; - strcat(new_buffer, output); - strcat(new_buffer, "\n"); - state->buffer_length = strlen(new_buffer); - } + // Ensure valid thread and stream before sending + if(uart->rx_thread && (current_pcap ? uart->pcap_stream : uart->rx_stream)) { + if(current_pcap) { + if(furi_stream_buffer_send(uart->pcap_stream, &data, 1, 0) == 1) { + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtPcapDone); + success = true; + FURI_LOG_D("UART", "Captured data byte (pcap=true): 0x%02X", data); + } + } else { + if(furi_stream_buffer_send(uart->rx_stream, &data, 1, 0) == 1) { + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone); + success = true; + FURI_LOG_D("UART", "Logged data byte (pcap=false): 0x%02X", data); } } } - // Update text box based on view preference - if(state->textBoxBuffer && state->buffer_length > 0) { - bool view_from_start = state->settings.view_logs_from_start_index; + if(!success) { + FURI_LOG_W("UART", "Failed to send data byte: 0x%02X", data); + } + + furi_mutex_release(uart->text_manager->mutex); +} + +void handle_uart_rx_data(uint8_t *buf, size_t len, void *context) { + AppState *state = (AppState *)context; + if(!state || !state->uart_context || !state->uart_context->is_serial_active || + !buf || len == 0) { + FURI_LOG_W("UART", "Invalid parameters in handle_uart_rx_data"); + return; + } + + // Only log data if NOT in PCAP mode + if(!state->uart_context->pcap && + state->uart_context->storageContext && + state->uart_context->storageContext->log_file && + state->uart_context->storageContext->HasOpenedFile) { + static size_t bytes_since_sync = 0; + + size_t written = storage_file_write( + state->uart_context->storageContext->log_file, + buf, + len + ); - if(view_from_start) { - text_box_set_text(state->text_box, state->textBoxBuffer); - text_box_set_focus(state->text_box, TextBoxFocusStart); + if(written != len) { + FURI_LOG_E("UART", "Failed to write log data: expected %zu, wrote %zu", len, written); } else { - size_t display_size = state->buffer_length; - if(display_size > (size_t)TEXT_BOX_STORE_SIZE - 1) { - display_size = (size_t)TEXT_BOX_STORE_SIZE - 1; + bytes_since_sync += written; + if(bytes_since_sync >= 8192) { + storage_file_sync(state->uart_context->storageContext->log_file); + bytes_since_sync = 0; + FURI_LOG_D("UART", "Synced log file to storage"); } - const char* display_start = state->textBoxBuffer + - (state->buffer_length > display_size ? state->buffer_length - display_size : 0); - text_box_set_text(state->text_box, display_start); - text_box_set_focus(state->text_box, TextBoxFocusEnd); } } - furi_mutex_release(buffer_mutex); + // Update text display + text_buffer_add(state->uart_context->text_manager, (char*)buf, len); + text_buffer_update_view(state->uart_context->text_manager, + state->settings.view_logs_from_start_index); + + bool view_from_start = state->settings.view_logs_from_start_index; + text_box_set_text(state->text_box, state->uart_context->text_manager->view_buffer); + text_box_set_focus(state->text_box, + view_from_start ? TextBoxFocusStart : TextBoxFocusEnd); } -// UART worker thread function + static int32_t uart_worker(void* context) { - UartContext* uart = (void*)context; + UartContext* uart = (UartContext*)context; + + FURI_LOG_I("Worker", "UART worker thread started"); while(1) { uint32_t events = furi_thread_flags_wait( - WORKER_ALL_RX_EVENTS, - FuriFlagWaitAny, + WORKER_ALL_RX_EVENTS, + FuriFlagWaitAny, FuriWaitForever); - - furi_check((events & FuriFlagError) == 0); - - if(events & WorkerEvtStop) break; - + + FURI_LOG_D("Worker", "Received events: 0x%08lX", (unsigned long)events); + + if(events & WorkerEvtStop) { + FURI_LOG_I("Worker", "Stopping worker thread"); + break; + } + if(events & WorkerEvtRxDone) { size_t len = furi_stream_buffer_receive( - uart->rx_stream, - uart->rx_buf, - RX_BUF_SIZE, + uart->rx_stream, + uart->rx_buf, + RX_BUF_SIZE, 0); - - if(len > 0) { - if(uart->handle_rx_data_cb) - uart->handle_rx_data_cb(uart->rx_buf, len, uart->state); + + FURI_LOG_D("Worker", "Processing rx_stream data: %zu bytes", len); + + if(len > 0 && uart->handle_rx_data_cb) { + FURI_LOG_D("Worker", "Invoking handle_rx_data_cb with %zu bytes", len); + uart->handle_rx_data_cb(uart->rx_buf, len, uart->state); + FURI_LOG_D("Worker", "rx_stream callback invoked successfully"); + } else { + if(len == 0) { + FURI_LOG_W("Worker", "Received zero bytes from rx_stream"); + } + if(!uart->handle_rx_data_cb) { + FURI_LOG_E("Worker", "handle_rx_data_cb is NULL"); + } } } - + if(events & WorkerEvtPcapDone) { size_t len = furi_stream_buffer_receive( - uart->pcap_stream, - uart->rx_buf, - RX_BUF_SIZE, + uart->pcap_stream, + uart->rx_buf, + RX_BUF_SIZE, 0); - - if(len > 0) { - if(uart->handle_rx_pcap_cb) - uart->handle_rx_pcap_cb(uart->rx_buf, len, uart); + + FURI_LOG_D("Worker", "Processing pcap_stream data: %zu bytes", len); + + if(len > 0 && uart->handle_rx_pcap_cb) { + FURI_LOG_D("Worker", "Invoking handle_rx_pcap_cb with %zu bytes", len); + uart->handle_rx_pcap_cb(uart->rx_buf, len, uart); // Corrected context + FURI_LOG_D("Worker", "pcap_stream callback invoked successfully"); + } else { + if(len == 0) { + FURI_LOG_W("Worker", "Received zero bytes from pcap_stream"); + } + if(!uart->handle_rx_pcap_cb) { + FURI_LOG_E("Worker", "handle_rx_pcap_cb is NULL"); + } } } } + // Clean up streams with detailed logging + FURI_LOG_I("Worker", "Cleaning up rx_stream and pcap_stream buffers"); furi_stream_buffer_free(uart->rx_stream); furi_stream_buffer_free(uart->pcap_stream); + FURI_LOG_I("Worker", "Worker thread exited"); return 0; } + void update_text_box_view(AppState* state) { - if(!state || !state->text_box || !state->textBoxBuffer) return; + if(!state || !state->text_box || !state->uart_context || !state->uart_context->text_manager) return; + text_buffer_update_view(state->uart_context->text_manager, + state->settings.view_logs_from_start_index); + bool view_from_start = state->settings.view_logs_from_start_index; - size_t content_length = strlen(state->textBoxBuffer); - - if(view_from_start) { - // Show from beginning - text_box_set_text(state->text_box, state->textBoxBuffer); - text_box_set_focus(state->text_box, TextBoxFocusStart); - } else { - // Show from end with proper size handling - size_t display_size = content_length; - if(display_size > (size_t)TEXT_BOX_STORE_SIZE - 1) { - display_size = (size_t)TEXT_BOX_STORE_SIZE - 1; - } - const char* display_start = state->textBoxBuffer + - (content_length > display_size ? content_length - display_size : 0); - text_box_set_text(state->text_box, display_start); - text_box_set_focus(state->text_box, TextBoxFocusEnd); - } + text_box_set_text(state->text_box, state->uart_context->text_manager->view_buffer); + text_box_set_focus(state->text_box, + view_from_start ? TextBoxFocusStart : TextBoxFocusEnd); } UartContext* uart_init(AppState* state) { + uint32_t start_time = furi_get_tick(); FURI_LOG_I("UART", "Starting UART initialization"); - + UartContext* uart = malloc(sizeof(UartContext)); if(!uart) { FURI_LOG_E("UART", "Failed to allocate UART context"); @@ -614,24 +359,21 @@ UartContext* uart_init(AppState* state) { } memset(uart, 0, sizeof(UartContext)); - // Initialize mutex - if(!init_buffer_mutex()) { - free(uart); - return NULL; - } - uart->state = state; uart->is_serial_active = false; - uart->streams_active = false; - uart->storage_active = false; uart->pcap = false; uart->mark_test_idx = 0; + uart->pcap_buf_len = 0; - // Stream initialization + // Initialize rx/pcap streams uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); - uart->pcap_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); - uart->storage_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); - uart->streams_active = (uart->rx_stream && uart->pcap_stream && uart->storage_stream); + uart->pcap_stream = furi_stream_buffer_alloc(PCAP_BUF_SIZE, 1); + + if(!uart->rx_stream || !uart->pcap_stream) { + FURI_LOG_E("UART", "Failed to allocate stream buffers"); + uart_free(uart); + return NULL; + } // Set callbacks uart->handle_rx_data_cb = handle_uart_rx_data; @@ -641,15 +383,23 @@ UartContext* uart_init(AppState* state) { uart->rx_thread = furi_thread_alloc(); if(uart->rx_thread) { furi_thread_set_name(uart->rx_thread, "UART_Receive"); - furi_thread_set_stack_size(uart->rx_thread, 4096); + furi_thread_set_stack_size(uart->rx_thread, 2048); furi_thread_set_context(uart->rx_thread, uart); furi_thread_set_callback(uart->rx_thread, uart_worker); furi_thread_start(uart->rx_thread); + } else { + FURI_LOG_E("UART", "Failed to allocate rx thread"); + uart_free(uart); + return NULL; } // Initialize storage uart->storageContext = uart_storage_init(uart); - uart->storage_active = (uart->storageContext != NULL); + if(!uart->storageContext) { + FURI_LOG_E("UART", "Failed to initialize storage"); + uart_free(uart); + return NULL; + } // Initialize serial uart->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart); @@ -657,30 +407,32 @@ UartContext* uart_init(AppState* state) { furi_hal_serial_init(uart->serial_handle, 115200); uart->is_serial_active = true; furi_hal_serial_async_rx_start(uart->serial_handle, uart_rx_callback, uart, false); + } else { + FURI_LOG_E("UART", "Failed to acquire serial handle"); + uart_free(uart); + return NULL; + } + + // Initialize text manager + uart->text_manager = text_buffer_alloc(); + if(!uart->text_manager) { + FURI_LOG_E("UART", "Failed to allocate text manager"); + uart_free(uart); + return NULL; } + uint32_t duration = furi_get_tick() - start_time; + FURI_LOG_I("UART", "UART initialization complete (Time taken: %lu ms)", duration); + return uart; } + void uart_free(UartContext *uart) { if(!uart) return; - // Clean up line state - if(uart->line_state) { - free(uart->line_state); - uart->line_state = NULL; - } - - - // Stop receiving new data first - if(uart->serial_handle) { - furi_hal_serial_async_rx_stop(uart->serial_handle); - furi_delay_ms(10); - } - - // Clean up mutex if it exists - if(buffer_mutex) { - furi_mutex_free(buffer_mutex); - buffer_mutex = NULL; + if(uart->text_manager) { + text_buffer_free(uart->text_manager); + uart->text_manager = NULL; } // Stop thread and wait for it to finish @@ -699,7 +451,6 @@ void uart_free(UartContext *uart) { // Free streams after everything is stopped if(uart->rx_stream) furi_stream_buffer_free(uart->rx_stream); if(uart->pcap_stream) furi_stream_buffer_free(uart->pcap_stream); - if(uart->storage_stream) furi_stream_buffer_free(uart->storage_stream); // Clean up storage context last if(uart->storageContext) uart_storage_free(uart->storageContext); @@ -729,91 +480,70 @@ void uart_send(UartContext *uart, const uint8_t *data, size_t len) { bool uart_is_esp_connected(UartContext* uart) { FURI_LOG_D("UART", "Checking ESP connection..."); - if(!uart || !uart->serial_handle || !uart->state) { + if(!uart || !uart->serial_handle || !uart->text_manager) { FURI_LOG_E("UART", "Invalid UART context"); return false; } - // Save state - FilterConfig* saved_config = uart->state->filter_config; - uart->state->filter_config = NULL; + // Temporarily disable callbacks + furi_hal_serial_async_rx_stop(uart->serial_handle); - // Initialize clean buffer with mutex protection - if(furi_mutex_acquire(buffer_mutex, MUTEX_TIMEOUT_MS) != FuriStatusOk) { - FURI_LOG_E("UART", "Failed to acquire mutex"); - uart->state->filter_config = saved_config; - return false; - } + // Clear and reset buffers atomically + furi_mutex_acquire(uart->text_manager->mutex, FuriWaitForever); + memset(uart->text_manager->ring_buffer, 0, RING_BUFFER_SIZE); + memset(uart->text_manager->view_buffer, 0, VIEW_BUFFER_SIZE); + uart->text_manager->ring_read_index = 0; + uart->text_manager->ring_write_index = 0; + uart->text_manager->buffer_full = false; + uart->text_manager->view_buffer_len = 0; + furi_mutex_release(uart->text_manager->mutex); + + // Re-enable callbacks with clean state + furi_hal_serial_async_rx_start(uart->serial_handle, uart_rx_callback, uart, false); - if(uart->state->textBoxBuffer) { - free(uart->state->textBoxBuffer); - } - uart->state->textBoxBuffer = malloc(RX_BUF_SIZE); // Larger buffer - if(!uart->state->textBoxBuffer) { - FURI_LOG_E("UART", "Buffer allocation failed"); - furi_mutex_release(buffer_mutex); - uart->state->filter_config = saved_config; - return false; - } - uart->state->textBoxBuffer[0] = '\0'; - uart->state->buffer_length = 0; - furi_mutex_release(buffer_mutex); - - // Try multiple test commands - const char* test_cmds[] = { - "stop\n", // Stop any running operation - }; + // Flush any pending data + furi_hal_serial_tx(uart->serial_handle, (uint8_t*)"\r\n", 2); + furi_delay_ms(50); // Allow time for flush + const char* test_cmd = "stop\n"; bool connected = false; - const uint32_t RETRY_COUNT = 3; - const uint32_t CMD_TIMEOUT_MS = 250; // Longer timeout per command - - for(uint32_t retry = 0; retry < RETRY_COUNT && !connected; retry++) { - for(size_t i = 0; i < COUNT_OF(test_cmds) && !connected; i++) { - // Clear buffer before each command - if(furi_mutex_acquire(buffer_mutex, MUTEX_TIMEOUT_MS) == FuriStatusOk) { - uart->state->textBoxBuffer[0] = '\0'; - uart->state->buffer_length = 0; - furi_mutex_release(buffer_mutex); - } + const uint32_t CMD_TIMEOUT_MS = 500; - // Send test command - uart_send(uart, (uint8_t*)test_cmds[i], strlen(test_cmds[i])); - FURI_LOG_D("UART", "Sent command: %s", test_cmds[i]); - - uint32_t start_time = furi_get_tick(); - while(furi_get_tick() - start_time < CMD_TIMEOUT_MS) { - if(furi_mutex_acquire(buffer_mutex, MUTEX_TIMEOUT_MS) == FuriStatusOk) { - if(uart->state->textBoxBuffer && uart->state->buffer_length > 0) { - FURI_LOG_D("UART", "Got response: %.20s", uart->state->textBoxBuffer); - connected = true; - furi_mutex_release(buffer_mutex); - break; - } - furi_mutex_release(buffer_mutex); - } - furi_delay_ms(10); - } - } + // Send test command + uart_send(uart, (uint8_t*)test_cmd, strlen(test_cmd)); + FURI_LOG_D("UART", "Sent command: %s", test_cmd); - if(!connected && retry < RETRY_COUNT - 1) { - FURI_LOG_D("UART", "Retry %lu of %lu", retry + 1, RETRY_COUNT); - furi_delay_ms(100); // Delay between retries + uint32_t start_time = furi_get_tick(); + while(furi_get_tick() - start_time < CMD_TIMEOUT_MS) { + furi_mutex_acquire(uart->text_manager->mutex, FuriWaitForever); + + // Check if we received any data + size_t available; + if(uart->text_manager->buffer_full) { + available = RING_BUFFER_SIZE; + } else if(uart->text_manager->ring_write_index >= uart->text_manager->ring_read_index) { + available = uart->text_manager->ring_write_index - uart->text_manager->ring_read_index; + } else { + available = RING_BUFFER_SIZE - uart->text_manager->ring_read_index + uart->text_manager->ring_write_index; } + + if(available > 0) { + connected = true; + FURI_LOG_D("UART", "Received %d bytes response", available); + } + + furi_mutex_release(uart->text_manager->mutex); + + if(connected) break; + furi_delay_ms(10); } - // Restore state - uart->state->filter_config = saved_config; - - if(!connected) { - FURI_LOG_W("UART", "ESP connection check failed after all retries"); - } else { - FURI_LOG_I("UART", "ESP connected successfully"); - } - + FURI_LOG_I("UART", "ESP connection check: %s", connected ? "Success" : "Failed"); return connected; } -void uart_receive_data( + + +bool uart_receive_data( UartContext* uart, ViewDispatcher* view_dispatcher, AppState* state, @@ -821,44 +551,42 @@ void uart_receive_data( const char* extension, const char* TargetFolder) { - if(!uart || !uart->storageContext || !view_dispatcher || !state || !prefix || !extension || !TargetFolder) { - FURI_LOG_E("UART", "Invalid parameters in uart_receive_data"); - return; + if(!uart || !uart->storageContext || !view_dispatcher || !state) { + FURI_LOG_E("UART", "Invalid parameters to uart_receive_data"); + return false; } - // Safely disable PCAP mode if active - if(uart->pcap) { - uart->pcap = false; - furi_delay_ms(10); // Allow buffers to flush - if(uart->pcap_stream) { - furi_stream_buffer_reset(uart->pcap_stream); - } + // Close any existing file + if(uart->storageContext->HasOpenedFile) { + storage_file_sync(uart->storageContext->current_file); + storage_file_close(uart->storageContext->current_file); + uart->storageContext->HasOpenedFile = false; } - + + uart->pcap = false; // Reset capture state + furi_stream_buffer_reset(uart->pcap_stream); + + // Clear display text_box_set_text(state->text_box, ""); text_box_set_focus(state->text_box, TextBoxFocusEnd); - if(strlen(prefix) > 1) { - // Close any existing file first - if(uart->storageContext->HasOpenedFile && uart->storageContext->current_file) { - storage_file_close(uart->storageContext->current_file); - uart->storageContext->HasOpenedFile = false; - } - - // Open new file with proper error handling + // Open new file if needed + if(prefix && extension && TargetFolder && strlen(prefix) > 1) { uart->storageContext->HasOpenedFile = sequential_file_open( uart->storageContext->storage_api, uart->storageContext->current_file, TargetFolder, prefix, extension); - + if(!uart->storageContext->HasOpenedFile) { - FURI_LOG_E("UART", "Failed to open file for writing"); - return; + FURI_LOG_E("UART", "Failed to open file: Prefix=%s, Ext=%s, Folder=%s", prefix, extension, TargetFolder); + return false; } } view_dispatcher_switch_to_view(view_dispatcher, 5); state->current_view = 5; -} \ No newline at end of file + + return true; // Indicate success +} diff --git a/uart_utils.h b/uart_utils.h index 7bd814c9566..68064d08357 100644 --- a/uart_utils.h +++ b/uart_utils.h @@ -10,8 +10,9 @@ #include #include "menu.h" #include "uart_storage.h" +#include #define TEXT_BOX_STORE_SIZE (4096) // 4KB text box buffer size -#define RX_BUF_SIZE 1024 +#define RX_BUF_SIZE 2048 #define PCAP_BUF_SIZE 4096 #define STORAGE_BUF_SIZE 4096 #define GHOST_ESP_APP_FOLDER "/ext/apps_data/ghost_esp" @@ -20,34 +21,25 @@ #define GHOST_ESP_APP_FOLDER_LOGS "/ext/apps_data/ghost_esp/logs" #define GHOST_ESP_APP_SETTINGS_FILE "/ext/apps_data/ghost_esp/settings.ini" #define ESP_CHECK_TIMEOUT_MS 100 -void update_text_box_view(AppState* state); -void handle_uart_rx_data(uint8_t *buf, size_t len, void *context); -// First define base structures +#define VIEW_BUFFER_SIZE (16 * 1024) // 16KB for view +#define RING_BUFFER_SIZE (8 * 1024) // 8KB for incoming data +#define PCAP_GLOBAL_HEADER_SIZE 24 +#define PCAP_PACKET_HEADER_SIZE 16 +#define PCAP_TEMP_BUFFER_SIZE 4096 -typedef struct { - bool in_ap_list; - int ap_count; - int expected_ap_count; -} APListState; -// 2. Then define LineProcessingState as it uses APListState -typedef struct { - // Buffer for incomplete lines - char partial_buffer[RX_BUF_SIZE]; - size_t partial_len; - // State tracking - bool in_escape; - bool in_timestamp; - char last_char; - // AP list state - APListState ap_state; -} LineProcessingState; +void update_text_box_view(AppState* state); +void handle_uart_rx_data(uint8_t *buf, size_t len, void *context); -// 3. Then UartLineState as it uses LineProcessingState typedef struct { - LineProcessingState line_state; - bool initialized; -} UartLineState; + char* ring_buffer; // Ring buffer for incoming data + char* view_buffer; // Buffer for current view + size_t ring_read_index; // Read position in ring buffer + size_t ring_write_index; // Write position in ring buffer + size_t view_buffer_len; // Length of current view content + bool buffer_full; // Ring buffer state + FuriMutex* mutex; // Synchronization mutex +} TextBufferManager; typedef enum { WorkerEvtStop = (1 << 0), @@ -56,34 +48,45 @@ typedef enum { WorkerEvtStorage = (1 << 3), } WorkerEvtFlags; -// Then define the main context structure +typedef struct { + char bssid[18]; + char ssid[33]; + double latitude; + double longitude; + int8_t rssi; + uint8_t channel; + char encryption_type[20]; + int64_t timestamp; +} wardriving_data_t; + typedef struct UartContext { FuriHalSerialHandle* serial_handle; FuriThread* rx_thread; FuriStreamBuffer* rx_stream; FuriStreamBuffer* pcap_stream; - FuriStreamBuffer* storage_stream; bool pcap; - uint8_t mark_test_buf[11]; + uint8_t mark_test_buf[11]; // Fixed size for markers uint8_t mark_test_idx; - uint8_t rx_buf[RX_BUF_SIZE + 1]; + uint8_t rx_buf[RX_BUF_SIZE + 1]; // Add +1 for null termination void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context); void (*handle_rx_pcap_cb)(uint8_t* buf, size_t len, void* context); - void (*handle_storage_cb)(uint8_t* buf, size_t len, void* context); AppState* state; UartStorageContext* storageContext; bool is_serial_active; - bool streams_active; - bool storage_active; - UartLineState* line_state; + TextBufferManager* text_manager; + uint8_t pcap_rx_buf[RX_BUF_SIZE]; // Buffer for PCAP data + size_t pcap_buf_len; // Current PCAP buffer length } UartContext; + + // Function prototypes UartContext* uart_init(AppState* state); void uart_free(UartContext* uart); void uart_stop_thread(UartContext* uart); void uart_send(UartContext* uart, const uint8_t* data, size_t len); -void uart_receive_data( +bool uart_is_marauder_firmware(UartContext* uart); +bool uart_receive_data( UartContext* uart, ViewDispatcher* view_dispatcher, AppState* current_view, @@ -92,5 +95,6 @@ void uart_receive_data( const char* TargetFolder); bool uart_is_esp_connected(UartContext* uart); void uart_storage_reset_logs(UartStorageContext *ctx); +void uart_storage_safe_cleanup(UartStorageContext* ctx); #endif \ No newline at end of file