Sfoglia il codice sorgente

FlipWiFi - v1.3

- Updated to save credentials for the FlipWorld game.
- Added fast commands: CUSTOM, PING, LIST, IP/ADDRESS, and WIFI/IP.
- Improved memory allocation.
- Increased the WiFi scan list capacity from 25 to 100.
jblanked 1 anno fa
parent
commit
70491e1ffa

+ 9 - 1
CHANGELOG.md

@@ -1,7 +1,15 @@
+## v1.3  
+- Updated to save credentials for the FlipWorld game.  
+- Added fast commands: CUSTOM, PING, LIST, IP/ADDRESS, and WIFI/IP.  
+- Improved memory allocation.  
+- Increased the WiFi scan list capacity from 25 to 100.  
+
+## v1.2.1
+- Fixed crash when saving networks manually.
+
 ## v1.2
 - Updated scan loading and parsing.
 - Added connectivity check on startup. (thanks to Derek Jamison)
-- Fixed crash when saving networks manually.
 
 ## v1.1
 - Fixed a freeze issue when configurations did not exist (thanks to WillyJL).  

+ 3 - 105
alloc/flip_wifi_alloc.c

@@ -1,4 +1,4 @@
-#include <alloc/flip_wifi_alloc.h>
+#include <callback/flip_wifi_callback.h>
 
 // Function to allocate resources for the FlipWiFiApp
 FlipWiFiApp *flip_wifi_app_alloc()
@@ -7,124 +7,22 @@ FlipWiFiApp *flip_wifi_app_alloc()
 
     Gui *gui = furi_record_open(RECORD_GUI);
 
-    // initialize uart
-    if (!flipper_http_init(flipper_http_rx_callback, app))
-    {
-        FURI_LOG_E(TAG, "Failed to initialize flipper http");
-        return NULL;
-    }
-
-    // Allocate the text input buffer
-    app->uart_text_input_buffer_size_password_scan = 64;
-    app->uart_text_input_buffer_size_password_saved = 64;
-    app->uart_text_input_buffer_size_add_ssid = 64;
-    app->uart_text_input_buffer_size_add_password = 64;
-    if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_password_scan, app->uart_text_input_buffer_size_password_scan))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_password_scan, app->uart_text_input_buffer_size_password_scan))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_password_saved, app->uart_text_input_buffer_size_password_saved))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_password_saved, app->uart_text_input_buffer_size_password_saved))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_add_ssid, app->uart_text_input_buffer_size_add_ssid))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_add_ssid, app->uart_text_input_buffer_size_add_ssid))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_add_password, app->uart_text_input_buffer_size_add_password))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_add_password, app->uart_text_input_buffer_size_add_password))
-    {
-        return NULL;
-    }
-
     // Allocate ViewDispatcher
     if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app))
     {
         return NULL;
     }
 
