jblanked 1 год назад
Родитель
Сommit
f0e265d054

+ 0 - 2
.gitignore

@@ -1,2 +0,0 @@
-
-.DS_Store

+ 27 - 26
app.c

@@ -8,46 +8,47 @@ int32_t main_flip_store(void *p)
     UNUSED(p);
 
     // Initialize the Hello World application
-    app_instance = flip_store_app_alloc();
-    if (!app_instance)
+    FlipStoreApp *app = flip_store_app_alloc();
+    if (!app)
     {
         FURI_LOG_E(TAG, "Failed to allocate FlipStoreApp");
         return -1;
     }
 
     // check if board is connected (Derek Jamison)
-    // initialize the http
-    if (flipper_http_init(flipper_http_rx_callback, app_instance))
+    FlipperHTTP *fhttp = flipper_http_alloc();
+    if (!fhttp)
     {
-        if (!flipper_http_ping())
-        {
-            FURI_LOG_E(TAG, "Failed to ping the device");
-            return -1;
-        }
-
-        // Try to wait for pong response.
-        uint8_t counter = 10;
-        while (fhttp.state == INACTIVE && --counter > 0)
-        {
-            FURI_LOG_D(TAG, "Waiting for PONG");
-            furi_delay_ms(100);
-        }
-
-        if (counter == 0)
-            easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
-
-        flipper_http_deinit();
+        easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
+        return -1;
     }
-    else
+
+    if (!flipper_http_ping(fhttp))
     {
-        easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
+        FURI_LOG_E(TAG, "Failed to ping the device");
+        flipper_http_free(fhttp);
+        return -1;
+    }
+
+    // Try to wait for pong response.
+    uint32_t counter = 10;
+    while (fhttp->state == INACTIVE && --counter > 0)
+    {
+        FURI_LOG_D(TAG, "Waiting for PONG");
+        furi_delay_ms(100);
+    }
+
+    flipper_http_free(fhttp);
+    if (counter == 0)
+    {
+        easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
     }
 
     // Run the view dispatcher
-    view_dispatcher_run(app_instance->view_dispatcher);
+    view_dispatcher_run(app->view_dispatcher);
 
     // Free the resources used by the Hello World application
-    flip_store_app_free(app_instance);
+    flip_store_app_free(app);
 
     // Return 0 to indicate success
     return 0;

+ 1 - 1
application.fam

@@ -10,5 +10,5 @@ App(
     fap_description="Download apps via WiFi directly to your Flipper Zero",
     fap_author="JBlanked",
     fap_weburl="https://github.com/jblanked/FlipStore",
-    fap_version="0.7.2",
+    fap_version="0.8",
 )

+ 26 - 19
apps/flip_store_apps.c

@@ -44,8 +44,13 @@ void flip_catalog_free()
     }
 }
 
-bool flip_store_process_app_list()
+bool flip_store_process_app_list(FlipperHTTP *fhttp)
 {
+    if (!fhttp)
+    {
+        FURI_LOG_E(TAG, "FlipperHTTP is NULL.");
+        return false;
+    }
     // Initialize the flip_catalog
     flip_catalog = flip_catalog_alloc();
     if (!flip_catalog)
@@ -54,16 +59,13 @@ bool flip_store_process_app_list()
         return false;
     }
 
-    FuriString *feed_data = flipper_http_load_from_file(fhttp.file_path);
+    FuriString *feed_data = flipper_http_load_from_file(fhttp->file_path);
     if (feed_data == NULL)
     {
         FURI_LOG_E(TAG, "Failed to load received data from file.");
         return false;
     }
 
-    // free the resources
-    flipper_http_deinit();
-
     char *data_cstr = (char *)furi_string_get_cstr(feed_data);
     if (data_cstr == NULL)
     {
@@ -259,29 +261,33 @@ bool flip_store_process_app_list()
     return app_count > 0;
 }
 
-bool flip_store_get_fap_file(char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor)
+bool flip_store_get_fap_file(FlipperHTTP *fhttp, char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor)
 {
-    if (!app_instance)
+    if (!fhttp)
     {
-        FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+        FURI_LOG_E(TAG, "FlipperHTTP is NULL.");
         return false;
     }
-    // initialize the http
-    if (!flipper_http_init(flipper_http_rx_callback, app_instance))
+    if (!build_id)
     {
-        FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP.");
+        FURI_LOG_E(TAG, "Build ID is NULL.");
         return false;
     }
-    fhttp.state = IDLE;
+    fhttp->state = IDLE;
     char url[128];
-    fhttp.save_received_data = false;
-    fhttp.is_bytes_request = true;
+    fhttp->save_received_data = false;
+    fhttp->is_bytes_request = true;
     snprintf(url, sizeof(url), "https://catalog.flipperzero.one/api/v0/application/version/%s/build/compatible?target=f%d&api=%d.%d", build_id, target, api_major, api_minor);
-    return flipper_http_get_request_bytes(url, "{\"Content-Type\": \"application/octet-stream\"}");
+    return flipper_http_get_request_bytes(fhttp, url, "{\"Content-Type\": \"application/octet-stream\"}");
 }
 
