Explorar el Código

update sudoku and esp flasher

MX hace 2 años
padre
commit
ee0905c5ad

+ 1 - 1
non_catalog_apps/flipperzero-esp-flasher/esp_flasher_app.h

@@ -4,7 +4,7 @@
 extern "C" {
 #endif
 
-#define ESP_FLASHER_APP_VERSION "v1.1"
+#define ESP_FLASHER_APP_VERSION "v1.2"
 
 typedef struct EspFlasherApp EspFlasherApp;
 

+ 12 - 2
non_catalog_apps/flipperzero-esp-flasher/esp_flasher_app_i.h

@@ -33,11 +33,18 @@ typedef enum SelectedFlashOptions {
     SelectedFlashPart,
     SelectedFlashNvs,
     SelectedFlashBootApp0,
-    SelectedFlashApp,
+    SelectedFlashAppA,
+    SelectedFlashAppB,
     SelectedFlashCustom,
     NUM_FLASH_OPTIONS
 } SelectedFlashOptions;
 
+typedef enum {
+    SwitchNotSet,
+    SwitchToFirmwareA,
+    SwitchToFirmwareB,
+} SwitchFirmware;
+
 struct EspFlasherApp {
     Gui* gui;
     ViewDispatcher* view_dispatcher;
@@ -59,13 +66,16 @@ struct EspFlasherApp {
     bool reset;
     bool boot;
 
+    SwitchFirmware switch_fw;
+
     bool selected_flash_options[NUM_FLASH_OPTIONS];
     int num_selected_flash_options;
     char bin_file_path_boot[100];
     char bin_file_path_part[100];
     char bin_file_path_nvs[100];
     char bin_file_path_boot_app0[100];
-    char bin_file_path_app[100];
+    char bin_file_path_app_a[100];
+    char bin_file_path_app_b[100];
     char bin_file_path_custom[100];
     FuriThread* flash_worker;
     bool flash_worker_busy;

+ 93 - 7
non_catalog_apps/flipperzero-esp-flasher/esp_flasher_worker.c

@@ -74,6 +74,85 @@ static esp_loader_error_t _flash_file(EspFlasherApp* app, char* filepath, uint32
     return ESP_LOADER_SUCCESS;
 }
 
+// This in-app FW switch "exploits" the otadata (boot_app0)
+// - the first four bytes of each array are the counter and the last four bytes are just a CRC of that counter
+// - the bootloader will just boot whichever app has the highest counter in the otadata partition
+//   so we'll just pick 1 for A, and then B will use either 0 or 2 depending on whether it's the slot in use
+
+#define MAGIC_PAYLOAD_SIZE (32)
+
+const uint8_t magic_payload_app_a[MAGIC_PAYLOAD_SIZE] = {0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
+                                                         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                                                         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                                                         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                                                         0x9a, 0x98, 0x43, 0x47};
+
+const uint8_t magic_payload_app_b_unset[MAGIC_PAYLOAD_SIZE] = {
+    0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+const uint8_t magic_payload_app_b_set[MAGIC_PAYLOAD_SIZE] = {
+    0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x74, 0x37, 0xf6, 0x55};
+
+// return true if "switching" fw selected instead of flashing new fw
+// (this does not indicate success)
+static bool _switch_fw(EspFlasherApp* app) {
+    if(app->switch_fw == SwitchNotSet) {
+        return false;
+    }
+
+    esp_loader_error_t err;
+    char user_msg[256];
+
+    loader_port_debug_print("Preparing to set flags for firmware A\n");
+    err = esp_loader_flash_start(
+        ESP_ADDR_BOOT_APP0 + ESP_ADDR_OTADATA_OFFSET_APP_A,
+        MAGIC_PAYLOAD_SIZE,
+        MAGIC_PAYLOAD_SIZE);
+    if(err != ESP_LOADER_SUCCESS) {
+        snprintf(user_msg, sizeof(user_msg), "Erasing flash failed with error %d\n", err);
+        loader_port_debug_print(user_msg);
+        return true;
+    }
+
+    loader_port_debug_print("Setting flags for firmware A\n");
+    const uint8_t* which_payload_app_a = magic_payload_app_a;
+    err = esp_loader_flash_write((void*)which_payload_app_a, MAGIC_PAYLOAD_SIZE);
+    if(err != ESP_LOADER_SUCCESS) {
+        snprintf(user_msg, sizeof(user_msg), "Packet could not be written! Error: %u\n", err);
+        loader_port_debug_print(user_msg);
+        return true;
+    }
+
+    loader_port_debug_print("Preparing to set flags for firmware B\n");
+    err = esp_loader_flash_start(
+        ESP_ADDR_BOOT_APP0 + ESP_ADDR_OTADATA_OFFSET_APP_B,
+        MAGIC_PAYLOAD_SIZE,
+        MAGIC_PAYLOAD_SIZE);
+    if(err != ESP_LOADER_SUCCESS) {
+        snprintf(user_msg, sizeof(user_msg), "Erasing flash failed with error %d\n", err);
+        loader_port_debug_print(user_msg);
+        return true;
+    }
+
+    loader_port_debug_print("Setting flags for firmware B\n");
+    const uint8_t* which_payload_app_b =
+        (app->switch_fw == SwitchToFirmwareB ? magic_payload_app_b_set :
+                                               magic_payload_app_b_unset);
+    err = esp_loader_flash_write((void*)which_payload_app_b, MAGIC_PAYLOAD_SIZE);
+    if(err != ESP_LOADER_SUCCESS) {
+        snprintf(user_msg, sizeof(user_msg), "Packet could not be written! Error: %u\n", err);
+        loader_port_debug_print(user_msg);
+        return true;
+    }
+
+    loader_port_debug_print("Finished programming\n");
+    return true;
+}
+
 typedef struct {
     SelectedFlashOptions selected;
     const char* description;
@@ -85,7 +164,7 @@ static void _flash_all_files(EspFlasherApp* app) {
     esp_loader_error_t err;
     const int num_steps = app->num_selected_flash_options;
 
-#define NUM_FLASH_ITEMS 6
+#define NUM_FLASH_ITEMS 7
     FlashItem items[NUM_FLASH_ITEMS] = {
         {SelectedFlashBoot,
          "bootloader",
@@ -94,7 +173,8 @@ static void _flash_all_files(EspFlasherApp* app) {
         {SelectedFlashPart, "partition table", app->bin_file_path_part, ESP_ADDR_PART},
         {SelectedFlashNvs, "NVS", app->bin_file_path_nvs, ESP_ADDR_NVS},
         {SelectedFlashBootApp0, "boot_app0", app->bin_file_path_boot_app0, ESP_ADDR_BOOT_APP0},
-        {SelectedFlashApp, "firmware", app->bin_file_path_app, ESP_ADDR_APP},
+        {SelectedFlashAppA, "firmware A", app->bin_file_path_app_a, ESP_ADDR_APP_A},
+        {SelectedFlashAppB, "firmware B", app->bin_file_path_app_b, ESP_ADDR_APP_B},
         {SelectedFlashCustom, "custom data", app->bin_file_path_custom, 0x0},
         /* if you add more entries, update NUM_FLASH_ITEMS above! */
     };
@@ -167,12 +247,16 @@ static int32_t esp_flasher_flash_bin(void* context) {
 
     if(!err) {
         loader_port_debug_print("Connected\n");
-        _flash_all_files(app);
+        if(!_switch_fw(app)) {
+            _flash_all_files(app);
+        }
+        app->switch_fw = SwitchNotSet;
 #if 0
         loader_port_debug_print("Restoring transmission rate\n");
         furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200);
 #endif
-        loader_port_debug_print("Done flashing. Please reset the board manually if it doesn't auto-reset.\n");
+        loader_port_debug_print(
+            "Done flashing. Please reset the board manually if it doesn't auto-reset.\n");
 
         // auto-reset for supported boards
         loader_port_reset_target();
@@ -222,10 +306,10 @@ static int32_t esp_flasher_reset(void* context) {
     _setRTS(false);
     _initRTS();
 
-    if (app->reset) {
+    if(app->reset) {
         loader_port_debug_print("Resetting board\n");
         loader_port_reset_target();
-    } else if (app->boot) {
+    } else if(app->boot) {
         loader_port_debug_print("Entering bootloader\n");
         loader_port_enter_bootloader();
     }
@@ -245,7 +329,7 @@ void esp_flasher_worker_start_thread(EspFlasherApp* app) {
     furi_thread_set_name(app->flash_worker, "EspFlasherFlashWorker");
     furi_thread_set_stack_size(app->flash_worker, 2048);
     furi_thread_set_context(app->flash_worker, app);
-    if (app->reset || app->boot) {
+    if(app->reset || app->boot) {
         furi_thread_set_callback(app->flash_worker, esp_flasher_reset);
     } else {
         furi_thread_set_callback(app->flash_worker, esp_flasher_flash_bin);
@@ -280,6 +364,8 @@ void loader_port_reset_target(void) {
 }
 
 void loader_port_enter_bootloader(void) {
+    // adapted from custom usb-jtag-serial reset in esptool
+    // (works on official wifi dev board)
     _setDTR(true);
     loader_port_delay_ms(SERIAL_FLASHER_RESET_HOLD_TIME_MS);
     _setRTS(true);

+ 5 - 1
non_catalog_apps/flipperzero-esp-flasher/esp_flasher_worker.h

@@ -14,7 +14,11 @@
 #define ESP_ADDR_PART 0x8000
 #define ESP_ADDR_NVS 0x9000
 #define ESP_ADDR_BOOT_APP0 0xE000
-#define ESP_ADDR_APP 0x10000
+#define ESP_ADDR_APP_A 0x10000
+#define ESP_ADDR_APP_B 0x150000
+
+#define ESP_ADDR_OTADATA_OFFSET_APP_A 0x0
+#define ESP_ADDR_OTADATA_OFFSET_APP_B 0x1000
 
 void esp_flasher_worker_start_thread(EspFlasherApp* app);
 void esp_flasher_worker_stop_thread(EspFlasherApp* app);

+ 39 - 13
non_catalog_apps/flipperzero-esp-flasher/scenes/esp_flasher_scene_browse.c

@@ -7,7 +7,8 @@ enum SubmenuIndex {
     SubmenuIndexPart,
     SubmenuIndexNvs,
     SubmenuIndexBootApp0,
-    SubmenuIndexApp,
+    SubmenuIndexAppA,
+    SubmenuIndexAppB,
     SubmenuIndexCustom,
     SubmenuIndexFlash,
 };
@@ -97,19 +98,35 @@ static void esp_flasher_scene_browse_callback(void* context, uint32_t index) {
         }
         view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu);
         break;
-    case SubmenuIndexApp:
-        app->selected_flash_options[SelectedFlashApp] =
-            !app->selected_flash_options[SelectedFlashApp];
+    case SubmenuIndexAppA:
+        app->selected_flash_options[SelectedFlashAppA] =
+            !app->selected_flash_options[SelectedFlashAppA];
         if(dialog_file_browser_show(
                app->dialogs, selected_filepath, predefined_filepath, &browser_options)) {
             strncpy(
-                app->bin_file_path_app,
+                app->bin_file_path_app_a,
                 furi_string_get_cstr(selected_filepath),
-                sizeof(app->bin_file_path_app));
+                sizeof(app->bin_file_path_app_a));
         }
-        if(app->bin_file_path_app[0] == '\0') {
+        if(app->bin_file_path_app_a[0] == '\0') {
             // if user didn't select a file, leave unselected
-            app->selected_flash_options[SelectedFlashApp] = false;
+            app->selected_flash_options[SelectedFlashAppA] = false;
+        }
+        view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu);
+        break;
+    case SubmenuIndexAppB:
+        app->selected_flash_options[SelectedFlashAppB] =
+            !app->selected_flash_options[SelectedFlashAppB];
+        if(dialog_file_browser_show(
+               app->dialogs, selected_filepath, predefined_filepath, &browser_options)) {
+            strncpy(
+                app->bin_file_path_app_b,
+                furi_string_get_cstr(selected_filepath),
+                sizeof(app->bin_file_path_app_b));
+        }
+        if(app->bin_file_path_app_b[0] == '\0') {
+            // if user didn't select a file, leave unselected
+            app->selected_flash_options[SelectedFlashAppB] = false;
         }
         view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu);
         break;
@@ -157,7 +174,8 @@ static void esp_flasher_scene_browse_callback(void* context, uint32_t index) {
 #define STR_PART "Part Table (" TOSTRING(ESP_ADDR_PART) ")"
 #define STR_NVS "NVS (" TOSTRING(ESP_ADDR_NVS) ")"
 #define STR_BOOT_APP0 "boot_app0 (" TOSTRING(ESP_ADDR_BOOT_APP0) ")"
-#define STR_APP "Firmware (" TOSTRING(ESP_ADDR_APP) ")"
+#define STR_APP_A "FirmwareA(" TOSTRING(ESP_ADDR_APP_A) ")"
+#define STR_APP_B "FirmwareB(" TOSTRING(ESP_ADDR_APP_B) ")"
 #define STR_CUSTOM "Custom"
 #define STR_FLASH_S3 "[>] FLASH (ESP32-S3)"
 #define STR_FLASH "[>] FLASH"
@@ -213,9 +231,16 @@ static void _refresh_submenu(EspFlasherApp* app) {
         app);
     submenu_add_item(
         submenu,
-        app->selected_flash_options[SelectedFlashApp] ? STR_SELECT " " STR_APP :
-                                                        STR_UNSELECT " " STR_APP,
-        SubmenuIndexApp,
+        app->selected_flash_options[SelectedFlashAppA] ? STR_SELECT " " STR_APP_A :
+                                                         STR_UNSELECT " " STR_APP_A,
+        SubmenuIndexAppA,
+        esp_flasher_scene_browse_callback,
+        app);
+    submenu_add_item(
+        submenu,
+        app->selected_flash_options[SelectedFlashAppB] ? STR_SELECT " " STR_APP_B :
+                                                         STR_UNSELECT " " STR_APP_B,
+        SubmenuIndexAppB,
         esp_flasher_scene_browse_callback,
         app);
     // TODO: custom addr
@@ -241,7 +266,8 @@ void esp_flasher_scene_browse_on_enter(void* context) {
     app->bin_file_path_part[0] = '\0';
     app->bin_file_path_nvs[0] = '\0';
     app->bin_file_path_boot_app0[0] = '\0';
-    app->bin_file_path_app[0] = '\0';
+    app->bin_file_path_app_a[0] = '\0';
+    app->bin_file_path_app_b[0] = '\0';
     app->bin_file_path_custom[0] = '\0';
 
     _refresh_submenu(app);

+ 22 - 0
non_catalog_apps/flipperzero-esp-flasher/scenes/esp_flasher_scene_start.c

@@ -2,6 +2,8 @@
 
 enum SubmenuIndex {
     SubmenuIndexEspFlasherFlash,
+    SubmenuIndexEspFlasherSwitchA,
+    SubmenuIndexEspFlasherSwitchB,
     SubmenuIndexEspFlasherAbout,
     SubmenuIndexEspFlasherReset,
     SubmenuIndexEspFlasherBootloader,
@@ -25,6 +27,18 @@ void esp_flasher_scene_start_on_enter(void* context) {
         SubmenuIndexEspFlasherFlash,
         esp_flasher_scene_start_submenu_callback,
         app);
+    submenu_add_item(
+        submenu,
+        "Switch to Firmware A",
+        SubmenuIndexEspFlasherSwitchA,
+        esp_flasher_scene_start_submenu_callback,
+        app);
+    submenu_add_item(
+        submenu,
+        "Switch to Firmware B",
+        SubmenuIndexEspFlasherSwitchB,
+        esp_flasher_scene_start_submenu_callback,
+        app);
     submenu_add_item(
         submenu,
         "Reset Board",
@@ -62,6 +76,14 @@ bool esp_flasher_scene_start_on_event(void* context, SceneManagerEvent event) {
         } else if(event.event == SubmenuIndexEspFlasherFlash) {
             scene_manager_next_scene(app->scene_manager, EspFlasherSceneBrowse);
             consumed = true;
+        } else if(event.event == SubmenuIndexEspFlasherSwitchA) {
+            app->switch_fw = SwitchToFirmwareA;
+            scene_manager_next_scene(app->scene_manager, EspFlasherSceneConsoleOutput);
+            consumed = true;
+        } else if(event.event == SubmenuIndexEspFlasherSwitchB) {
+            app->switch_fw = SwitchToFirmwareB;
+            scene_manager_next_scene(app->scene_manager, EspFlasherSceneConsoleOutput);
+            consumed = true;
         } else if(event.event == SubmenuIndexEspFlasherReset) {
             app->reset = true;
             scene_manager_next_scene(app->scene_manager, EspFlasherSceneConsoleOutput);

+ 61 - 10
non_catalog_apps/sudoku/sudoku.c

@@ -1,10 +1,11 @@
 #include <stdio.h>
 #include <furi.h>
 #include <furi_hal.h>
+#include <dolphin/dolphin.h>
 #include <gui/gui.h>
 #include <input/input.h>
 #include <notification/notification_messages.h>
-#include <dolphin/dolphin.h>
+#include <storage/storage.h>
 
 #define TAG "sudoku"
 
@@ -25,7 +26,8 @@ static_assert(USER_INPUT_FLAG > VALUE_MASK);
 typedef enum {
     GameStateRunning,
     GameStatePaused,
-    GameStateWin,
+    GameStateVictory,
+    GameStateRestart, // util state
 } GameState;
 
 typedef struct {
@@ -46,7 +48,7 @@ const char* MENU_ITEMS[] = {
     "Easy game",
     "Nornal game",
     "Hard game",
-    "Exit",
+    "Save+Exit",
 };
 
 /*
@@ -80,6 +82,47 @@ const uint8_t u8g2_font_tom_thumb_4x6_tr[725] =
     "y\11\227\307$\225dJ\0z\7\223\310\254\221\6{\10\227\310\251\32D\1|\6\265\310(\1}\11"
     "\227\310\310\14RR\0~\6\213\313\215\4\0\0\0\4\377\377\0";
 
+#define SAVE_VERSION 1
+#define SAVE_FILE APP_DATA_PATH("save.dat")
+
+bool load_game(SudokuState* state) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    File* file = storage_file_alloc(storage);
+    bool res = false;
+
+    if(storage_file_open(file, SAVE_FILE, FSAM_READ, FSOM_OPEN_EXISTING)) {
+        uint16_t version = 0;
+        uint64_t expectedSize = sizeof(version) + sizeof(SudokuState);
+        uint64_t fileSize = storage_file_size(file);
+        if(fileSize >= expectedSize) {
+            storage_file_read(file, &version, sizeof(version));
+            if(version != SAVE_VERSION) {
+                storage_simply_remove(storage, SAVE_FILE);
+            } else {
+                res = storage_file_read(file, state, sizeof(SudokuState)) == sizeof(SudokuState);
+            }
+        }
+    }
+
+    storage_file_free(file); // Closes the file if it was open.
+    furi_record_close(RECORD_STORAGE);
+    return res;
+}
+
+void save_game(SudokuState* app) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    File* file = storage_file_alloc(storage);
+
+    if(storage_file_open(file, SAVE_FILE, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+        uint16_t version = SAVE_VERSION;
+        storage_file_write(file, &version, sizeof(version));
+        storage_file_write(file, app, sizeof(SudokuState));
+    }
+
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+}
+
 // inspired by game_2048
 static void gray_canvas(Canvas* const canvas) {
     canvas_set_color(canvas, ColorWhite);
@@ -184,7 +227,7 @@ static void draw_callback(Canvas* canvas, void* ctx) {
             i * FONT_SIZE * 3 + gapY + yOffset);
     }
 
-    if(state->state == GameStateWin || state->state == GameStatePaused) {
+    if(state->state == GameStateVictory || state->state == GameStatePaused) {
         gray_canvas(canvas);
         canvas_set_color(canvas, ColorWhite);
         int w = canvas_width(canvas);
@@ -214,7 +257,7 @@ static void draw_callback(Canvas* canvas, void* ctx) {
                 winY + offY + itemH * i + itemH / 2,
                 AlignCenter,
                 AlignCenter,
-                i == 0 && state->state == GameStateWin ? "VICTORY!" : MENU_ITEMS[i]);
+                i == 0 && state->state == GameStateVictory ? "VICTORY!" : MENU_ITEMS[i]);
         }
     }
     furi_mutex_release(state->mutex);
@@ -372,11 +415,13 @@ int32_t sudoku_main(void* p) {
     FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
 
     SudokuState* state = malloc(sizeof(SudokuState));
+    if(!load_game(state) || state->state == GameStateRestart) {
+        state->state = GameStateRunning;
+        state->menuCursor = 0;
+        start_game(state, NORMAL_GAPS);
+    }
     state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
     furi_check(state->mutex, "mutex alloc failed");
-    state->state = GameStateRunning;
-    state->menuCursor = 0;
-    start_game(state, NORMAL_GAPS);
     ViewPort* view_port = view_port_alloc();
     view_port_draw_callback_set(view_port, draw_callback, state);
     view_port_input_callback_set(view_port, input_callback, event_queue);
@@ -392,7 +437,7 @@ int32_t sudoku_main(void* p) {
 
         furi_mutex_acquire(state->mutex, FuriWaitForever);
 
-        if(state->state == GameStatePaused || state->state == GameStateWin) {
+        if(state->state == GameStatePaused || state->state == GameStateVictory) {
             bool exit = false;
             if(event.type == InputTypePress || event.type == InputTypeLong ||
                event.type == InputTypeRepeat) {
@@ -470,7 +515,7 @@ int32_t sudoku_main(void* p) {
             }
             if(invalidField && validate_board(state)) {
                 dolphin_deed(DolphinDeedPluginGameWin);
-                state->state = GameStateWin;
+                state->state = GameStateVictory;
                 state->menuCursor = 0;
                 for(int i = 0; i != BOARD_SIZE; ++i) {
                     for(int j = 0; j != BOARD_SIZE; ++j) {
@@ -491,6 +536,12 @@ int32_t sudoku_main(void* p) {
     furi_record_close(RECORD_GUI);
 
     furi_mutex_free(state->mutex);
+    if(state->state == GameStateVictory) {
+        state->state = GameStateRestart;
+    }
+    state->menuCursor = 0; // reset menu cursor, because we stand on exit
+    state->mutex = NULL;
+    save_game(state);
     free(state);
 
     return 0;