-    // View(s)
-    if (!easy_flipper_set_view(&app->view_wifi_scan, FlipWiFiViewWiFiScan, flip_wifi_view_draw_callback_scan, flip_wifi_view_input_callback_scan, callback_to_submenu_scan, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_view(&app->view_wifi_saved, FlipWiFiViewWiFiSaved, flip_wifi_view_draw_callback_saved, flip_wifi_view_input_callback_saved, callback_to_submenu_saved, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-
-    // Widget
-    if (!easy_flipper_set_widget(&app->widget_info, FlipWiFiViewAbout, "FlipWiFi v1.2.1\n-----\nFlipperHTTP companion app.\nScan and save WiFi networks.\n-----\nwww.github.com/jblanked", callback_to_submenu_main, &app->view_dispatcher))
-    {
-        return NULL;
-    }
-
-    // Text Input
-    if (!easy_flipper_set_uart_text_input(&app->uart_text_input_password_scan, FlipWiFiViewTextInputScan, "Enter WiFi Password", app->uart_text_input_temp_buffer_password_scan, app->uart_text_input_buffer_size_password_scan, flip_wifi_text_updated_password_scan, callback_to_submenu_scan, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->uart_text_input_password_saved, FlipWiFiViewTextInputSaved, "Enter WiFi Password", app->uart_text_input_temp_buffer_password_saved, app->uart_text_input_buffer_size_password_saved, flip_wifi_text_updated_password_saved, callback_to_submenu_saved, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->uart_text_input_add_ssid, FlipWiFiViewTextInputSavedAddSSID, "Enter SSID", app->uart_text_input_temp_buffer_add_ssid, app->uart_text_input_buffer_size_add_ssid, flip_wifi_text_updated_add_ssid, callback_to_submenu_saved, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->uart_text_input_add_password, FlipWiFiViewTextInputSavedAddPassword, "Enter Password", app->uart_text_input_temp_buffer_add_password, app->uart_text_input_buffer_size_add_password, flip_wifi_text_updated_add_password, callback_to_submenu_saved, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-
     // Submenu
-    if (!easy_flipper_set_submenu(&app->submenu_main, FlipWiFiViewSubmenuMain, "FlipWiFi v1.2.1", easy_flipper_callback_exit_app, &app->view_dispatcher))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_submenu(&app->submenu_wifi_scan, FlipWiFiViewSubmenuScan, "WiFi Scan", callback_to_submenu_main, &app->view_dispatcher))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_submenu(&app->submenu_wifi_saved, FlipWiFiViewSubmenuSaved, "Saved APs", callback_to_submenu_main, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu_main, FlipWiFiViewSubmenuMain, "FlipWiFi v1.3", callback_exit_app, &app->view_dispatcher))
     {
         return NULL;
     }
     submenu_add_item(app->submenu_main, "Scan", FlipWiFiSubmenuIndexWiFiScan, callback_submenu_choices, app);
     submenu_add_item(app->submenu_main, "Saved APs", FlipWiFiSubmenuIndexWiFiSaved, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_main, "Commands", FlipWiFiSubmenuIndexCommands, callback_submenu_choices, app);
     submenu_add_item(app->submenu_main, "Info", FlipWiFiSubmenuIndexAbout, callback_submenu_choices, app);
 
-    // Load the playlist from storage
-    if (!load_playlist(&app->wifi_playlist))
-    {
-        FURI_LOG_E(TAG, "Failed to load playlist");
-
-        // playlist is empty?
-        submenu_reset(app->submenu_wifi_saved);
-        submenu_set_header(app->submenu_wifi_saved, "Saved APs");
-        submenu_add_item(app->submenu_wifi_saved, "[Add Network]", FlipWiFiSubmenuIndexWiFiSavedAddSSID, callback_submenu_choices, app);
-    }
-    else
-    {
-        // Update the submenu
-        flip_wifi_redraw_submenu_saved(app);
-    }
-
     // Switch to the main view
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuMain);
 

+ 1 - 6
alloc/flip_wifi_alloc.h

@@ -1,11 +1,6 @@
-#ifndef FLIP_WIFI_I_H
-#define FLIP_WIFI_I_H
+#pragma once
 
 #include <flip_wifi.h>
-#include <flip_storage/flip_wifi_storage.h>
-#include <callback/flip_wifi_callback.h>
 
 // Function to allocate resources for the FlipWiFiApp
 FlipWiFiApp *flip_wifi_app_alloc();
-
-#endif // FLIP_WIFI_I_H

+ 19 - 29
app.c

@@ -4,27 +4,26 @@
 // Entry point for the FlipWiFi application
 int32_t flip_wifi_main(void *p)
 {
-    // Suppress unused parameter warning
     UNUSED(p);
 
     // Initialize the FlipWiFi application
-    app_instance = flip_wifi_app_alloc();
-    if (!app_instance)
+    FlipWiFiApp *app = flip_wifi_app_alloc();
+    if (!app)
     {
         FURI_LOG_E(TAG, "Failed to allocate FlipWiFiApp");
         return -1;
     }
 
-    if (!flipper_http_ping())
+    // check if board is connected (Derek Jamison)
+    // initialize the http
+    if (flipper_http_init(flipper_http_rx_callback, app))
     {
-        FURI_LOG_E(TAG, "Failed to ping the device");
-        return -1;
-    }
+        if (!flipper_http_ping())
+        {
+            FURI_LOG_E(TAG, "Failed to ping the device");
+            return -1;
+        }
 
-    // Thanks to Derek Jamison for the following code snippet:
-    if (app_instance->uart_text_input_buffer_add_ssid != NULL &&
-        app_instance->uart_text_input_buffer_add_password != NULL)
-    {
         // Try to wait for pong response.
         uint8_t counter = 10;
         while (fhttp.state == INACTIVE && --counter > 0)
@@ -34,29 +33,20 @@ int32_t flip_wifi_main(void *p)
         }
 
         if (counter == 0)
-        {
-            DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
-            DialogMessage *message = dialog_message_alloc();
-            dialog_message_set_header(
-                message, "[FlipperHTTP Error]", 64, 0, AlignCenter, AlignTop);
-            dialog_message_set_text(
-                message,
-                "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.",
-                0,
-                63,
-                AlignLeft,
-                AlignBottom);
-            dialog_message_show(dialogs, message);
-            dialog_message_free(message);
-            furi_record_close(RECORD_DIALOGS);
-        }
+            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();
+    }
+    else
+    {
+        easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
     }
 
     // Run the view dispatcher
-    view_dispatcher_run(app_instance->view_dispatcher);
+    view_dispatcher_run(app->view_dispatcher);
 
     // Free the resources used by the FlipWiFi application
-    flip_wifi_app_free(app_instance);
+    flip_wifi_app_free(app);
 
     // Return 0 to indicate success
     return 0;

+ 1 - 1
application.fam

@@ -9,6 +9,6 @@ App(
     fap_icon_assets="assets",
     fap_author="JBlanked",
     fap_weburl="https://github.com/jblanked/FlipWiFi",
-    fap_version="1.2.1",
+    fap_version="1.3",
     fap_description="FlipperHTTP companion app.",
 )

BIN
assets/01-home.png


File diff suppressed because it is too large
+ 674 - 126
callback/flip_wifi_callback.c


+ 4 - 37
callback/flip_wifi_callback.h

@@ -1,41 +1,8 @@
-#ifndef FLIP_WIFI_CALLBACK_H
-#define FLIP_WIFI_CALLBACK_H
-
+#pragma once
 #include <flip_wifi.h>
 #include <flip_storage/flip_wifi_storage.h>
 #include <flip_wifi_icons.h>
 
-// array to store each SSID
-extern char *ssid_list[64];
-extern uint32_t ssid_index;
-
-void flip_wifi_redraw_submenu_saved(FlipWiFiApp *app);
-
-uint32_t callback_to_submenu_main(void *context);
-
-uint32_t callback_to_submenu_scan(void *context);
-
-uint32_t callback_to_submenu_saved(void *context);
-
-// Callback for drawing the main screen
-void flip_wifi_view_draw_callback_scan(Canvas *canvas, void *model);
-
-void flip_wifi_view_draw_callback_saved(Canvas *canvas, void *model);
-
-// Input callback for the view (async input handling)
-bool flip_wifi_view_input_callback_scan(InputEvent *event, void *context);
-
-// Input callback for the view (async input handling)
-bool flip_wifi_view_input_callback_saved(InputEvent *event, void *context);
-
-void callback_submenu_choices(void *context, uint32_t index);
-
-void flip_wifi_text_updated_password_scan(void *context);
-
-void flip_wifi_text_updated_password_saved(void *context);
-
-void flip_wifi_text_updated_add_ssid(void *context);
-
-void flip_wifi_text_updated_add_password(void *context);
-
-#endif // FLIP_WIFI_CALLBACK_H
+void flip_wifi_free_all(void *context);
+uint32_t callback_exit_app(void *context);
+void callback_submenu_choices(void *context, uint32_t index);

+ 20 - 0
easy_flipper/easy_flipper.c

@@ -1,5 +1,25 @@
 #include <easy_flipper/easy_flipper.h>
 
+void easy_flipper_dialog(
+    char *header,
+    char *text)
+{
+    DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
+    DialogMessage *message = dialog_message_alloc();
+    dialog_message_set_header(
+        message, header, 64, 0, AlignCenter, AlignTop);
+    dialog_message_set_text(
+        message,
+        text,
+        0,
+        63,
+        AlignLeft,
+        AlignBottom);
+    dialog_message_show(dialogs, message);
+    dialog_message_free(message);
+    furi_record_close(RECORD_DIALOGS);
+}
+
 /**
  * @brief Navigation callback for exiting the application
  * @param context The context - unused

+ 6 - 0
easy_flipper/easy_flipper.h

@@ -8,6 +8,7 @@
 #include <gui/view.h>
 #include <gui/modules/submenu.h>
 #include <gui/view_dispatcher.h>
+#include <gui/elements.h>
 #include <gui/modules/menu.h>
 #include <gui/modules/submenu.h>
 #include <gui/modules/widget.h>
@@ -23,9 +24,14 @@
 #include <stdio.h>
 #include <string.h>
 #include <jsmn/jsmn.h>
+#include <jsmn/jsmn_furi.h>
 
 #define EASY_TAG "EasyFlipper"
 
+void easy_flipper_dialog(
+    char *header,
+    char *text);
+
 /**
  * @brief Navigation callback for exiting the application
  * @param context The context - unused

+ 141 - 151
flip_storage/flip_wifi_storage.c

@@ -1,6 +1,6 @@
 #include <flip_storage/flip_wifi_storage.h>
 
-char *app_ids[8] = {
+static char *app_ids[8] = {
     "flip_wifi",
     "flip_store",
     "flip_social",
@@ -8,7 +8,7 @@ char *app_ids[8] = {
     "flip_weather",
     "flip_library",
     "web_crawler",
-    "flip_rss"};
+    "flip_world"};
 
 // Function to save the playlist
 void save_playlist(WiFiPlaylist *playlist)
@@ -21,7 +21,7 @@ void save_playlist(WiFiPlaylist *playlist)
 
     // Create the directory for saving settings
     char directory_path[128];
-    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi");
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data");
 
     // Open storage
     Storage *storage = furi_record_open(RECORD_STORAGE);
@@ -50,30 +50,36 @@ void save_playlist(WiFiPlaylist *playlist)
         return;
     }
 
+    FuriString *json_result = furi_string_alloc();
+    if (!json_result)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate FuriString");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return;
+    }
+    furi_string_cat(json_result, "{\"ssids\":[\n");
     for (size_t i = 0; i < playlist->count; i++)
     {
-        if (!playlist->ssids[i] || !playlist->passwords[i])
+        furi_string_cat_printf(json_result, "{\"ssid\":\"%s\",\"password\":\"%s\"}", playlist->ssids[i], playlist->passwords[i]);
+        if (i < playlist->count - 1)
         {
-            FURI_LOG_E(TAG, "Invalid SSID or password at index %zu", i);
-            continue;
-        }
-        size_t ssid_length = strlen(playlist->ssids[i]);
-        size_t password_length = strlen(playlist->passwords[i]);
-        if (storage_file_write(file, playlist->ssids[i], ssid_length) != ssid_length ||
-            storage_file_write(file, ",", 1) != 1 ||
-            storage_file_write(file, playlist->passwords[i], password_length) != password_length ||
-            storage_file_write(file, "\n", 1) != 1)
-        {
-            FURI_LOG_E(TAG, "Failed to write playlist");
+            furi_string_cat(json_result, ",\n");
         }
     }
-
+    furi_string_cat(json_result, "\n]}");
+    size_t json_length = furi_string_size(json_result);
+    if (storage_file_write(file, furi_string_get_cstr(json_result), json_length) != json_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write playlist to file");
+    }
+    furi_string_free(json_result);
     storage_file_close(file);
     storage_file_free(file);
     furi_record_close(RECORD_STORAGE);
 }
 
-// Function to load the playlist
 bool load_playlist(WiFiPlaylist *playlist)
 {
     if (!playlist)
@@ -82,151 +88,40 @@ bool load_playlist(WiFiPlaylist *playlist)
         return false;
     }
 
-    // Initialize playlist count
-    playlist->count = 0;
-
-    // Allocate memory for SSIDs and passwords if not already allocated
-    for (size_t i = 0; i < MAX_WIFI_NETWORKS; i++)
-    {
-        if (!playlist->ssids[i])
-        {
-            playlist->ssids[i] = malloc(64); // Adjust size as needed
-            if (!playlist->ssids[i])
-            {
-                FURI_LOG_E(TAG, "Memory allocation failed for ssids[%zu]", i);
-                // Handle memory allocation failure (e.g., clean up and return)
-                return false;
-            }
-        }
-
-        if (!playlist->passwords[i])
-        {
-            playlist->passwords[i] = malloc(64); // Adjust size as needed
-            if (!playlist->passwords[i])
-            {
-                FURI_LOG_E(TAG, "Memory allocation failed for passwords[%zu]", i);
-                // Handle memory allocation failure (e.g., clean up and return)
-                return false;
-            }
-        }
-    }
-
-    // Open the settings file
-    Storage *storage = furi_record_open(RECORD_STORAGE);
-    if (!storage)
-    {
-        FURI_LOG_E(TAG, "Failed to open storage record");
-        return false;
-    }
-
-    File *file = storage_file_alloc(storage);
-    if (!file)
+    FuriString *json_result = flipper_http_load_from_file(WIFI_SSID_LIST_PATH);
+    if (!json_result)
     {
-        FURI_LOG_E(TAG, "Failed to allocate file handle");
-        furi_record_close(RECORD_STORAGE);
+        FURI_LOG_E(TAG, "Failed to load playlist from file");
         return false;
     }
 
-    if (!storage_file_open(file, WIFI_SSID_LIST_PATH, FSAM_READ, FSOM_OPEN_EXISTING))
-    {
-        FURI_LOG_E(TAG, "Failed to open settings file for reading: %s", WIFI_SSID_LIST_PATH);
-        storage_file_free(file);
-        furi_record_close(RECORD_STORAGE);
-        return false; // Return false if the file does not exist
-    }
-
-    // Buffer to hold each line
-    char line_buffer[128];
-    size_t line_pos = 0;
-    char ch;
-
-    while (storage_file_read(file, &ch, 1) == 1)
-    {
-        if (ch == '\n')
-        {
-            // Null-terminate the line
-            line_buffer[line_pos] = '\0';
-
-            // Split the line into SSID and Password
-            char *comma_pos = strchr(line_buffer, ',');
-            if (comma_pos)
-            {
-                *comma_pos = '\0'; // Replace comma with null character
-
-                // Copy SSID
-                strncpy(playlist->ssids[playlist->count], line_buffer, 63);
-                playlist->ssids[playlist->count][63] = '\0'; // Ensure null-termination
-
-                // Copy Password
-                strncpy(playlist->passwords[playlist->count], comma_pos + 1, 63);
-                playlist->passwords[playlist->count][63] = '\0'; // Ensure null-termination
-
-                playlist->count++;
-
-                if (playlist->count >= MAX_WIFI_NETWORKS)
-                {
-                    FURI_LOG_W(TAG, "Reached maximum number of WiFi networks: %d", MAX_WIFI_NETWORKS);
-                    break;
-                }
-            }
-            else
-            {
-                FURI_LOG_E(TAG, "Invalid line format (no comma found): %s", line_buffer);
-            }
-
-            // Reset line buffer position for the next line
-            line_pos = 0;
-        }
-        else
-        {
-            if (line_pos < sizeof(line_buffer) - 1)
-            {
-                line_buffer[line_pos++] = ch;
-            }
-            else
-            {
-                FURI_LOG_E(TAG, "Line buffer overflow");
-                // Optionally handle line overflow (e.g., skip the rest of the line)
-                line_pos = 0;
-            }
-        }
-    }
+    // Initialize playlist count
+    playlist->count = 0;
 
-    // Handle the last line if it does not end with a newline
-    if (line_pos > 0)
+    // Parse the JSON result
+    for (size_t i = 0; i < MAX_SAVED_NETWORKS; i++)
     {
-        line_buffer[line_pos] = '\0';
-        char *comma_pos = strchr(line_buffer, ',');
-        if (comma_pos)
+        FuriString *json_data = get_json_array_value_furi("ssids", i, json_result);
+        if (!json_data)
         {
-            *comma_pos = '\0'; // Replace comma with null character
-
-            // Copy SSID
-            strncpy(playlist->ssids[playlist->count], line_buffer, 63);
-            playlist->ssids[playlist->count][63] = '\0'; // Ensure null-termination
-
-            // Copy Password
-            strncpy(playlist->passwords[playlist->count], comma_pos + 1, 63);
-            playlist->passwords[playlist->count][63] = '\0'; // Ensure null-termination
-
-            playlist->count++;
-
-            if (playlist->count >= MAX_WIFI_NETWORKS)
-            {
-                FURI_LOG_W(TAG, "Reached maximum number of WiFi networks: %d", MAX_WIFI_NETWORKS);
-            }
+            break;
         }
-        else
+        FuriString *ssid = get_json_value_furi("ssid", json_data);
+        FuriString *password = get_json_value_furi("password", json_data);
+        if (!ssid || !password)
         {
-            FURI_LOG_E(TAG, "Invalid line format (no comma found): %s", line_buffer);
+            FURI_LOG_E(TAG, "Failed to get SSID or Password from JSON");
+            furi_string_free(json_data);
+            break;
         }
+        snprintf(playlist->ssids[i], MAX_SSID_LENGTH, "%s", furi_string_get_cstr(ssid));
+        snprintf(playlist->passwords[i], MAX_SSID_LENGTH, "%s", furi_string_get_cstr(password));
+        playlist->count++;
+        furi_string_free(json_data);
+        furi_string_free(ssid);
+        furi_string_free(password);
     }
-
-    // Close and free file resources
-    storage_file_close(file);
-    storage_file_free(file);
-    furi_record_close(RECORD_STORAGE);
-
+    furi_string_free(json_result);
     return true;
 }
 
@@ -429,4 +324,99 @@ void save_settings(const char *ssid, const char *password)
         storage_file_free(file);
         furi_record_close(RECORD_STORAGE);
     }
+}
+bool save_char(
+    const char *path_name, const char *value)
+{
+    if (!value)
+    {
+        return false;
+    }
+    // Create the directory for saving settings
+    char directory_path[256];
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data");
+
+    // Create the directory
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    if (!storage)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to open storage record");
+        return false;
+    }
+    storage_common_mkdir(storage, directory_path);
+
+    // Open the settings file
+    File *file = storage_file_alloc(storage);
+    char file_path[256];
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data/%s.txt", path_name);
+
+    // Open the file in write mode
+    if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to open file for writing: %s", file_path);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Write the data to the file
+    size_t data_size = strlen(value) + 1; // Include null terminator
+    if (storage_file_write(file, value, data_size) != data_size)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+
+    return true;
+}
+
+bool load_char(
+    const char *path_name,
+    char *value,
+    size_t value_size)
+{
+    if (!value)
+    {
+        return false;
+    }
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    File *file = storage_file_alloc(storage);
+
+    char file_path[256];
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data/%s.txt", path_name);
+
+    // Open the file for reading
+    if (!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING))
+    {
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return NULL; // Return false if the file does not exist
+    }
+
+    // Read data into the buffer
+    size_t read_count = storage_file_read(file, value, value_size);
+    if (storage_file_get_error(file) != FSE_OK)
+    {
+        FURI_LOG_E(HTTP_TAG, "Error reading from file.");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Ensure null-termination
+    value[read_count - 1] = '\0';
+
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+
+    return true;
 }

+ 10 - 6
flip_storage/flip_wifi_storage.h

@@ -1,12 +1,9 @@
-#ifndef FLIP_WIFI_STORAGE_H
-#define FLIP_WIFI_STORAGE_H
+#pragma once
 
 #include <flip_wifi.h>
 
 // define the paths for all of the FlipperHTTP apps
-#define WIFI_SSID_LIST_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/wifi_list.txt"
-
-extern char *app_ids[8];
+#define WIFI_SSID_LIST_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data/wifi_list.txt"
 
 // Function to save the playlist
 void save_playlist(WiFiPlaylist *playlist);
@@ -15,4 +12,11 @@ void save_playlist(WiFiPlaylist *playlist);
 bool load_playlist(WiFiPlaylist *playlist);
 
 void save_settings(const char *ssid, const char *password);
-#endif
+
+bool save_char(
+    const char *path_name, const char *value);
+
+bool load_char(
+    const char *path_name,
+    char *value,
+    size_t value_size);

+ 3 - 72
flip_wifi.c

@@ -1,7 +1,6 @@
 #include "flip_wifi.h"
-
-FlipWiFiApp *app_instance = NULL;
-
+#include <callback/flip_wifi_callback.h>
+WiFiPlaylist *wifi_playlist = NULL;
 // Function to free the resources used by FlipWiFiApp
 void flip_wifi_app_free(FlipWiFiApp *app)
 {
@@ -11,82 +10,14 @@ void flip_wifi_app_free(FlipWiFiApp *app)
         return;
     }
 
-    // Free View(s)
-    if (app->view_wifi_scan)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewWiFiScan);
-        view_free(app->view_wifi_scan);
-    }
-    if (app->view_wifi_saved)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewWiFiSaved);
-        view_free(app->view_wifi_saved);
-    }
-
     // Free Submenu(s)
     if (app->submenu_main)
     {
         view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewSubmenuMain);
         submenu_free(app->submenu_main);
     }
-    if (app->submenu_wifi_scan)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewSubmenuScan);
-        submenu_free(app->submenu_wifi_scan);
-    }
-    if (app->submenu_wifi_saved)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
-        submenu_free(app->submenu_wifi_saved);
-    }
-
-    // Free Widget(s)
-    if (app->widget_info)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewAbout);
-        widget_free(app->widget_info);
-    }
-
-    // Free Text Input(s)
-    if (app->uart_text_input_password_scan)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputScan);
-        uart_text_input_free(app->uart_text_input_password_scan);
-    }
-    if (app->uart_text_input_password_saved)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputSaved);
-        uart_text_input_free(app->uart_text_input_password_saved);
-    }
-    if (app->uart_text_input_add_ssid)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputSavedAddSSID);
-        uart_text_input_free(app->uart_text_input_add_ssid);
-    }
-    if (app->uart_text_input_add_password)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputSavedAddPassword);
-        uart_text_input_free(app->uart_text_input_add_password);
-    }
-
-    // free playlist
-    for (size_t i = 0; i < app->wifi_playlist.count; i++)
-    {
-        if (app->wifi_playlist.ssids[i])
-            free(app->wifi_playlist.ssids[i]);
-        if (app->wifi_playlist.passwords[i])
-            free(app->wifi_playlist.passwords[i]);
-    }
-
-    // free popup
-    if (app->popup)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewPopup);
-        popup_free(app->popup);
-    }
 
-    // deinitalize flipper http
-    flipper_http_deinit();
+    flip_wifi_free_all(app);
 
     // free the view dispatcher
     if (app->view_dispatcher)

+ 32 - 47
flip_wifi.h

@@ -6,7 +6,9 @@
 #include <storage/storage.h>
 
 #define TAG "FlipWiFi"
-#define MAX_WIFI_NETWORKS 25
+#define MAX_SCAN_NETWORKS 100
+#define MAX_SAVED_NETWORKS 25
+#define MAX_SSID_LENGTH 64
 
 // Define the submenu items for our FlipWiFi application
 typedef enum
@@ -15,9 +17,11 @@ typedef enum
     //
     FlipWiFiSubmenuIndexWiFiScan,
     FlipWiFiSubmenuIndexWiFiSaved,
+    FlipWiFiSubmenuIndexCommands,
     //
     FlipWiFiSubmenuIndexWiFiSavedAddSSID,
     //
+    FlipWiFiSubmenuIndexFastCommandStart = 50,
     FlipWiFiSubmenuIndexWiFiScanStart = 100,
     FlipWiFiSubmenuIndexWiFiSavedStart = 200,
 } FlipWiFiSubmenuIndex;
@@ -25,70 +29,51 @@ typedef enum
 // Define a single view for our FlipWiFi application
 typedef enum
 {
-    FlipWiFiViewWiFiScan,       // The view for the wifi scan screen
-    FlipWiFiViewWiFiSaved,      // The view for the wifi scan screen
-    FlipWiFiViewSubmenuMain,    // The submenu for the main screen
-    FlipWiFiViewSubmenuScan,    // The submenu for the wifi scan screen
-    FlipWiFiViewSubmenuSaved,   // The submenu for the wifi scan screen
-    FlipWiFiViewAbout,          // The about screen
-    FlipWiFiViewTextInputScan,  // The text input screen for the wifi scan screen
-    FlipWiFiViewTextInputSaved, // The text input screen for the wifi saved screen
+    FlipWiFiViewWiFiScan,  // The view for the wifi scan screen
+    FlipWiFiViewWiFiSaved, // The view for the wifi scan screen
+    //
+    FlipWiFiViewSubmenuMain,     // The submenu for the main screen
+    FlipWiFiViewSubmenuScan,     // The submenu for the wifi scan screen
+    FlipWiFiViewSubmenuSaved,    // The submenu for the wifi saved screen
+    FlipWiFiViewSubmenuCommands, // The submenu for the fast commands screen
+    FlipWiFiViewAbout,           // The about screen
+    FlipWiFiViewTextInputScan,   // The text input screen for the wifi scan screen
+    FlipWiFiViewTextInputSaved,  // The text input screen for the wifi saved screen
     //
     FlipWiFiViewTextInputSavedAddSSID,     // The text input screen for the wifi saved screen
     FlipWiFiViewTextInputSavedAddPassword, // The text input screen for the wifi saved screen
     //
-    FlipWiFiViewPopup, // The popup screen
+    FlipWiFiViewGeneric,   // generic view
+    FlipWiFiViewSubmenu,   // generic submenu
+    FlipWiFiViewTextInput, // generic text input
 } FlipWiFiView;
 
 // Define the WiFiPlaylist structure
 typedef struct
 {
-    char *ssids[MAX_WIFI_NETWORKS];
-    char *passwords[MAX_WIFI_NETWORKS];
+    char ssids[MAX_SAVED_NETWORKS][MAX_SSID_LENGTH];
+    char passwords[MAX_SAVED_NETWORKS][MAX_SSID_LENGTH];
     size_t count;
 } WiFiPlaylist;
 
 // Each screen will have its own view
 typedef struct
 {
-    ViewDispatcher *view_dispatcher;                // Switches between our views
-    Popup *popup;                                   // The popup for the app
-    View *view_wifi_scan;                           // The view for the wifi scan screen
-    View *view_wifi_saved;                          // The view for the wifi saved screen
-    Submenu *submenu_main;                          // The submenu for the main screen
-    Submenu *submenu_wifi_scan;                     // The submenu for the wifi scan screen
-    Submenu *submenu_wifi_saved;                    // The submenu for the saved wifi screen
-    Widget *widget_info;                            // The widget
-    VariableItemList *variable_item_list_wifi;      // The variable item list (settngs)
-    VariableItem *variable_item_ssid;               // The variable item
-    UART_TextInput *uart_text_input_password_scan;  // The text input for the wifi scan screen
-    UART_TextInput *uart_text_input_password_saved; // The text input for the wifi saved screen
-    //
-    UART_TextInput *uart_text_input_add_ssid;     // The text input for the wifi saved screen
-    UART_TextInput *uart_text_input_add_password; // The text input for the wifi saved screen
-
-    char *uart_text_input_buffer_password_scan;         // Buffer for the text input
-    char *uart_text_input_temp_buffer_password_scan;    // Temporary buffer for the text input
-    uint32_t uart_text_input_buffer_size_password_scan; // Size of the text input buffer
-
-    char *uart_text_input_buffer_password_saved;         // Buffer for the text input
-    char *uart_text_input_temp_buffer_password_saved;    // Temporary buffer for the text input
-    uint32_t uart_text_input_buffer_size_password_saved; // Size of the text input buffer
-
-    char *uart_text_input_buffer_add_ssid;         // Buffer for the text input
-    char *uart_text_input_temp_buffer_add_ssid;    // Temporary buffer for the text input
-    uint32_t uart_text_input_buffer_size_add_ssid; // Size of the text input buffer
-
-    char *uart_text_input_buffer_add_password;         // Buffer for the text input
-    char *uart_text_input_temp_buffer_add_password;    // Temporary buffer for the text input
-    uint32_t uart_text_input_buffer_size_add_password; // Size of the text input buffer
-
-    WiFiPlaylist wifi_playlist; // The playlist of wifi networks
+    ViewDispatcher *view_dispatcher;           // Switches between our views
+    Widget *widget_info;                       // The widget for the about screen
+    View *view_wifi;                           // generic view for the wifi scan and saved screens
+    Submenu *submenu_main;                     // The submenu for the main screen
+    Submenu *submenu_wifi;                     // generic submenu for the wifi scan and saved screens
+    VariableItemList *variable_item_list_wifi; // The variable item list (settngs)
+    VariableItem *variable_item_ssid;          // The variable item
+    UART_TextInput *uart_text_input;           // The text input screen
+    char *uart_text_input_buffer;              // Buffer for the text input
+    char *uart_text_input_temp_buffer;         // Temporary buffer for the text input
+    uint32_t uart_text_input_buffer_size;      // Size of the text input buffer
 } FlipWiFiApp;
 
-extern FlipWiFiApp *app_instance;
-
 // Function to free the resources used by FlipWiFiApp
 void flip_wifi_app_free(FlipWiFiApp *app);
+extern WiFiPlaylist *wifi_playlist; // The playlist of wifi networks
 
 #endif // FLIP_WIFI_E_H

+ 13 - 1
flipper_http/flipper_http.c

@@ -650,10 +650,22 @@ bool flipper_http_scan_wifi()
     // custom for FlipWiFi app
     fhttp.just_started_get = true;
 
+    // Create the directory for saving settings
+    char directory_path[256];
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data");
+
+    // Create the directory
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    storage_common_mkdir(storage, directory_path);
+
     snprintf(
         fhttp.file_path,
         sizeof(fhttp.file_path),
-        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/scan.txt");
+        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data/scan.txt");
+
+    // ensure the file is empty
+    storage_simply_remove_recursive(storage, fhttp.file_path);
+    furi_record_close(RECORD_STORAGE);
 
     fhttp.save_received_data = true;
 

+ 1 - 1
flipper_http/flipper_http.h

@@ -19,7 +19,7 @@
 #define UART_CH (FuriHalSerialIdUsart)    // UART channel
 #define TIMEOUT_DURATION_TICKS (5 * 1000) // 5 seconds
 #define BAUDRATE (115200)                 // UART baudrate
-#define RX_BUF_SIZE 1024                  // UART RX buffer size
+#define RX_BUF_SIZE 2048                  // UART RX buffer size
 #define RX_LINE_BUFFER_SIZE 4096          // UART RX line buffer size (increase for large responses)
 #define MAX_FILE_SHOW 4096                // Maximum data from file to show
 #define FILE_BUFFER_SIZE 512              // File buffer size

+ 85 - 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,32 @@ 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);
         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 +633,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 +643,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 +768,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 */

+ 718 - 0
jsmn/jsmn_furi.c

@@ -0,0 +1,718 @@
+/*
+ * 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)
+    {
+        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);

Some files were not shown because too many files changed in this diff