-bool flip_store_install_app(char *category)
+bool flip_store_install_app(FlipperHTTP *fhttp, char *category)
 {
+    if (!fhttp)
+    {
+        FURI_LOG_E(TAG, "FlipperHTTP is NULL.");
+        return false;
+    }
     // create /apps/FlipStore directory if it doesn't exist
     char directory_path[128];
     snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps/%s", category);
@@ -289,15 +295,16 @@ bool flip_store_install_app(char *category)
     // Create the directory
     Storage *storage = furi_record_open(RECORD_STORAGE);
     storage_common_mkdir(storage, directory_path);
+    furi_record_close(RECORD_STORAGE);
 
-    snprintf(fhttp.file_path, sizeof(fhttp.file_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap", category, flip_catalog[app_selected_index].app_id);
+    snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap", category, flip_catalog[app_selected_index].app_id);
 
     uint8_t target = furi_hal_version_get_hw_target();
     uint16_t api_major, api_minor;
     furi_hal_info_get_api_version(&api_major, &api_minor);
-    if (fhttp.state != INACTIVE && flip_store_get_fap_file(flip_catalog[app_selected_index].app_build_id, target, api_major, api_minor))
+    if (fhttp->state != INACTIVE && flip_store_get_fap_file(fhttp, flip_catalog[app_selected_index].app_build_id, target, api_major, api_minor))
     {
-        fhttp.state = RECEIVING;
+        fhttp->state = RECEIVING;
         return true;
     }
     else

+ 3 - 3
apps/flip_store_apps.h

@@ -46,10 +46,10 @@ FlipStoreAppInfo *flip_catalog_alloc();
 void flip_catalog_free();
 
 // Utility function to parse JSON incrementally from a file
-bool flip_store_process_app_list();
+bool flip_store_process_app_list(FlipperHTTP *fhttp);
 
-bool flip_store_get_fap_file(char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor);
+bool flip_store_get_fap_file(FlipperHTTP *fhttp, char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor);
 
 // function to handle the entire installation process "asynchronously"
-bool flip_store_install_app(char *category);
+bool flip_store_install_app(FlipperHTTP *fhttp, char *category);
 #endif // FLIP_STORE_APPS_H

+ 3 - 0
assets/CHANGELOG.md

@@ -1,3 +1,6 @@
+## v0.8
+- Updated FlipperHTTP to the latest library.
+
 ## v0.7.2
 - Final memory allocation improvements
 

+ 186 - 172
callback/flip_store_callback.c

@@ -16,18 +16,20 @@ static uint32_t callback_to_app_category_list(void *context);
 
 static bool flip_store_dl_app_fetch(DataLoaderModel *model)
 {
-    UNUSED(model);
-    return flip_store_install_app(categories[flip_store_category_index]);
+    if (!model->fhttp)
+    {
+        FURI_LOG_E(TAG, "FlipperHTTP is NULL");
+        return false;
+    }
+    return flip_store_install_app(model->fhttp, categories[flip_store_category_index]);
 }
 static char *flip_store_dl_app_parse(DataLoaderModel *model)
 {
-    UNUSED(model);
-    if (fhttp.state != IDLE)
+    if (!model->fhttp || model->fhttp->state != IDLE)
     {
-        return NULL;
+        FURI_LOG_E(TAG, "FlipperHTTP is NULL or not IDLE");
+        return "Failed to install app.";
     }
-    // free the resources
-    flipper_http_deinit();
     return "App installed successfully.";
 }
 static void flip_store_dl_app_switch_to_view(FlipStoreApp *app)
@@ -37,42 +39,36 @@ static void flip_store_dl_app_switch_to_view(FlipStoreApp *app)
 //
 static bool flip_store_fetch_app_list(DataLoaderModel *model)
 {
-    UNUSED(model);
-    if (!app_instance)
+    if (!model->fhttp)
     {
-        FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+        FURI_LOG_E(TAG, "FlipperHTTP is NULL");
         return false;
     }
-    // initialize the http
-    if (!flipper_http_init(flipper_http_rx_callback, app_instance))
-    {
-        FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP.");
-        return false;
-    }
-    fhttp.state = IDLE;
+    model->fhttp->state = IDLE;
     flip_catalog_free();
     snprintf(
-        fhttp.file_path,
-        sizeof(fhttp.file_path),
+        model->fhttp->file_path,
+        sizeof(model->fhttp->file_path),
         STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s.json", categories[flip_store_category_index]);
-    fhttp.save_received_data = true;
-    fhttp.is_bytes_request = false;
+    model->fhttp->save_received_data = true;
+    model->fhttp->is_bytes_request = false;
     char url[128];
     snprintf(url, sizeof(url), "https://www.flipsocial.net/api/flipper/apps/%s/max/", categories[flip_store_category_index]);
-    return flipper_http_get_request_with_headers(url, "{\"Content-Type\":\"application/json\"}");
+    return flipper_http_get_request_with_headers(model->fhttp, url, "{\"Content-Type\":\"application/json\"}");
 }
-static bool set_appropriate_list(Submenu **submenu)
+static bool set_appropriate_list(FlipperHTTP *fhttp, Submenu **submenu, FlipStoreApp *app)
 {
-    if (!submenu)
+    if (!submenu || !fhttp || !app)
     {
-        FURI_LOG_E(TAG, "Submenu is NULL");
+        FURI_LOG_E(TAG, "Submenu, FlipperHTTP, or app is NULL");
         return false;
     }
-    if (!easy_flipper_set_submenu(submenu, FlipStoreViewAppListCategory, categories[flip_store_category_index], callback_to_app_list, &app_instance->view_dispatcher))
+
+    if (!easy_flipper_set_submenu(submenu, FlipStoreViewAppListCategory, categories[flip_store_category_index], callback_to_app_list, &app->view_dispatcher))
     {
         return false;
     }
-    if (flip_store_process_app_list() && submenu && flip_catalog)
+    if (flip_store_process_app_list(fhttp) && submenu && flip_catalog)
     {
         submenu_reset(*submenu);
         // add each app name to submenu
@@ -80,14 +76,14 @@ static bool set_appropriate_list(Submenu **submenu)
         {
             if (strlen(flip_catalog[i].app_name) > 0)
             {
-                submenu_add_item(*submenu, flip_catalog[i].app_name, FlipStoreSubmenuIndexStartAppList + i, callback_submenu_choices, app_instance);
+                submenu_add_item(*submenu, flip_catalog[i].app_name, FlipStoreSubmenuIndexStartAppList + i, callback_submenu_choices, app);
             }
             else
             {
                 break;
             }
         }
-        view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipStoreViewAppListCategory);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppListCategory);
         return true;
     }
     else
@@ -99,13 +95,12 @@ static bool set_appropriate_list(Submenu **submenu)
 }
 static char *flip_store_parse_app_list(DataLoaderModel *model)
 {
-    UNUSED(model);
-    if (!app_instance)
+    if (!model->fhttp)
     {
-        FURI_LOG_E(TAG, "FlipStoreApp is NULL");
         return "Failed to fetch app list.";
     }
-    return set_appropriate_list(&app_instance->submenu_app_list_category) ? "App list fetched successfully." : "Failed to fetch app list.";
+    FlipStoreApp *app = (FlipStoreApp *)model->parser_context;
+    return set_appropriate_list(model->fhttp, &app->submenu_app_list_category, app) ? "App list fetched successfully." : "Failed to fetch app list.";
 }
 static void flip_store_switch_to_app_list(FlipStoreApp *app)
 {
@@ -114,18 +109,12 @@ static void flip_store_switch_to_app_list(FlipStoreApp *app)
 //
 static bool flip_store_fetch_firmware(DataLoaderModel *model)
 {
-    if (!app_instance)
-    {
-        FURI_LOG_E(TAG, "FlipStoreApp is NULL");
-        return false;
-    }
-    // initialize the http
-    if (!flipper_http_init(flipper_http_rx_callback, app_instance))
+    if (!model->fhttp)
     {
-        FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP.");
+        FURI_LOG_E(TAG, "FlipperHTTP is NULL");
         return false;
     }
-    fhttp.state = IDLE;
+    model->fhttp->state = IDLE;
     if (model->request_index == 0)
     {
         firmware_free();
@@ -135,6 +124,7 @@ static bool flip_store_fetch_firmware(DataLoaderModel *model)
             return false;
         }
         firmware_request_success = flip_store_get_firmware_file(
+            model->fhttp,
             firmwares[selected_firmware_index].links[0],
             firmwares[selected_firmware_index].name,
             strrchr(firmwares[selected_firmware_index].links[0], '/') + 1);
@@ -143,6 +133,7 @@ static bool flip_store_fetch_firmware(DataLoaderModel *model)
     else if (model->request_index == 1)
     {
         firmware_request_success_2 = flip_store_get_firmware_file(
+            model->fhttp,
             firmwares[selected_firmware_index].links[1],
             firmwares[selected_firmware_index].name,
             strrchr(firmwares[selected_firmware_index].links[1], '/') + 1);
@@ -151,6 +142,7 @@ static bool flip_store_fetch_firmware(DataLoaderModel *model)
     else if (model->request_index == 2)
     {
         firmware_request_success_3 = flip_store_get_firmware_file(
+            model->fhttp,
             firmwares[selected_firmware_index].links[2],
             firmwares[selected_firmware_index].name,
             strrchr(firmwares[selected_firmware_index].links[2], '/') + 1);
@@ -160,8 +152,6 @@ static bool flip_store_fetch_firmware(DataLoaderModel *model)
 }
 static char *flip_store_parse_firmware(DataLoaderModel *model)
 {
-    // free the resources
-    flipper_http_deinit();
     if (model->request_index == 0)
     {
         if (firmware_request_success)
@@ -191,7 +181,7 @@ static void flip_store_switch_to_firmware_list(FlipStoreApp *app)
 }
 
 // Function to draw the message on the canvas with word wrapping
-void draw_description(Canvas *canvas, const char *description, int x, int y)
+static void draw_description(Canvas *canvas, const char *description, int x, int y)
 {
     if (description == NULL || strlen(description) == 0)
     {
@@ -251,7 +241,7 @@ void draw_description(Canvas *canvas, const char *description, int x, int y)
     }
 }
 
-void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
+static void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
 {
     UNUSED(model);
     canvas_clear(canvas);
@@ -277,7 +267,7 @@ void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
     canvas_draw_str_aligned(canvas, 97, 54, AlignLeft, AlignTop, "Install");
 }
 
-bool flip_store_input_callback(InputEvent *event, void *context)
+static bool flip_store_input_callback(InputEvent *event, void *context)
 {
     FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
@@ -313,7 +303,7 @@ bool flip_store_input_callback(InputEvent *event, void *context)
     return false;
 }
 
-void flip_store_text_updated_ssid(void *context)
+static void flip_store_text_updated_ssid(void *context)
 {
     FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
@@ -346,16 +336,17 @@ void flip_store_text_updated_ssid(void *context)
                 save_settings(app->uart_text_input_buffer, pass);
 
                 // initialize the http
-                if (flipper_http_init(flipper_http_rx_callback, app))
+                FlipperHTTP *fhttp = flipper_http_alloc();
+                if (fhttp)
                 {
                     // save the wifi if the device is connected
-                    if (!flipper_http_save_wifi(app->uart_text_input_buffer, pass))
+                    if (!flipper_http_save_wifi(fhttp, app->uart_text_input_buffer, pass))
                     {
                         easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
                     }
 
                     // free the resources
-                    flipper_http_deinit();
+                    flipper_http_free(fhttp);
                 }
                 else
                 {
@@ -368,7 +359,7 @@ void flip_store_text_updated_ssid(void *context)
     // switch to the settings view
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
 }
-void flip_store_text_updated_pass(void *context)
+static void flip_store_text_updated_pass(void *context)
 {
     FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
@@ -402,16 +393,17 @@ void flip_store_text_updated_pass(void *context)
             save_settings(ssid, app->uart_text_input_buffer);
 
             // initialize the http
-            if (flipper_http_init(flipper_http_rx_callback, app))
+            FlipperHTTP *fhttp = flipper_http_alloc();
+            if (fhttp)
             {
                 // save the wifi if the device is connected
-                if (!flipper_http_save_wifi(ssid, app->uart_text_input_buffer))
+                if (!flipper_http_save_wifi(fhttp, ssid, app->uart_text_input_buffer))
                 {
                     easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
                 }
 
                 // free the resources
-                flipper_http_deinit();
+                flipper_http_free(fhttp);
             }
             else
             {
@@ -477,39 +469,7 @@ static uint32_t callback_to_wifi_settings(void *context)
     return FlipStoreViewSettings;
 }
 
-void dialog_delete_callback(DialogExResult result, void *context)
-{
-    FlipStoreApp *app = (FlipStoreApp *)context;
-    if (!app)
-    {
-        FURI_LOG_E(TAG, "FlipStoreApp is NULL");
-        return;
-    }
-    if (result == DialogExResultLeft) // No
-    {
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
-    }
-    else if (result == DialogExResultRight)
-    {
-        // delete the app then return to the app list
-        if (!delete_app(flip_catalog[app_selected_index].app_id, categories[flip_store_category_index]))
-        {
-            // pop up a message
-            easy_flipper_dialog("Error", "Issue deleting app.");
-            furi_delay_ms(2000); // delay for 2 seconds
-            view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
-        }
-        else
-        {
-            // pop up a message
-            easy_flipper_dialog("Success", "App deleted successfully.");
-            furi_delay_ms(2000); // delay for 2 seconds
-            view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
-        }
-    }
-}
-
-void dialog_firmware_callback(DialogExResult result, void *context)
+static void dialog_firmware_callback(DialogExResult result, void *context)
 {
     FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
@@ -555,18 +515,13 @@ static bool alloc_about_view(FlipStoreApp *app)
     return true;
 }
 
-static void free_about_view()
+static void free_about_view(FlipStoreApp *app)
 {
-    if (!app_instance)
+    if (app && app->widget_about != NULL)
     {
-        FURI_LOG_E(TAG, "FlipStoreApp is NULL");
-        return;
-    }
-    if (app_instance->widget_about != NULL)
-    {
-        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipStoreViewAbout);
-        widget_free(app_instance->widget_about);
-        app_instance->widget_about = NULL;
+        view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAbout);
+        widget_free(app->widget_about);
+        app->widget_about = NULL;
     }
 }
 static bool alloc_text_input_view(void *context, char *title)
@@ -632,9 +587,8 @@ static bool alloc_text_input_view(void *context, char *title)
     }
     return true;
 }
-static void free_text_input_view(void *context)
+static void free_text_input_view(FlipStoreApp *app)
 {
-    FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
     {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
@@ -657,6 +611,7 @@ static void free_text_input_view(void *context)
         app->uart_text_input_temp_buffer = NULL;
     }
 }
+static void settings_item_selected(void *context, uint32_t index);
 static bool alloc_variable_item_list(void *context)
 {
     FlipStoreApp *app = (FlipStoreApp *)context;
@@ -695,28 +650,28 @@ static bool alloc_variable_item_list(void *context)
     }
     return true;
 }
-static void free_variable_item_list()
+static void free_variable_item_list(FlipStoreApp *app)
 {
-    if (!app_instance)
+    if (!app)
     {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
         return;
     }
-    if (app_instance->variable_item_list)
+    if (app->variable_item_list)
     {
-        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipStoreViewSettings);
-        variable_item_list_free(app_instance->variable_item_list);
-        app_instance->variable_item_list = NULL;
+        view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewSettings);
+        variable_item_list_free(app->variable_item_list);
+        app->variable_item_list = NULL;
     }
-    if (app_instance->variable_item_ssid)
+    if (app->variable_item_ssid)
     {
-        free(app_instance->variable_item_ssid);
-        app_instance->variable_item_ssid = NULL;
+        free(app->variable_item_ssid);
+        app->variable_item_ssid = NULL;
     }
-    if (app_instance->variable_item_pass)
+    if (app->variable_item_pass)
     {
-        free(app_instance->variable_item_pass);
-        app_instance->variable_item_pass = NULL;
+        free(app->variable_item_pass);
+        app->variable_item_pass = NULL;
     }
 }
 static bool alloc_dialog_firmware(void *context)
@@ -751,9 +706,8 @@ static bool alloc_dialog_firmware(void *context)
     }
     return true;
 }
-static void free_dialog_firmware(void *context)
+static void free_dialog_firmware(FlipStoreApp *app)
 {
-    FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
     {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
@@ -766,9 +720,8 @@ static void free_dialog_firmware(void *context)
         app->dialog_firmware = NULL;
     }
 }
-static bool alloc_app_info_view(void *context)
+static bool alloc_app_info_view(FlipStoreApp *app)
 {
-    FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
     {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
@@ -794,9 +747,8 @@ static bool alloc_app_info_view(void *context)
     }
     return true;
 }
-static void free_app_info_view(void *context)
+static void free_app_info_view(FlipStoreApp *app)
 {
-    FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
     {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
@@ -809,28 +761,32 @@ static void free_app_info_view(void *context)
         app->view_app_info = NULL;
     }
 }
-static void free_all_views(bool should_free_variable_item_list)
+static void free_all_views(FlipStoreApp *app, bool should_free_variable_item_list)
 {
-    free_about_view();
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+        return;
+    }
+    free_about_view(app);
     flip_catalog_free();
     if (should_free_variable_item_list)
     {
-        free_variable_item_list();
+        free_variable_item_list(app);
     }
-    if (app_instance->submenu_app_list_category)
+    if (app->submenu_app_list_category)
     {
-        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipStoreViewAppListCategory);
-        submenu_free(app_instance->submenu_app_list_category);
-        app_instance->submenu_app_list_category = NULL;
+        view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListCategory);
+        submenu_free(app->submenu_app_list_category);
+        app->submenu_app_list_category = NULL;
     }
-    free_text_input_view(app_instance);
-    free_dialog_firmware(app_instance);
-    free_app_info_view(app_instance);
+    free_text_input_view(app);
+    free_dialog_firmware(app);
+    free_app_info_view(app);
 }
 uint32_t callback_exit_app(void *context)
 {
     UNUSED(context);
-    free_all_views(true);
     return VIEW_NONE; // Return VIEW_NONE to exit the app
 }
 
@@ -846,7 +802,7 @@ void callback_submenu_choices(void *context, uint32_t index)
     switch (index)
     {
     case FlipStoreSubmenuIndexAbout:
-        free_all_views(true);
+        free_all_views(app, true);
         if (!alloc_about_view(app))
         {
             FURI_LOG_E(TAG, "Failed to set about view");
@@ -855,7 +811,7 @@ void callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAbout);
         break;
     case FlipStoreSubmenuIndexSettings:
-        free_all_views(true);
+        free_all_views(app, true);
         if (!alloc_variable_item_list(app))
         {
             FURI_LOG_E(TAG, "Failed to allocate variable item list");
@@ -864,7 +820,7 @@ void callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
         break;
     case FlipStoreSubmenuIndexOptions:
-        free_all_views(true);
+        free_all_views(app, true);
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenuOptions);
         break;
     case FlipStoreSubmenuIndexAppList:
@@ -893,67 +849,67 @@ void callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwares);
         break;
     case FlipStoreSubmenuIndexAppListBluetooth:
-        free_all_views(true);
+        free_all_views(app, true);
         flip_store_category_index = 0;
         flip_store_app_does_exist = false;
         flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListGames:
-        free_all_views(true);
+        free_all_views(app, true);
         flip_store_category_index = 1;
         flip_store_app_does_exist = false;
         flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListGPIO:
-        free_all_views(true);
+        free_all_views(app, true);
         flip_store_category_index = 2;
         flip_store_app_does_exist = false;
         flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListInfrared:
-        free_all_views(true);
+        free_all_views(app, true);
         flip_store_category_index = 3;
         flip_store_app_does_exist = false;
         flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListiButton:
-        free_all_views(true);
+        free_all_views(app, true);
         flip_store_category_index = 4;
         flip_store_app_does_exist = false;
         flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListMedia:
-        free_all_views(true);
+        free_all_views(app, true);
         flip_store_category_index = 5;
         flip_store_app_does_exist = false;
         flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListNFC:
-        free_all_views(true);
+        free_all_views(app, true);
         flip_store_category_index = 6;
         flip_store_app_does_exist = false;
         flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListRFID:
-        free_all_views(true);
+        free_all_views(app, true);
         flip_store_category_index = 7;
         flip_store_app_does_exist = false;
         flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListSubGHz:
-        free_all_views(true);
+        free_all_views(app, true);
         flip_store_category_index = 8;
         flip_store_app_does_exist = false;
         flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListTools:
-        free_all_views(true);
+        free_all_views(app, true);
         flip_store_category_index = 9;
         flip_store_app_does_exist = false;
         flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListUSB:
-        free_all_views(true);
+        free_all_views(app, true);
         flip_store_category_index = 10;
         flip_store_app_does_exist = false;
         flip_store_switch_to_app_list(app);
@@ -972,7 +928,7 @@ void callback_submenu_choices(void *context, uint32_t index)
                 selected_firmware_index = firmware_index;
 
                 // Switch to the firmware download view
-                free_all_views(true);
+                free_all_views(app, true);
                 if (!alloc_dialog_firmware(app))
                 {
                     FURI_LOG_E(TAG, "Failed to allocate dialog firmware");
@@ -1029,7 +985,7 @@ void callback_submenu_choices(void *context, uint32_t index)
     }
 }
 
-void settings_item_selected(void *context, uint32_t index)
+static void settings_item_selected(void *context, uint32_t index)
 {
     FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
@@ -1043,7 +999,7 @@ void settings_item_selected(void *context, uint32_t index)
     {
     case 0: // Input SSID
         // Text Input
-        free_all_views(false);
+        free_all_views(app, false);
         if (!alloc_text_input_view(app, "SSID"))
         {
             FURI_LOG_E(TAG, "Failed to allocate text input view");
@@ -1058,7 +1014,7 @@ void settings_item_selected(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInput);
         break;
     case 1: // Input Password
-        free_all_views(false);
+        free_all_views(app, false);
         if (!alloc_text_input_view(app, "Password"))
         {
             FURI_LOG_E(TAG, "Failed to allocate text input view");
@@ -1180,6 +1136,50 @@ static void flip_store_widget_set_text(char *message, Widget **widget)
     // Add the formatted message to the widget
     widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
 }
+static void flip_store_request_error(Canvas *canvas, FlipperHTTP *fhttp)
+{
+    if (!canvas)
+    {
+        FURI_LOG_E(TAG, "Canvas is NULL");
+        return;
+    }
+    if (!fhttp)
+    {
+        FURI_LOG_E(TAG, "FlipperHTTP is NULL");
+        return;
+    }
+    if (fhttp->last_response != NULL)
+    {
+        if (strstr(fhttp->last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
+        {
+            canvas_clear(canvas);
+            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
+            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+        }
+        else if (strstr(fhttp->last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
+        {
+            canvas_clear(canvas);
+            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
+            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+        }
+        else
+        {
+            FURI_LOG_E(TAG, "Received an error: %s", fhttp->last_response);
+            canvas_draw_str(canvas, 0, 42, "Unusual error...");
+            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+        }
+    }
+    else
+    {
+        canvas_clear(canvas);
+        canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
+        canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+        canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+    }
+}
 
 void flip_store_loader_draw_callback(Canvas *canvas, void *model)
 {
@@ -1189,8 +1189,8 @@ void flip_store_loader_draw_callback(Canvas *canvas, void *model)
         return;
     }
 
-    SerialState http_state = fhttp.state;
     DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
+    SerialState http_state = data_loader_model->fhttp->state;
     DataState data_state = data_loader_model->data_state;
     char *title = data_loader_model->title;
 
@@ -1198,7 +1198,7 @@ void flip_store_loader_draw_callback(Canvas *canvas, void *model)
 
     if (http_state == INACTIVE)
     {
-        canvas_draw_str(canvas, 0, 7, "WiFi Dev Board disconnected.");
+        canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
         canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
         canvas_draw_str(canvas, 0, 32, "If your board is connected,");
         canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
@@ -1209,7 +1209,7 @@ void flip_store_loader_draw_callback(Canvas *canvas, void *model)
 
     if (data_state == DataStateError || data_state == DataStateParseError)
     {
-        flip_store_request_error(canvas);
+        flip_store_request_error(canvas, data_loader_model->fhttp);
         return;
     }
 
@@ -1259,7 +1259,14 @@ static void flip_store_loader_process_callback(void *context)
     View *view = app->view_loader;
 
     DataState current_data_state;
-    with_view_model(view, DataLoaderModel * model, { current_data_state = model->data_state; }, false);
+    DataLoaderModel *loader_model = NULL;
+    with_view_model(view, DataLoaderModel * model, { current_data_state = model->data_state; loader_model = model; }, false);
+    if (!loader_model || !loader_model->fhttp)
+    {
+        FURI_LOG_E(TAG, "Model or fhttp is NULL");
+        DEV_CRASH();
+        return;
+    }
 
     if (current_data_state == DataStateInitial)
     {
@@ -1277,12 +1284,7 @@ static void flip_store_loader_process_callback(void *context)
                 }
 
                 // Clear any previous responses
-                if (fhttp.last_response != NULL)
-                {
-                    free(fhttp.last_response);
-                    fhttp.last_response = NULL;
-                }
-                // strncpy(fhttp.last_response, "", 1);
+                strncpy(model->fhttp->last_response, "", 1);
                 bool request_status = fetch(model);
                 if (!request_status)
                 {
@@ -1293,21 +1295,21 @@ static void flip_store_loader_process_callback(void *context)
     }
     else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
     {
-        if (fhttp.state == IDLE && fhttp.last_response != NULL)
+        if (loader_model->fhttp->state == IDLE && loader_model->fhttp->last_response != NULL)
         {
-            if (strstr(fhttp.last_response, "[PONG]") != NULL)
+            if (strstr(loader_model->fhttp->last_response, "[PONG]") != NULL)
             {
                 FURI_LOG_DEV(TAG, "PONG received.");
             }
-            else if (strncmp(fhttp.last_response, "[SUCCESS]", 9) == 0)
+            else if (strncmp(loader_model->fhttp->last_response, "[SUCCESS]", 9) == 0)
             {
-                FURI_LOG_DEV(TAG, "SUCCESS received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+                FURI_LOG_DEV(TAG, "SUCCESS received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
             }
-            else if (strncmp(fhttp.last_response, "[ERROR]", 9) == 0)
+            else if (strncmp(loader_model->fhttp->last_response, "[ERROR]", 9) == 0)
             {
-                FURI_LOG_DEV(TAG, "ERROR received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+                FURI_LOG_DEV(TAG, "ERROR received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
             }
-            else if (strlen(fhttp.last_response) == 0)
+            else if (strlen(loader_model->fhttp->last_response) == 0)
             {
                 // Still waiting on response
             }
@@ -1316,21 +1318,21 @@ static void flip_store_loader_process_callback(void *context)
                 with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
             }
         }
-        else if (fhttp.state == SENDING || fhttp.state == RECEIVING)
+        else if (loader_model->fhttp->state == SENDING || loader_model->fhttp->state == RECEIVING)
         {
             // continue waiting
         }
-        else if (fhttp.state == INACTIVE)
+        else if (loader_model->fhttp->state == INACTIVE)
         {
             // inactive. try again
         }
-        else if (fhttp.state == ISSUE)
+        else if (loader_model->fhttp->state == ISSUE)
         {
             with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
         }
         else
         {
-            FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", fhttp.state, fhttp.last_response ? fhttp.last_response : "NULL");
+            FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", loader_model->fhttp->state, loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
             DEV_CRASH();
         }
     }
@@ -1351,7 +1353,7 @@ static void flip_store_loader_process_callback(void *context)
                 {
                     data_text = model->parser(model);
                 }
-                FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", fhttp.last_response ? fhttp.last_response : "NULL", data_text ? data_text : "NULL");
+                FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", model->fhttp->last_response ? model->fhttp->last_response : "NULL", data_text ? data_text : "NULL");
                 model->data_text = data_text;
                 if (data_text == NULL)
                 {
@@ -1376,7 +1378,7 @@ static void flip_store_loader_process_callback(void *context)
                 }
                 else
                 {
-                    flip_store_widget_set_text(model->data_text != NULL ? model->data_text : "Empty result", &app->widget_result);
+                    flip_store_widget_set_text(model->data_text != NULL ? model->data_text : "", &app->widget_result);
                     if (model->data_text != NULL)
                     {
                         free(model->data_text);
@@ -1480,8 +1482,14 @@ void flip_store_loader_free_model(View *view)
             }
             if (model->parser_context)
             {
-                free(model->parser_context);
-                model->parser_context = NULL;
+                // do not free the context here, it is the app context
+                // free(model->parser_context);
+                // model->parser_context = NULL;
+            }
+            if (model->fhttp)
+            {
+                flipper_http_free(model->fhttp);
+                model->fhttp = NULL;
             }
         },
         false);
@@ -1536,6 +1544,12 @@ void flip_store_generic_switch_to_view(FlipStoreApp *app, char *title, DataLoade
             model->back_callback = back;
             model->data_state = DataStateInitial;
             model->data_text = NULL;
+            //
+            model->parser_context = app;
+            if (!model->fhttp)
+            {
+                model->fhttp = flipper_http_alloc();
+            }
         },
         true);
 

+ 1 - 16
callback/flip_store_callback.h

@@ -15,17 +15,6 @@
 extern bool flip_store_app_does_exist;
 extern uint32_t selected_firmware_index;
 
-// Function to draw the description on the canvas with word wrapping
-void draw_description(Canvas *canvas, const char *user_message, int x, int y);
-
-void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model);
-
-bool flip_store_input_callback(InputEvent *event, void *context);
-
-void flip_store_text_updated_ssid(void *context);
-
-void flip_store_text_updated_pass(void *context);
-
 uint32_t callback_to_submenu(void *context);
 
 uint32_t callback_to_submenu_options(void *context);
@@ -34,11 +23,6 @@ uint32_t callback_to_firmware_list(void *context);
 
 uint32_t callback_to_app_list(void *context);
 
-void settings_item_selected(void *context, uint32_t index);
-
-void dialog_delete_callback(DialogExResult result, void *context);
-void dialog_firmware_callback(DialogExResult result, void *context);
-
 uint32_t callback_exit_app(void *context);
 void callback_submenu_choices(void *context, uint32_t index);
 
@@ -75,6 +59,7 @@ struct DataLoaderModel
     size_t request_count;
     ViewNavigationCallback back_callback;
     FuriTimer *timer;
+    FlipperHTTP *fhttp;
 };
 void flip_store_generic_switch_to_view(FlipStoreApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id);
 

+ 14 - 8
firmwares/flip_store_firmwares.c

@@ -85,9 +85,14 @@ void firmware_free()
     }
 }
 
-bool flip_store_get_firmware_file(char *link, char *name, char *filename)
+bool flip_store_get_firmware_file(FlipperHTTP *fhttp, char *link, char *name, char *filename)
 {
-    if (fhttp.state == INACTIVE)
+    if (!fhttp)
+    {
+        FURI_LOG_E(TAG, "FlipperHTTP is NULL");
+        return false;
+    }
+    if (fhttp->state == INACTIVE)
     {
         return false;
     }
@@ -97,15 +102,16 @@ bool flip_store_get_firmware_file(char *link, char *name, char *filename)
     storage_common_mkdir(storage, directory_path);
     snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher/%s", firmwares[selected_firmware_index].name);
     storage_common_mkdir(storage, directory_path);
-    snprintf(fhttp.file_path, sizeof(fhttp.file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher/%s/%s", name, filename);
-    fhttp.save_received_data = false;
-    fhttp.is_bytes_request = true;
-    bool sent_request = flipper_http_get_request_bytes(link, "{\"Content-Type\":\"application/octet-stream\"}");
+    snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher/%s/%s", name, filename);
+    furi_record_close(RECORD_STORAGE);
+    fhttp->save_received_data = false;
+    fhttp->is_bytes_request = true;
+    bool sent_request = flipper_http_get_request_bytes(fhttp, link, "{\"Content-Type\":\"application/octet-stream\"}");
     if (sent_request)
     {
-        fhttp.state = RECEIVING;
+        fhttp->state = RECEIVING;
         return true;
     }
-    fhttp.state = ISSUE;
+    fhttp->state = ISSUE;
     return false;
 }

+ 1 - 1
firmwares/flip_store_firmwares.h

@@ -16,7 +16,7 @@ Firmware *firmware_alloc();
 void firmware_free();
 
 // download and waiting process
-bool flip_store_get_firmware_file(char *link, char *name, char *filename);
+bool flip_store_get_firmware_file(FlipperHTTP *fhttp, char *link, char *name, char *filename);
 
 extern bool sent_firmware_request;
 extern bool sent_firmware_request_2;

+ 4 - 39
flip_store.c

@@ -1,8 +1,6 @@
 #include <flip_store.h>
 #include <apps/flip_store_apps.h>
 
-FlipStoreApp *app_instance = NULL;
-
 // Function to free the resources used by FlipStoreApp
 void flip_store_app_free(FlipStoreApp *app)
 {
@@ -50,46 +48,13 @@ void flip_store_app_free(FlipStoreApp *app)
     }
 
     // free the view dispatcher
-    view_dispatcher_free(app->view_dispatcher);
+    if (app->view_dispatcher)
+        view_dispatcher_free(app->view_dispatcher);
 
     // close the gui
     furi_record_close(RECORD_GUI);
 
     // free the app
-    free(app);
-}
-
-void flip_store_request_error(Canvas *canvas)
-{
-    if (fhttp.last_response != NULL)
-    {
-        if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
-            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-        }
-        else if (strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
-            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-        }
-        else
-        {
-            FURI_LOG_E(TAG, "Received an error: %s", fhttp.last_response);
-            canvas_draw_str(canvas, 0, 42, "Unusual error...");
-            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-        }
-    }
-    else
-    {
-        canvas_clear(canvas);
-        canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
-        canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-        canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-    }
+    if (app)
+        free(app);
 }

+ 1 - 4
flip_store.h

@@ -15,7 +15,7 @@
 #include <flip_store_icons.h>
 
 #define TAG "FlipStore"
-#define VERSION_TAG "FlipStore v0.7.2"
+#define VERSION_TAG "FlipStore v0.8"
 
 #define FIRMWARE_COUNT 3
 #define FIRMWARE_LINKS 3
@@ -105,7 +105,4 @@ typedef struct
 
 void flip_store_app_free(FlipStoreApp *app);
 
-void flip_store_request_error(Canvas *canvas);
-extern FlipStoreApp *app_instance;
-
 #endif // FLIP_STORE_E_H

Разница между файлами не показана из-за своего большого размера
+ 324 - 150
flipper_http/flipper_http.c


+ 55 - 32
flipper_http/flipper_http.h

@@ -1,6 +1,8 @@
-// flipper_http.h
-#ifndef FLIPPER_HTTP_H
-#define FLIPPER_HTTP_H
+// Description: Flipper HTTP API (For use with Flipper Zero and the FlipperHTTP flash: https://github.com/jblanked/FlipperHTTP)
+// License: MIT
+// Author: JBlanked
+// File: flipper_http.h
+#pragma once
 
 #include <gui/gui.h>
 #include <gui/view.h>
@@ -88,8 +90,6 @@ typedef struct
     size_t file_buffer_len;
 } FlipperHTTP;
 
-extern FlipperHTTP fhttp;
-
 // fhttp.last_response holds the last received data from the UART
 
 // Function to append received data to file
@@ -138,172 +138,189 @@ void _flipper_http_rx_callback(
 // UART initialization function
 /**
  * @brief      Initialize UART.
- * @return     true if the UART was initialized successfully, false otherwise.
- * @param      callback  The callback function to handle received data (ex. flipper_http_rx_callback).
- * @param      context   The context to pass to the callback.
+ * @return     FlipperHTTP context if the UART was initialized successfully, NULL otherwise.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_init(FlipperHTTP_Callback callback, void *context);
+FlipperHTTP *flipper_http_alloc();
 
 // Deinitialize UART
 /**
  * @brief      Deinitialize UART.
  * @return     void
+ * @param fhttp The FlipperHTTP context
  * @note       This function will stop the asynchronous RX, release the serial handle, and free the resources.
  */
-void flipper_http_deinit();
+void flipper_http_free(FlipperHTTP *fhttp);
 
 // Function to send data over UART with newline termination
 /**
  * @brief      Send data over UART with newline termination.
  * @return     true if the data was sent successfully, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @param      data  The data to send over UART.
  * @note       The data will be sent over UART with a newline character appended.
  */
-bool flipper_http_send_data(const char *data);
+bool flipper_http_send_data(FlipperHTTP *fhttp, const char *data);
 
 // Function to send a PING request
 /**
  * @brief      Send a PING request to check if the Wifi Dev Board is connected.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @note       The received data will be handled asynchronously via the callback.
  * @note       This is best used to check if the Wifi Dev Board is connected.
  * @note       The state will remain INACTIVE until a PONG is received.
  */
-bool flipper_http_ping();
+bool flipper_http_ping(FlipperHTTP *fhttp);
 
 // Function to list available commands
 /**
  * @brief      Send a command to list available commands.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_list_commands();
+bool flipper_http_list_commands(FlipperHTTP *fhttp);
 
 // Function to turn on the LED
 /**
  * @brief      Allow the LED to display while processing.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_led_on();
+bool flipper_http_led_on(FlipperHTTP *fhttp);
 
 // Function to turn off the LED
 /**
  * @brief      Disable the LED from displaying while processing.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_led_off();
+bool flipper_http_led_off(FlipperHTTP *fhttp);
 
 // Function to parse JSON data
 /**
  * @brief      Parse JSON data.
  * @return     true if the JSON data was parsed successfully, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @param      key       The key to parse from the JSON data.
  * @param      json_data The JSON data to parse.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_parse_json(const char *key, const char *json_data);
+bool flipper_http_parse_json(FlipperHTTP *fhttp, const char *key, const char *json_data);
 
 // Function to parse JSON array data
 /**
  * @brief      Parse JSON array data.
  * @return     true if the JSON array data was parsed successfully, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @param      key       The key to parse from the JSON array data.
  * @param      index     The index to parse from the JSON array data.
  * @param      json_data The JSON array data to parse.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_parse_json_array(const char *key, int index, const char *json_data);
+bool flipper_http_parse_json_array(FlipperHTTP *fhttp, const char *key, int index, const char *json_data);
 
 // Function to scan for WiFi networks
 /**
  * @brief      Send a command to scan for WiFi networks.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_scan_wifi();
+bool flipper_http_scan_wifi(FlipperHTTP *fhttp);
 
 // Function to save WiFi settings (returns true if successful)
 /**
  * @brief      Send a command to save WiFi settings.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_save_wifi(const char *ssid, const char *password);
+bool flipper_http_save_wifi(FlipperHTTP *fhttp, const char *ssid, const char *password);
 
 // Function to get IP address of WiFi Devboard
 /**
  * @brief      Send a command to get the IP address of the WiFi Devboard
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_ip_address();
+bool flipper_http_ip_address(FlipperHTTP *fhttp);
 
 // Function to get IP address of the connected WiFi network
 /**
  * @brief      Send a command to get the IP address of the connected WiFi network.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_ip_wifi();
+bool flipper_http_ip_wifi(FlipperHTTP *fhttp);
 
 // Function to disconnect from WiFi (returns true if successful)
 /**
  * @brief      Send a command to disconnect from WiFi.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_disconnect_wifi();
+bool flipper_http_disconnect_wifi(FlipperHTTP *fhttp);
 
 // Function to connect to WiFi (returns true if successful)
 /**
  * @brief      Send a command to connect to WiFi.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_connect_wifi();
+bool flipper_http_connect_wifi(FlipperHTTP *fhttp);
 
 // Function to send a GET request
 /**
  * @brief      Send a GET request to the specified URL.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @param      url  The URL to send the GET request to.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_get_request(const char *url);
+bool flipper_http_get_request(FlipperHTTP *fhttp, const char *url);
 
 // Function to send a GET request with headers
 /**
  * @brief      Send a GET request to the specified URL.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @param      url  The URL to send the GET request to.
  * @param      headers  The headers to send with the GET request.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_get_request_with_headers(const char *url, const char *headers);
+bool flipper_http_get_request_with_headers(FlipperHTTP *fhttp, const char *url, const char *headers);
 
 // Function to send a GET request with headers and return bytes
 /**
  * @brief      Send a GET request to the specified URL.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @param      url  The URL to send the GET request to.
  * @param      headers  The headers to send with the GET request.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_get_request_bytes(const char *url, const char *headers);
+bool flipper_http_get_request_bytes(FlipperHTTP *fhttp, const char *url, const char *headers);
 
 // Function to send a POST request with headers
 /**
  * @brief      Send a POST request to the specified URL.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @param      url  The URL to send the POST request to.
  * @param      headers  The headers to send with the POST request.
  * @param      data  The data to send with the POST request.
  * @note       The received data will be handled asynchronously via the callback.
  */
 bool flipper_http_post_request_with_headers(
+    FlipperHTTP *fhttp,
     const char *url,
     const char *headers,
     const char *payload);
@@ -312,23 +329,26 @@ bool flipper_http_post_request_with_headers(
 /**
  * @brief      Send a POST request to the specified URL.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @param      url  The URL to send the POST request to.
  * @param      headers  The headers to send with the POST request.
  * @param      payload  The data to send with the POST request.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_post_request_bytes(const char *url, const char *headers, const char *payload);
+bool flipper_http_post_request_bytes(FlipperHTTP *fhttp, const char *url, const char *headers, const char *payload);
 
 // Function to send a PUT request with headers
 /**
  * @brief      Send a PUT request to the specified URL.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @param      url  The URL to send the PUT request to.
  * @param      headers  The headers to send with the PUT request.
  * @param      data  The data to send with the PUT request.
  * @note       The received data will be handled asynchronously via the callback.
  */
 bool flipper_http_put_request_with_headers(
+    FlipperHTTP *fhttp,
     const char *url,
     const char *headers,
     const char *payload);
@@ -337,12 +357,14 @@ bool flipper_http_put_request_with_headers(
 /**
  * @brief      Send a DELETE request to the specified URL.
  * @return     true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
  * @param      url  The URL to send the DELETE request to.
  * @param      headers  The headers to send with the DELETE request.
  * @param      data  The data to send with the DELETE request.
  * @note       The received data will be handled asynchronously via the callback.
  */
 bool flipper_http_delete_request_with_headers(
+    FlipperHTTP *fhttp,
     const char *url,
     const char *headers,
     const char *payload);
@@ -352,21 +374,23 @@ bool flipper_http_delete_request_with_headers(
  * @brief      Callback function to handle received data asynchronously.
  * @return     void
  * @param      line     The received line.
- * @param      context  The context passed to the callback.
+ * @param      context  The FlipperHTTP context.
  * @note       The received data will be handled asynchronously via the callback and handles the state of the UART.
  */
 void flipper_http_rx_callback(const char *line, void *context);
 
 /**
  * @brief Process requests and parse JSON data asynchronously
+ * @param fhttp The FlipperHTTP context
  * @param http_request The function to send the request
  * @param parse_json The function to parse the JSON
  * @return true if successful, false otherwise
  */
-bool flipper_http_process_response_async(bool (*http_request)(void), bool (*parse_json)(void));
+bool flipper_http_process_response_async(FlipperHTTP *fhttp, bool (*http_request)(void), bool (*parse_json)(void));
 
 /**
  * @brief Perform a task while displaying a loading screen
+ * @param fhttp The FlipperHTTP context
  * @param http_request The function to send the request
  * @param parse_response The function to parse the response
  * @param success_view_id The view ID to switch to on success
@@ -374,10 +398,9 @@ bool flipper_http_process_response_async(bool (*http_request)(void), bool (*pars
  * @param view_dispatcher The view dispatcher to use
  * @return
  */
-void flipper_http_loading_task(bool (*http_request)(void),
+void flipper_http_loading_task(FlipperHTTP *fhttp,
+                               bool (*http_request)(void),
                                bool (*parse_response)(void),
                                uint32_t success_view_id,
                                uint32_t failure_view_id,
                                ViewDispatcher **view_dispatcher);
-
-#endif // FLIPPER_HTTP_H

+ 86 - 47
jsmn/jsmn.c

@@ -7,8 +7,6 @@
  */
 
 #include <jsmn/jsmn.h>
-#include <stdlib.h>
-#include <string.h>
 
 /**
  * Allocates a fresh unused token from the token pool.
@@ -424,7 +422,7 @@ int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
 }
 
 // Helper function to create a JSON object
-char *jsmn(const char *key, const char *value)
+char *get_json(const char *key, const char *value)
 {
     int length = strlen(key) + strlen(value) + 8;         // Calculate required length
     char *result = (char *)malloc(length * sizeof(char)); // Allocate memory
@@ -448,14 +446,14 @@ int jsoneq(const char *json, jsmntok_t *tok, const char *s)
 }
 
 // Return the value of the key in the JSON data
-char *get_json_value(char *key, char *json_data, uint32_t max_tokens)
+char *get_json_value(char *key, const char *json_data)
 {
     // Parse the JSON feed
     if (json_data != NULL)
     {
         jsmn_parser parser;
         jsmn_init(&parser);
-
+        uint32_t max_tokens = json_token_count(json_data);
         // Allocate tokens array on the heap
         jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
         if (tokens == NULL)
@@ -510,26 +508,73 @@ char *get_json_value(char *key, char *json_data, uint32_t max_tokens)
     {
         FURI_LOG_E("JSMM.H", "JSON data is NULL");
     }
-    FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
+    char warning[128];
+    snprintf(warning, sizeof(warning), "Failed to find the key \"%s\" in the JSON.", key);
+    FURI_LOG_E("JSMM.H", warning);
     return NULL; // Return NULL if something goes wrong
 }
 
-// Revised get_json_array_value function
-char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t max_tokens)
+// Helper function to skip a token and all its descendants.
+// Returns the index of the next token after skipping this one.
+// On error or out of bounds, returns -1.
+static int skip_token(const jsmntok_t *tokens, int start, int total)
 {
-    // Retrieve the array string for the given key
-    char *array_str = get_json_value(key, json_data, max_tokens);
+    if (start < 0 || start >= total)
+        return -1;
+
+    int i = start;
+    if (tokens[i].type == JSMN_OBJECT)
+    {
+        // For an object: size is number of key-value pairs
+        int pairs = tokens[i].size;
+        i++; // move to first key-value pair
+        for (int p = 0; p < pairs; p++)
+        {
+            // skip key (primitive/string)
+            i++;
+            if (i >= total)
+                return -1;
+            // skip value (which could be object/array and must be skipped recursively)
+            i = skip_token(tokens, i, total);
+            if (i == -1)
+                return -1;
+        }
+        return i; // i is now just past the object
+    }
+    else if (tokens[i].type == JSMN_ARRAY)
+    {
+        // For an array: size is number of elements
+        int elems = tokens[i].size;
+        i++; // move to first element
+        for (int e = 0; e < elems; e++)
+        {
+            i = skip_token(tokens, i, total);
+            if (i == -1)
+                return -1;
+        }
+        return i; // i is now just past the array
+    }
+    else
+    {
+        // Primitive or string token, just skip it
+        return i + 1;
+    }
+}
+
+// Revised get_json_array_value
+char *get_json_array_value(char *key, uint32_t index, const char *json_data)
+{
+    // Always extract the full array each time from the original json_data
+    char *array_str = get_json_value(key, json_data);
     if (array_str == NULL)
     {
         FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
         return NULL;
     }
+    uint32_t max_tokens = json_token_count(array_str);
 
-    // Initialize the JSON parser
     jsmn_parser parser;
     jsmn_init(&parser);
-
-    // Allocate memory for JSON tokens
     jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
     if (tokens == NULL)
     {
@@ -538,7 +583,6 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
         return NULL;
     }
 
-    // Parse the JSON array
     int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
     if (ret < 0)
     {
@@ -548,7 +592,6 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
         return NULL;
     }
 
-    // Ensure the root element is an array
     if (ret < 1 || tokens[0].type != JSMN_ARRAY)
     {
         FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key);
@@ -557,50 +600,33 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
         return NULL;
     }
 
-    // Check if the index is within bounds
     if (index >= (uint32_t)tokens[0].size)
     {
-        FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %d.", (unsigned long)index, tokens[0].size);
+        FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %u.", index, tokens[0].size);
         free(tokens);
         free(array_str);
         return NULL;
     }
 
-    // Locate the token corresponding to the desired array element
-    int current_token = 1; // Start after the array token
+    // Find the index-th element: start from token[1], which is the first element
+    int elem_token = 1;
     for (uint32_t i = 0; i < index; i++)
     {
-        if (tokens[current_token].type == JSMN_OBJECT)
-        {
-            // For objects, skip all key-value pairs
-            current_token += 1 + 2 * tokens[current_token].size;
-        }
-        else if (tokens[current_token].type == JSMN_ARRAY)
-        {
-            // For nested arrays, skip all elements
-            current_token += 1 + tokens[current_token].size;
-        }
-        else
+        elem_token = skip_token(tokens, elem_token, ret);
+        if (elem_token == -1 || elem_token >= ret)
         {
-            // For primitive types, simply move to the next token
-            current_token += 1;
-        }
-
-        // Safety check to prevent out-of-bounds
-        if (current_token >= ret)
-        {
-            FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
+            FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
             free(tokens);
             free(array_str);
             return NULL;
         }
     }
 
-    // Extract the array element
-    jsmntok_t element = tokens[current_token];
+    // Now elem_token should point to the token of the requested element
+    jsmntok_t element = tokens[elem_token];
     int length = element.end - element.start;
     char *value = malloc(length + 1);
-    if (value == NULL)
+    if (!value)
     {
         FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
         free(tokens);
@@ -608,11 +634,9 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
         return NULL;
     }
 
-    // Copy the element value to a new string
     strncpy(value, array_str + element.start, length);
-    value[length] = '\0'; // Null-terminate the string
+    value[length] = '\0';
 
-    // Clean up
     free(tokens);
     free(array_str);
 
@@ -620,16 +644,16 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
 }
 
 // Revised get_json_array_values function with correct token skipping
-char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values)
+char **get_json_array_values(char *key, char *json_data, int *num_values)
 {
     // Retrieve the array string for the given key
-    char *array_str = get_json_value(key, json_data, max_tokens);
+    char *array_str = get_json_value(key, json_data);
     if (array_str == NULL)
     {
         FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
         return NULL;
     }
-
+    uint32_t max_tokens = json_token_count(array_str);
     // Initialize the JSON parser
     jsmn_parser parser;
     jsmn_init(&parser);
@@ -745,3 +769,18 @@ char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, in
     free(array_str);
     return values;
 }
+
+int json_token_count(const char *json)
+{
+    if (json == NULL)
+    {
+        return JSMN_ERROR_INVAL;
+    }
+
+    jsmn_parser parser;
+    jsmn_init(&parser);
+
+    // Pass NULL for tokens and 0 for num_tokens to get the token count only
+    int ret = jsmn_parse(&parser, json, strlen(json), NULL, 0);
+    return ret; // If ret >= 0, it represents the number of tokens needed.
+}

+ 7 - 65
jsmn/jsmn.h

@@ -17,6 +17,7 @@
 #define JSMN_H
 
 #include <stddef.h>
+#include <jsmn/jsmn_h.h>
 
 #ifdef __cplusplus
 extern "C"
@@ -28,61 +29,6 @@ extern "C"
 #else
 #define JSMN_API extern
 #endif
-
-    /**
-     * JSON type identifier. Basic types are:
-     * 	o Object
-     * 	o Array
-     * 	o String
-     * 	o Other primitive: number, boolean (true/false) or null
-     */
-    typedef enum
-    {
-        JSMN_UNDEFINED = 0,
-        JSMN_OBJECT = 1 << 0,
-        JSMN_ARRAY = 1 << 1,
-        JSMN_STRING = 1 << 2,
-        JSMN_PRIMITIVE = 1 << 3
-    } jsmntype_t;
-
-    enum jsmnerr
-    {
-        /* Not enough tokens were provided */
-        JSMN_ERROR_NOMEM = -1,
-        /* Invalid character inside JSON string */
-        JSMN_ERROR_INVAL = -2,
-        /* The string is not a full JSON packet, more bytes expected */
-        JSMN_ERROR_PART = -3
-    };
-
-    /**
-     * JSON token description.
-     * type		type (object, array, string etc.)
-     * start	start position in JSON data string
-     * end		end position in JSON data string
-     */
-    typedef struct
-    {
-        jsmntype_t type;
-        int start;
-        int end;
-        int size;
-#ifdef JSMN_PARENT_LINKS
-        int parent;
-#endif
-    } jsmntok_t;
-
-    /**
-     * JSON parser. Contains an array of token blocks available. Also stores
-     * the string being parsed now and current position in that string.
-     */
-    typedef struct
-    {
-        unsigned int pos;     /* offset in the JSON string */
-        unsigned int toknext; /* next token to allocate */
-        int toksuper;         /* superior token node, e.g. parent object or array */
-    } jsmn_parser;
-
     /**
      * Create JSON parser over an array of tokens
      */
@@ -110,23 +56,19 @@ extern "C"
 #define JB_JSMN_EDIT
 /* Added in by JBlanked on 2024-10-16 for use in Flipper Zero SDK*/
 
-#include <string.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <furi.h>
-
 // Helper function to create a JSON object
-char *jsmn(const char *key, const char *value);
+char *get_json(const char *key, const char *value);
 // Helper function to compare JSON keys
 int jsoneq(const char *json, jsmntok_t *tok, const char *s);
 
 // Return the value of the key in the JSON data
-char *get_json_value(char *key, char *json_data, uint32_t max_tokens);
+char *get_json_value(char *key, const char *json_data);
 
 // Revised get_json_array_value function
-char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t max_tokens);
+char *get_json_array_value(char *key, uint32_t index, const char *json_data);
 
 // Revised get_json_array_values function with correct token skipping
-char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values);
+char **get_json_array_values(char *key, char *json_data, int *num_values);
+
+int json_token_count(const char *json);
 #endif /* JB_JSMN_EDIT */

+ 719 - 0
jsmn/jsmn_furi.c

@@ -0,0 +1,719 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * [License text continues...]
+ */
+
+#include <jsmn/jsmn_furi.h>
+
+// Forward declarations of helper functions
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s);
+static int skip_token(const jsmntok_t *tokens, int start, int total);
+
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens)
+{
+    if (parser->toknext >= num_tokens)
+    {
+        return NULL;
+    }
+    jsmntok_t *tok = &tokens[parser->toknext++];
+    tok->start = tok->end = -1;
+    tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+    tok->parent = -1;
+#endif
+    return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end)
+{
+    token->type = type;
+    token->start = start;
+    token->end = end;
+    token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const FuriString *js,
+                                jsmntok_t *tokens, const size_t num_tokens)
+{
+    size_t len = furi_string_size(js);
+    int start = parser->pos;
+
+    for (; parser->pos < len; parser->pos++)
+    {
+        char c = furi_string_get_char(js, parser->pos);
+        switch (c)
+        {
+#ifndef JSMN_STRICT
+        case ':':
+#endif
+        case '\t':
+        case '\r':
+        case '\n':
+        case ' ':
+        case ',':
+        case ']':
+        case '}':
+            goto found;
+        default:
+            break;
+        }
+        if (c < 32 || c >= 127)
+        {
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+        }
+    }
+
+#ifdef JSMN_STRICT
+    // In strict mode primitive must be followed by a comma/object/array
+    parser->pos = start;
+    return JSMN_ERROR_PART;
+#endif
+
+found:
+    if (tokens == NULL)
+    {
+        parser->pos--;
+        return 0;
+    }
+    jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
+    if (token == NULL)
+    {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+    }
+    jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+    token->parent = parser->toksuper;
+#endif
+    parser->pos--;
+    return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const FuriString *js,
+                             jsmntok_t *tokens, const size_t num_tokens)
+{
+    size_t len = furi_string_size(js);
+    int start = parser->pos;
+    parser->pos++;
+
+    for (; parser->pos < len; parser->pos++)
+    {
+        char c = furi_string_get_char(js, parser->pos);
+        if (c == '\"')
+        {
+            if (tokens == NULL)
+            {
+                return 0;
+            }
+            jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
+            if (token == NULL)
+            {
+                parser->pos = start;
+                return JSMN_ERROR_NOMEM;
+            }
+            jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+            token->parent = parser->toksuper;
+#endif
+            return 0;
+        }
+
+        if (c == '\\' && (parser->pos + 1) < len)
+        {
+            parser->pos++;
+            char esc = furi_string_get_char(js, parser->pos);
+            switch (esc)
+            {
+            case '\"':
+            case '/':
+            case '\\':
+            case 'b':
+            case 'f':
+            case 'r':
+            case 'n':
+            case 't':
+                break;
+            case 'u':
+            {
+                parser->pos++;
+                for (int i = 0; i < 4 && parser->pos < len; i++)
+                {
+                    char hex = furi_string_get_char(js, parser->pos);
+                    if (!((hex >= '0' && hex <= '9') ||
+                          (hex >= 'A' && hex <= 'F') ||
+                          (hex >= 'a' && hex <= 'f')))
+                    {
+                        parser->pos = start;
+                        return JSMN_ERROR_INVAL;
+                    }
+                    parser->pos++;
+                }
+                parser->pos--;
+                break;
+            }
+            default:
+                parser->pos = start;
+                return JSMN_ERROR_INVAL;
+            }
+        }
+    }
+    parser->pos = start;
+    return JSMN_ERROR_PART;
+}
+
+/**
+ * Create JSON parser
+ */
+void jsmn_init_furi(jsmn_parser *parser)
+{
+    parser->pos = 0;
+    parser->toknext = 0;
+    parser->toksuper = -1;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ * Now uses FuriString for the input JSON.
+ */
+int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+                    jsmntok_t *tokens, const unsigned int num_tokens)
+{
+    size_t len = furi_string_size(js);
+    int r;
+    int i;
+    int count = parser->toknext;
+
+    for (; parser->pos < len; parser->pos++)
+    {
+        char c = furi_string_get_char(js, parser->pos);
+        jsmntype_t type;
+
+        switch (c)
+        {
+        case '{':
+        case '[':
+        {
+            count++;
+            if (tokens == NULL)
+            {
+                break;
+            }
+            jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
+            if (token == NULL)
+                return JSMN_ERROR_NOMEM;
+            if (parser->toksuper != -1)
+            {
+                jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+                if (t->type == JSMN_OBJECT)
+                    return JSMN_ERROR_INVAL;
+#endif
+                t->size++;
+#ifdef JSMN_PARENT_LINKS
+                token->parent = parser->toksuper;
+#endif
+            }
+            token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+            token->start = parser->pos;
+            parser->toksuper = parser->toknext - 1;
+            break;
+        }
+        case '}':
+        case ']':
+            if (tokens == NULL)
+            {
+                break;
+            }
+            type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+            if (parser->toknext < 1)
+            {
+                return JSMN_ERROR_INVAL;
+            }
+            {
+                jsmntok_t *token = &tokens[parser->toknext - 1];
+                for (;;)
+                {
+                    if (token->start != -1 && token->end == -1)
+                    {
+                        if (token->type != type)
+                            return JSMN_ERROR_INVAL;
+                        token->end = parser->pos + 1;
+                        parser->toksuper = token->parent;
+                        break;
+                    }
+                    if (token->parent == -1)
+                    {
+                        if (token->type != type || parser->toksuper == -1)
+                        {
+                            return JSMN_ERROR_INVAL;
+                        }
+                        break;
+                    }
+                    token = &tokens[token->parent];
+                }
+            }
+#else
+            {
+                jsmntok_t *token;
+                for (i = parser->toknext - 1; i >= 0; i--)
+                {
+                    token = &tokens[i];
+                    if (token->start != -1 && token->end == -1)
+                    {
+                        if (token->type != type)
+                            return JSMN_ERROR_INVAL;
+                        parser->toksuper = -1;
+                        token->end = parser->pos + 1;
+                        break;
+                    }
+                }
+                if (i == -1)
+                    return JSMN_ERROR_INVAL;
+                for (; i >= 0; i--)
+                {
+                    token = &tokens[i];
+                    if (token->start != -1 && token->end == -1)
+                    {
+                        parser->toksuper = i;
+                        break;
+                    }
+                }
+            }
+#endif
+            break;
+        case '\"':
+            r = jsmn_parse_string(parser, js, tokens, num_tokens);
+            if (r < 0)
+                return r;
+            count++;
+            if (parser->toksuper != -1 && tokens != NULL)
+            {
+                tokens[parser->toksuper].size++;
+            }
+            break;
+        case '\t':
+        case '\r':
+        case '\n':
+        case ' ':
+            // Whitespace - ignore
+            break;
+        case ':':
+            parser->toksuper = parser->toknext - 1;
+            break;
+        case ',':
+            if (tokens != NULL && parser->toksuper != -1 &&
+                tokens[parser->toksuper].type != JSMN_ARRAY &&
+                tokens[parser->toksuper].type != JSMN_OBJECT)
+            {
+#ifdef JSMN_PARENT_LINKS
+                parser->toksuper = tokens[parser->toksuper].parent;
+#else
+                for (i = parser->toknext - 1; i >= 0; i--)
+                {
+                    if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT)
+                    {
+                        if (tokens[i].start != -1 && tokens[i].end == -1)
+                        {
+                            parser->toksuper = i;
+                            break;
+                        }
+                    }
+                }
+#endif
+            }
+            break;
+#ifdef JSMN_STRICT
+        case '-':
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+        case 't':
+        case 'f':
+        case 'n':
+            if (tokens != NULL && parser->toksuper != -1)
+            {
+                const jsmntok_t *t = &tokens[parser->toksuper];
+                if (t->type == JSMN_OBJECT ||
+                    (t->type == JSMN_STRING && t->size != 0))
+                {
+                    return JSMN_ERROR_INVAL;
+                }
+            }
+#else
+        default:
+#endif
+            r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
+            if (r < 0)
+                return r;
+            count++;
+            if (parser->toksuper != -1 && tokens != NULL)
+            {
+                tokens[parser->toksuper].size++;
+            }
+            break;
+#ifdef JSMN_STRICT
+        default:
+            return JSMN_ERROR_INVAL;
+#endif
+        }
+    }
+
+    if (tokens != NULL)
+    {
+        for (i = parser->toknext - 1; i >= 0; i--)
+        {
+            if (tokens[i].start != -1 && tokens[i].end == -1)
+            {
+                return JSMN_ERROR_PART;
+            }
+        }
+    }
+
+    return count;
+}
+
+// Helper function to create a JSON object: {"key":"value"}
+FuriString *get_json_furi(const FuriString *key, const FuriString *value)
+{
+    FuriString *result = furi_string_alloc();
+    furi_string_printf(result, "{\"%s\":\"%s\"}",
+                       furi_string_get_cstr(key),
+                       furi_string_get_cstr(value));
+    return result; // Caller responsible for furi_string_free
+}
+
+// Helper function to compare JSON keys
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s)
+{
+    size_t s_len = furi_string_size(s);
+    size_t tok_len = tok->end - tok->start;
+
+    if (tok->type != JSMN_STRING)
+        return -1;
+    if (s_len != tok_len)
+        return -1;
+
+    FuriString *sub = furi_string_alloc_set(json);
+    furi_string_mid(sub, tok->start, tok_len);
+
+    int res = furi_string_cmp(sub, s);
+    furi_string_free(sub);
+
+    return (res == 0) ? 0 : -1;
+}
+
+// Skip a token and its descendants
+static int skip_token(const jsmntok_t *tokens, int start, int total)
+{
+    if (start < 0 || start >= total)
+        return -1;
+
+    int i = start;
+    if (tokens[i].type == JSMN_OBJECT)
+    {
+        int pairs = tokens[i].size;
+        i++;
+        for (int p = 0; p < pairs; p++)
+        {
+            i++; // skip key
+            if (i >= total)
+                return -1;
+            i = skip_token(tokens, i, total); // skip value
+            if (i == -1)
+                return -1;
+        }
+        return i;
+    }
+    else if (tokens[i].type == JSMN_ARRAY)
+    {
+        int elems = tokens[i].size;
+        i++;
+        for (int e = 0; e < elems; e++)
+        {
+            i = skip_token(tokens, i, total);
+            if (i == -1)
+                return -1;
+        }
+        return i;
+    }
+    else
+    {
+        return i + 1;
+    }
+}
+
+/**
+ * Parse JSON and return the value associated with a given char* key.
+ */
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data)
+{
+    if (json_data == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "JSON data is NULL");
+        return NULL;
+    }
+    uint32_t max_tokens = json_token_count_furi(json_data);
+    // Create a temporary FuriString from key
+    FuriString *key_str = furi_string_alloc();
+    furi_string_cat_str(key_str, key);
+
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+    if (tokens == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        furi_string_free(key_str);
+        return NULL;
+    }
+
+    int ret = jsmn_parse_furi(&parser, json_data, tokens, max_tokens);
+    if (ret < 0)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
+        free(tokens);
+        furi_string_free(key_str);
+        return NULL;
+    }
+
+    if (ret < 1 || tokens[0].type != JSMN_OBJECT)
+    {
+        FURI_LOG_E("JSMM.H", "Root element is not an object.");
+        free(tokens);
+        furi_string_free(key_str);
+        return NULL;
+    }
+
+    for (int i = 1; i < ret; i++)
+    {
+        if (jsoneq_furi(json_data, &tokens[i], key_str) == 0)
+        {
+            int length = tokens[i + 1].end - tokens[i + 1].start;
+            FuriString *value = furi_string_alloc_set(json_data);
+            furi_string_mid(value, tokens[i + 1].start, length);
+            free(tokens);
+            furi_string_free(key_str);
+            return value;
+        }
+    }
+
+    free(tokens);
+    furi_string_free(key_str);
+    char warning[128];
+    snprintf(warning, sizeof(warning), "Failed to find the key \"%s\" in the JSON.", key);
+    FURI_LOG_E("JSMM.H", warning);
+    return NULL;
+}
+
+/**
+ * Return the value at a given index in a JSON array for a given char* key.
+ */
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data)
+{
+    FuriString *array_str = get_json_value_furi(key, json_data);
+    if (array_str == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to get array for key");
+        return NULL;
+    }
+    uint32_t max_tokens = json_token_count_furi(array_str);
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+    if (tokens == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+    if (ret < 0)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    if (ret < 1 || tokens[0].type != JSMN_ARRAY)
+    {
+        FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    if (index >= (uint32_t)tokens[0].size)
+    {
+        FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %u.", index, tokens[0].size);
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int elem_token = 1;
+    for (uint32_t i = 0; i < index; i++)
+    {
+        elem_token = skip_token(tokens, elem_token, ret);
+        if (elem_token == -1 || elem_token >= ret)
+        {
+            FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
+            free(tokens);
+            furi_string_free(array_str);
+            return NULL;
+        }
+    }
+
+    jsmntok_t element = tokens[elem_token];
+    int length = element.end - element.start;
+
+    FuriString *value = furi_string_alloc_set(array_str);
+    furi_string_mid(value, element.start, length);
+
+    free(tokens);
+    furi_string_free(array_str);
+
+    return value;
+}
+
+/**
+ * Extract all object values from a JSON array associated with a given char* key.
+ */
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values)
+{
+    *num_values = 0;
+    // Convert key to FuriString and call get_json_value_furi
+    FuriString *array_str = get_json_value_furi(key, json_data);
+    if (array_str == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to get array for key");
+        return NULL;
+    }
+
+    uint32_t max_tokens = json_token_count_furi(array_str);
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+    if (tokens == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+    if (ret < 0)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    if (tokens[0].type != JSMN_ARRAY)
+    {
+        FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int array_size = tokens[0].size;
+    FuriString **values = (FuriString **)malloc(array_size * sizeof(FuriString *));
+    if (values == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int actual_num_values = 0;
+    int current_token = 1;
+    for (int i = 0; i < array_size; i++)
+    {
+        if (current_token >= ret)
+        {
+            FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
+            break;
+        }
+
+        jsmntok_t element = tokens[current_token];
+
+        int length = element.end - element.start;
+        FuriString *value = furi_string_alloc_set(array_str);
+        furi_string_mid(value, element.start, length);
+
+        values[actual_num_values] = value;
+        actual_num_values++;
+
+        // Skip this element and its descendants
+        current_token = skip_token(tokens, current_token, ret);
+        if (current_token == -1)
+        {
+            FURI_LOG_E("JSMM.H", "Error skipping tokens after element %d.", i);
+            break;
+        }
+    }
+
+    *num_values = actual_num_values;
+    if (actual_num_values < array_size)
+    {
+        FuriString **reduced_values = (FuriString **)realloc(values, actual_num_values * sizeof(FuriString *));
+        if (reduced_values != NULL)
+        {
+            values = reduced_values;
+        }
+    }
+
+    free(tokens);
+    furi_string_free(array_str);
+    return values;
+}
+
+uint32_t json_token_count_furi(const FuriString *json)
+{
+    if (json == NULL)
+    {
+        return JSMN_ERROR_INVAL;
+    }
+
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    // Pass NULL for tokens and 0 for num_tokens to get the token count only
+    int ret = jsmn_parse_furi(&parser, json, NULL, 0);
+    return ret; // If ret >= 0, it represents the number of tokens needed.
+}

+ 74 - 0
jsmn/jsmn_furi.h

@@ -0,0 +1,74 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * [License text continues...]
+ */
+
+#ifndef JSMN_FURI_H
+#define JSMN_FURI_H
+
+#include <jsmn/jsmn_h.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+    JSMN_API void jsmn_init_furi(jsmn_parser *parser);
+    JSMN_API int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+                                 jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/* Implementation in jsmn_furi.c */
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_FURI_H */
+
+#ifndef JB_JSMN_FURI_EDIT
+#define JB_JSMN_FURI_EDIT
+
+// Helper function to create a JSON object
+FuriString *get_json_furi(const FuriString *key, const FuriString *value);
+
+// Updated signatures to accept const char* key
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data);
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data);
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values);
+
+uint32_t json_token_count_furi(const FuriString *json);
+/* Example usage:
+char *json = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
+FuriString *json_data = char_to_furi_string(json);
+if (!json_data)
+{
+    FURI_LOG_E(TAG, "Failed to allocate FuriString");
+    return -1;
+}
+FuriString *value = get_json_value_furi("key1", json_data, json_token_count_furi(json_data));
+if (value)
+{
+    FURI_LOG_I(TAG, "Value: %s", furi_string_get_cstr(value));
+    furi_string_free(value);
+}
+furi_string_free(json_data);
+*/
+#endif /* JB_JSMN_EDIT */

+ 14 - 0
jsmn/jsmn_h.c

@@ -0,0 +1,14 @@
+#include <jsmn/jsmn_h.h>
+FuriString *char_to_furi_string(const char *str)
+{
+    FuriString *furi_str = furi_string_alloc();
+    if (!furi_str)
+    {
+        return NULL;
+    }
+    for (size_t i = 0; i < strlen(str); i++)
+    {
+        furi_string_push_back(furi_str, str[i]);
+    }
+    return furi_str;
+}

+ 53 - 0
jsmn/jsmn_h.h

@@ -0,0 +1,53 @@
+#pragma once
+#include <furi.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+typedef enum
+{
+    JSMN_UNDEFINED = 0,
+    JSMN_OBJECT = 1 << 0,
+    JSMN_ARRAY = 1 << 1,
+    JSMN_STRING = 1 << 2,
+    JSMN_PRIMITIVE = 1 << 3
+} jsmntype_t;
+
+enum jsmnerr
+{
+    JSMN_ERROR_NOMEM = -1,
+    JSMN_ERROR_INVAL = -2,
+    JSMN_ERROR_PART = -3
+};
+
+typedef struct
+{
+    jsmntype_t type;
+    int start;
+    int end;
+    int size;
+#ifdef JSMN_PARENT_LINKS
+    int parent;
+#endif
+} jsmntok_t;
+
+typedef struct
+{
+    unsigned int pos;     /* offset in the JSON string */
+    unsigned int toknext; /* next token to allocate */
+    int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+typedef struct
+{
+    char *key;
+    char *value;
+} JSON;
+
+typedef struct
+{
+    FuriString *key;
+    FuriString *value;
+} FuriJSON;
+
+FuriString *char_to_furi_string(const char *str);

Некоторые файлы не были показаны из-за большого количества измененных файлов