فهرست منبع

FlipStore - v0.3

- Edits by Willy-JL
- Improved memory allocation
- Stability Patch
jblanked 1 سال پیش
والد
کامیت
66e92ab5e9

+ 1 - 1
README.md

@@ -20,7 +20,7 @@ Download Flipper Zero apps directly to your Flipper Zero using WiFi. You no long
 - App Categories
 
 **v0.3**
-- Caching
+- Improved memory allocation
 - Stability Patch 2
 - App Catalog Patch (add in required functionalility)
 

+ 4 - 8
flip_store_i.h → alloc/flip_store_alloc.c

@@ -1,8 +1,6 @@
-#ifndef FLIP_STORE_I_H
-#define FLIP_STORE_I_H
-
+#include <alloc/flip_store_alloc.h>
 // Function to allocate resources for the FlipStoreApp
-static FlipStoreApp *flip_store_app_alloc()
+FlipStoreApp *flip_store_app_alloc()
 {
     FlipStoreApp *app = (FlipStoreApp *)malloc(sizeof(FlipStoreApp));
 
@@ -107,7 +105,7 @@ static FlipStoreApp *flip_store_app_alloc()
     app->variable_item_pass = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
 
     // Submenu
-    if (!easy_flipper_set_submenu(&app->submenu, FlipStoreViewSubmenu, "FlipStore", callback_exit_app, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu, FlipStoreViewSubmenu, "FlipStore v0.3", callback_exit_app, &app->view_dispatcher))
     {
         return NULL;
     }
@@ -202,6 +200,4 @@ static FlipStoreApp *flip_store_app_alloc()
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenu);
 
     return app;
-}
-
-#endif // FLIP_STORE_I_H
+}

+ 10 - 0
alloc/flip_store_alloc.h

@@ -0,0 +1,10 @@
+#ifndef FLIP_STORE_I_H
+#define FLIP_STORE_I_H
+
+#include <flip_store.h>
+#include <callback/flip_store_callback.h>
+
+// Function to allocate resources for the FlipStoreApp
+FlipStoreApp *flip_store_app_alloc();
+
+#endif // FLIP_STORE_I_H

+ 2 - 5
app.c

@@ -1,8 +1,5 @@
-#include <flip_store_e.h>
-#include <flip_store_storage.h>
-#include <flip_store_callback.h>
-#include <flip_store_i.h>
-#include <flip_store_free.h>
+#include <flip_store.h>
+#include <alloc/flip_store_alloc.h>
 
 // Entry point for the Hello World application
 int32_t main_flip_store(void *p)

+ 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.2",
+    fap_version="0.3",
 )

+ 104 - 112
flip_store_apps.h → apps/flip_store_apps.c

@@ -1,69 +1,75 @@
-#ifndef FLIP_STORE_APPS_H
-#define FLIP_STORE_APPS_H
+#include <apps/flip_store_apps.h>
 
-// Define maximum limits
-#define MAX_APP_NAME_LENGTH 32
-#define MAX_ID_LENGTH 32
-#define MAX_APP_COUNT 100
+FlipStoreAppInfo *flip_catalog = NULL;
 
-typedef struct
-{
-    char app_name[MAX_APP_NAME_LENGTH];
-    char app_id[MAX_APP_NAME_LENGTH];
-    char app_build_id[MAX_ID_LENGTH];
-} FlipStoreAppInfo;
-
-static FlipStoreAppInfo *flip_catalog = NULL;
+uint32_t app_selected_index = 0;
+bool flip_store_sent_request = false;
+bool flip_store_success = false;
+bool flip_store_saved_data = false;
+bool flip_store_saved_success = false;
+uint32_t flip_store_category_index = 0;
 
-static uint32_t app_selected_index = 0;
-static bool flip_store_sent_request = false;
-static bool flip_store_success = false;
-static bool flip_store_saved_data = false;
-static bool flip_store_saved_success = false;
-static uint32_t flip_store_category_index = 0;
-
-enum ObjectState
+FlipStoreAppInfo *flip_catalog_alloc()
 {
-    OBJECT_EXPECT_KEY,
-    OBJECT_EXPECT_COLON,
-    OBJECT_EXPECT_VALUE,
-    OBJECT_EXPECT_COMMA_OR_END
-};
-
-static void flip_catalog_free()
-{
-    if (flip_catalog)
+    FlipStoreAppInfo *app_catalog = (FlipStoreAppInfo *)malloc(MAX_APP_COUNT * sizeof(FlipStoreAppInfo));
+    if (!app_catalog)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for flip_catalog.");
+        return NULL;
+    }
+    for (int i = 0; i < MAX_APP_COUNT; i++)
     {
-        free(flip_catalog);
-        flip_catalog = NULL;
+        app_catalog[i].app_name = (char *)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
+        if (!app_catalog[i].app_name)
+        {
+            FURI_LOG_E(TAG, "Failed to allocate memory for app_name.");
+            return NULL;
+        }
+        app_catalog[i].app_id = (char *)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
+        if (!app_catalog[i].app_id)
+        {
+            FURI_LOG_E(TAG, "Failed to allocate memory for app_id.");
+            return NULL;
+        }
+        app_catalog[i].app_build_id = (char *)malloc(MAX_ID_LENGTH * sizeof(char));
+        if (!app_catalog[i].app_build_id)
+        {
+            FURI_LOG_E(TAG, "Failed to allocate memory for app_build_id.");
+            return NULL;
+        }
     }
+    return app_catalog;
 }
-
-static bool flip_catalog_alloc()
+void flip_catalog_free()
 {
     if (!flip_catalog)
     {
-        flip_catalog = (FlipStoreAppInfo *)malloc(MAX_APP_COUNT * sizeof(FlipStoreAppInfo));
+        return;
     }
-    if (!flip_catalog)
+    for (int i = 0; i < MAX_APP_COUNT; i++)
     {
-        FURI_LOG_E(TAG, "Failed to allocate memory for flip_catalog.");
-        return false;
+        if (flip_catalog[i].app_name)
+        {
+            free(flip_catalog[i].app_name);
+        }
+        if (flip_catalog[i].app_id)
+        {
+            free(flip_catalog[i].app_id);
+        }
+        if (flip_catalog[i].app_build_id)
+        {
+            free(flip_catalog[i].app_build_id);
+        }
     }
-    return true;
+    free(flip_catalog);
 }
 
 // Utility function to parse JSON incrementally from a file
-static bool flip_store_process_app_list(const char *file_path)
+bool flip_store_process_app_list()
 {
-    if (file_path == NULL)
-    {
-        FURI_LOG_E(TAG, "JSON file path is NULL.");
-        return false;
-    }
-
     // initialize the flip_catalog
-    if (!flip_catalog_alloc())
+    flip_catalog = flip_catalog_alloc();
+    if (!flip_catalog)
     {
         FURI_LOG_E(TAG, "Failed to allocate memory for flip_catalog.");
         return false;
@@ -113,7 +119,7 @@ static bool flip_store_process_app_list(const char *file_path)
         return false;
     }
 
-    if (!storage_file_open(_file, file_path, FSAM_READ, FSOM_OPEN_EXISTING))
+    if (!storage_file_open(_file, fhttp.file_path, FSAM_READ, FSOM_OPEN_EXISTING))
     {
         FURI_LOG_E(TAG, "Failed to open JSON file for reading.");
         storage_file_free(_file);
@@ -211,20 +217,17 @@ static bool flip_store_process_app_list(const char *file_path)
                         {
                             if (strcmp(current_key, "name") == 0)
                             {
-                                strncpy(flip_catalog[app_count].app_name, current_value, MAX_APP_NAME_LENGTH - 1);
-                                flip_catalog[app_count].app_name[MAX_APP_NAME_LENGTH - 1] = '\0';
+                                snprintf(flip_catalog[app_count].app_name, MAX_APP_NAME_LENGTH, "%.31s", current_value);
                                 found_name = true;
                             }
                             else if (strcmp(current_key, "id") == 0)
                             {
-                                strncpy(flip_catalog[app_count].app_id, current_value, MAX_APP_NAME_LENGTH - 1);
-                                flip_catalog[app_count].app_id[MAX_APP_NAME_LENGTH - 1] = '\0';
+                                snprintf(flip_catalog[app_count].app_id, MAX_ID_LENGTH, "%.31s", current_value);
                                 found_id = true;
                             }
                             else if (strcmp(current_key, "build_id") == 0)
                             {
-                                strncpy(flip_catalog[app_count].app_build_id, current_value, MAX_APP_NAME_LENGTH - 1);
-                                flip_catalog[app_count].app_build_id[MAX_ID_LENGTH - 1] = '\0';
+                                snprintf(flip_catalog[app_count].app_build_id, MAX_ID_LENGTH, "%.31s", current_value);
                                 found_build_id = true;
                             }
 
@@ -329,18 +332,14 @@ static bool flip_store_process_app_list(const char *file_path)
     storage_file_free(_file);
     furi_record_close(RECORD_STORAGE);
 
-    if (app_count == 0)
-    {
-        FURI_LOG_E(TAG, "No valid apps were parsed.");
-        return false;
-    }
-    return true;
+    return app_count > 0;
 }
 
-static 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(char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor)
 {
-    is_compile_app_request = true;
-    char url[164];
+    char url[128];
+    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);
     char *headers = jsmn("Content-Type", "application/octet-stream");
     bool sent_request = flipper_http_get_request_bytes(url, headers);
@@ -348,38 +347,28 @@ static bool flip_store_get_fap_file(char *build_id, uint8_t target, uint16_t api
     return sent_request;
 }
 
-static void flip_store_request_error(Canvas *canvas)
+void flip_store_request_error(Canvas *canvas)
 {
-    if (fhttp.received_data == NULL)
+    if (fhttp.last_response != NULL)
     {
-        if (fhttp.last_response != NULL)
+        if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != 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.");
-            }
+            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
+        else if (strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
         {
             canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
+            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.");
         }
@@ -387,12 +376,13 @@ static void flip_store_request_error(Canvas *canvas)
     else
     {
         canvas_clear(canvas);
-        canvas_draw_str(canvas, 0, 10, "Failed to receive data.");
+        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.");
     }
 }
 // function to handle the entire installation process "asynchronously"
-static bool flip_store_install_app(Canvas *canvas, char *category)
+bool flip_store_install_app(Canvas *canvas, char *category)
 {
     // create /apps/FlipStore directory if it doesn't exist
     char directory_path[128];
@@ -403,12 +393,9 @@ static bool flip_store_install_app(Canvas *canvas, char *category)
     storage_common_mkdir(storage, directory_path);
 
     // Adjusted to access flip_catalog as an array of structures
-    char *app_name = flip_catalog[app_selected_index].app_name;
-    char installing_text[128];
-    snprintf(installing_text, sizeof(installing_text), "Installing %s", app_name);
-    char bin_path[256];
-    snprintf(bin_path, sizeof(bin_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap", category, flip_catalog[app_selected_index].app_id);
-    strncpy(fhttp.file_path, bin_path, sizeof(fhttp.file_path) - 1);
+    char installing_text[64];
+    snprintf(installing_text, sizeof(installing_text), "Installing %s", flip_catalog[app_selected_index].app_name);
+    snprintf(fhttp.file_path, sizeof(fhttp.file_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap", category, flip_catalog[app_selected_index].app_id);
     canvas_draw_str(canvas, 0, 10, installing_text);
     canvas_draw_str(canvas, 0, 20, "Sending request..");
     uint8_t target = furi_hal_version_get_hw_target();
@@ -433,7 +420,7 @@ static bool flip_store_install_app(Canvas *canvas, char *category)
         furi_delay_ms(10);
     }
     // furi_timer_stop(fhttp.get_timeout_timer);
-    if (fhttp.state == ISSUE || fhttp.received_data == NULL)
+    if (fhttp.state == ISSUE)
     {
         flip_store_request_error(canvas);
         flip_store_success = false;
@@ -444,7 +431,7 @@ static bool flip_store_install_app(Canvas *canvas, char *category)
 }
 
 // process the app list and return view
-static int32_t flip_store_handle_app_list(FlipStoreApp *app, int32_t success_view, char *category, Submenu **submenu)
+int32_t flip_store_handle_app_list(FlipStoreApp *app, int32_t success_view, char *category, Submenu **submenu)
 {
     // reset the flip_catalog
     if (flip_catalog)
@@ -458,18 +445,27 @@ static int32_t flip_store_handle_app_list(FlipStoreApp *app, int32_t success_vie
         return FlipStoreViewPopup;
     }
     char url[128];
-    is_compile_app_request = false;
-    // append the category to the end of the url
+    snprintf(
+        fhttp.file_path,
+        sizeof(fhttp.file_path),
+        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/apps.txt");
+
+    fhttp.save_received_data = true;
+    fhttp.is_bytes_request = false;
     snprintf(url, sizeof(url), "https://www.flipsocial.net/api/flipper/apps/%s/extended/", category);
+    char *headers = jsmn("Content-Type", "application/json");
     // async call to the app list with timer
-    if (fhttp.state != INACTIVE && flipper_http_get_request_with_headers(url, jsmn("Content-Type", "application/json")))
+    if (fhttp.state != INACTIVE && flipper_http_get_request_with_headers(url, headers))
     {
         furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        free(headers);
         fhttp.state = RECEIVING;
     }
     else
     {
         FURI_LOG_E(TAG, "Failed to send the request");
+        free(headers);
+        fhttp.state = ISSUE;
         return FlipStoreViewPopup;
     }
     while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
@@ -478,11 +474,11 @@ static int32_t flip_store_handle_app_list(FlipStoreApp *app, int32_t success_vie
         furi_delay_ms(100);
     }
     furi_timer_stop(fhttp.get_timeout_timer);
-    if (fhttp.state == ISSUE || fhttp.received_data == NULL)
+    if (fhttp.state == ISSUE)
     {
-        if (fhttp.received_data == NULL)
+        FURI_LOG_E(TAG, "Failed to receive data");
+        if (fhttp.last_response == NULL)
         {
-            FURI_LOG_E(TAG, "Failed to receive data");
             if (fhttp.last_response != NULL)
             {
                 if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
@@ -506,7 +502,6 @@ static int32_t flip_store_handle_app_list(FlipStoreApp *app, int32_t success_vie
         }
         else
         {
-            FURI_LOG_E(TAG, "Failed to receive data");
             popup_set_text(app->popup, "Failed to received data.", 0, 10, AlignLeft, AlignTop);
             return FlipStoreViewPopup;
         }
@@ -514,8 +509,7 @@ static int32_t flip_store_handle_app_list(FlipStoreApp *app, int32_t success_vie
     else
     {
         // process the app list
-        const char *output_file_path = STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt";
-        if (flip_store_process_app_list(output_file_path))
+        if (flip_store_process_app_list())
         {
             submenu_reset(*submenu);
             // add each app name to submenu
@@ -535,6 +529,4 @@ static int32_t flip_store_handle_app_list(FlipStoreApp *app, int32_t success_vie
             return FlipStoreViewPopup;
         }
     }
-}
-
-#endif // FLIP_STORE_APPS_H
+}

+ 54 - 0
apps/flip_store_apps.h

@@ -0,0 +1,54 @@
+#ifndef FLIP_STORE_APPS_H
+#define FLIP_STORE_APPS_H
+
+#include <flip_store.h>
+#include <flip_storage/flip_store_storage.h>
+#include <callback/flip_store_callback.h>
+
+// Define maximum limits
+#define MAX_APP_NAME_LENGTH 32
+#define MAX_ID_LENGTH 32
+#define MAX_APP_COUNT 100
+
+typedef struct
+{
+    char *app_name;
+    char *app_id;
+    char *app_build_id;
+} FlipStoreAppInfo;
+
+extern FlipStoreAppInfo *flip_catalog;
+
+extern uint32_t app_selected_index;
+extern bool flip_store_sent_request;
+extern bool flip_store_success;
+extern bool flip_store_saved_data;
+extern bool flip_store_saved_success;
+extern uint32_t flip_store_category_index;
+
+enum ObjectState
+{
+    OBJECT_EXPECT_KEY,
+    OBJECT_EXPECT_COLON,
+    OBJECT_EXPECT_VALUE,
+    OBJECT_EXPECT_COMMA_OR_END
+};
+
+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_get_fap_file(char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor);
+
+void flip_store_request_error(Canvas *canvas);
+
+// function to handle the entire installation process "asynchronously"
+bool flip_store_install_app(Canvas *canvas, char *category);
+
+// process the app list and return view
+int32_t flip_store_handle_app_list(FlipStoreApp *app, int32_t success_view, char *category, Submenu **submenu);
+
+#endif // FLIP_STORE_APPS_H

BIN
assets/01-main-menu.png


BIN
assets/01-main.png


BIN
assets/02-catalog.png


+ 0 - 0
assets/02-list.png → assets/03-list.png


BIN
assets/04-app-folder.png


+ 0 - 0
assets/03-success.png → assets/04-success.png


+ 5 - 1
assets/CHANGELOG.md

@@ -1,5 +1,9 @@
+## v0.3
+- Edits by Willy-JL
+- Improved memory allocation
+- Stability Patch
+
 ## v0.2
-- Refactored using the Easy Flipper library.
 - Added categories: Users can now navigate through categories, and when FlipStore installs a selected app, it will download directly to the corresponding category's folder in the apps directory.
 - Improved memory allocation to prevent "Out of Memory" warnings
 - Updated installation messages

+ 6 - 7
assets/README.md

@@ -10,7 +10,7 @@ Download Flipper Zero apps directly to your Flipper Zero using WiFi. You no long
 - Install Official Firmware (coming soon)
 
 ## Installation
-1. Flash your WiFi Devboard: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
+1. Flash your WiFi Devboard: https://github.com/jblanked/FlipperHTTP
 2. Install the app.
 3. Enjoy :D
 
@@ -20,7 +20,8 @@ Download Flipper Zero apps directly to your Flipper Zero using WiFi. You no long
 - App Categories
 
 **v0.3**
-- Caching
+- Improved memory allocation
+- Stability Patch 2
 - App Catalog Patch (add in required functionalility)
 
 **v0.4**
@@ -46,9 +47,7 @@ Download Flipper Zero apps directly to your Flipper Zero using WiFi. You no long
 This is a big task, and I welcome all contributors, especially developers interested in animations and graphics. Fork the repository, create a pull request, and I will review your edits.
 
 ## Known Bugs
-1. When clicking the Catalog, I get an "out of memory" error.
-   - This has been addressed but may still occur. If it does, just restart the app.
+1. Clicking the catalog results in an "Out of Memory" error.
+   - This issue has been addressed, but it may still occur. If it does, restart the app.
 2. The app file is corrupted.
-   - It's likely there was an error parsing the data. Restart the app and wait until the green LED light turns off after downloading the app before exiting the view.
-3. The app is stuck on "receiving".
-   - Restart your Flipper Zero with your WiFi Devboard plugged in.
+   - This is likely due to an error parsing the data. Restart the app and wait until the green LED light turns off after downloading the app before exiting the view. If this happens more than three times, the current version of FlipStore may not be able to download that app successfully.

+ 13 - 21
flip_store_callback.h → callback/flip_store_callback.c

@@ -1,14 +1,7 @@
-#ifndef FLIP_STORE_CALLBACK_H
-#define FLIP_STORE_CALLBACK_H
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <stdbool.h>
-#include <flip_store_apps.h>
+#include <callback/flip_store_callback.h>
 
 // Callback for drawing the main screen
-static void flip_store_view_draw_callback_main(Canvas *canvas, void *model)
+void flip_store_view_draw_callback_main(Canvas *canvas, void *model)
 {
     UNUSED(model);
     canvas_set_font(canvas, FontSecondary);
@@ -62,7 +55,7 @@ static void flip_store_view_draw_callback_main(Canvas *canvas, void *model)
     }
 }
 
-static void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
+void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
 {
     UNUSED(model);
     canvas_clear(canvas);
@@ -77,7 +70,7 @@ static void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
     canvas_draw_str_aligned(canvas, 97, 54, AlignLeft, AlignTop, "Install");
 }
 
-static bool flip_store_input_callback(InputEvent *event, void *context)
+bool flip_store_input_callback(InputEvent *event, void *context)
 {
     FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
@@ -114,7 +107,7 @@ static bool flip_store_input_callback(InputEvent *event, void *context)
     return false;
 }
 
-static void flip_store_text_updated_ssid(void *context)
+void flip_store_text_updated_ssid(void *context)
 {
     FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
@@ -151,7 +144,7 @@ static void flip_store_text_updated_ssid(void *context)
     // switch to the settings view
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
 }
-static void flip_store_text_updated_pass(void *context)
+void flip_store_text_updated_pass(void *context)
 {
     FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
@@ -189,7 +182,7 @@ static void flip_store_text_updated_pass(void *context)
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
 }
 
-static uint32_t callback_to_submenu(void *context)
+uint32_t callback_to_submenu(void *context)
 {
     if (!context)
     {
@@ -200,7 +193,7 @@ static uint32_t callback_to_submenu(void *context)
     return FlipStoreViewSubmenu;
 }
 
-static uint32_t callback_to_app_list(void *context)
+uint32_t callback_to_app_list(void *context)
 {
     if (!context)
     {
@@ -212,10 +205,11 @@ static uint32_t callback_to_app_list(void *context)
     flip_store_success = false;
     flip_store_saved_data = false;
     flip_store_saved_success = false;
+    flip_catalog_free();
     return FlipStoreViewAppList;
 }
 
-static void settings_item_selected(void *context, uint32_t index)
+void settings_item_selected(void *context, uint32_t index)
 {
     FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
@@ -274,7 +268,7 @@ void popup_callback(void *context)
  * @param context The context - unused
  * @return next view id (VIEW_NONE to exit the app)
  */
-static uint32_t callback_exit_app(void *context)
+uint32_t callback_exit_app(void *context)
 {
     // Exit the application
     if (!context)
@@ -286,7 +280,7 @@ static uint32_t callback_exit_app(void *context)
     return VIEW_NONE; // Return VIEW_NONE to exit the app
 }
 
-static void callback_submenu_choices(void *context, uint32_t index)
+void callback_submenu_choices(void *context, uint32_t index)
 {
     FlipStoreApp *app = (FlipStoreApp *)context;
     if (!app)
@@ -388,6 +382,4 @@ static void callback_submenu_choices(void *context, uint32_t index)
         }
         break;
     }
-}
-
-#endif // FLIP_STORE_CALLBACK_H
+}

+ 40 - 0
callback/flip_store_callback.h

@@ -0,0 +1,40 @@
+#ifndef FLIP_STORE_CALLBACK_H
+#define FLIP_STORE_CALLBACK_H
+#include <flip_store.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <apps/flip_store_apps.h>
+#include <flip_storage/flip_store_storage.h>
+
+// Callback for drawing the main screen
+void flip_store_view_draw_callback_main(Canvas *canvas, void *model);
+
+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_app_list(void *context);
+
+void settings_item_selected(void *context, uint32_t index);
+
+void dialog_callback(DialogExResult result, void *context);
+
+void popup_callback(void *context);
+/**
+ * @brief Navigation callback for exiting the application
+ * @param context The context - unused
+ * @return next view id (VIEW_NONE to exit the app)
+ */
+uint32_t callback_exit_app(void *context);
+void callback_submenu_choices(void *context, uint32_t index);
+
+#endif // FLIP_STORE_CALLBACK_H

+ 2 - 25
easy_flipper.h → easy_flipper/easy_flipper.c

@@ -1,25 +1,4 @@
-#ifndef EASY_FLIPPER_H
-#define EASY_FLIPPER_H
-
-#include <malloc.h>
-#include <furi.h>
-#include <furi_hal.h>
-#include <gui/gui.h>
-#include <gui/view.h>
-#include <gui/modules/submenu.h>
-#include <gui/view_dispatcher.h>
-#include <gui/modules/menu.h>
-#include <gui/modules/submenu.h>
-#include <gui/modules/widget.h>
-#include <gui/modules/text_input.h>
-#include <gui/modules/text_box.h>
-#include <gui/modules/variable_item_list.h>
-#include <gui/modules/dialog_ex.h>
-#include <gui/modules/popup.h>
-#include <gui/modules/loading.h>
-#include <uart_text_input.h>
-
-#define EASY_TAG "EasyFlipper"
+#include <easy_flipper/easy_flipper.h>
 
 /**
  * @brief Navigation callback for exiting the application
@@ -588,6 +567,4 @@ bool easy_flipper_set_char_to_furi_string(FuriString **furi_string, char *buffer
     }
     furi_string_set_str(*furi_string, buffer);
     return true;
-}
-
-#endif // EASY_FLIPPER_H
+}

+ 262 - 0
easy_flipper/easy_flipper.h

@@ -0,0 +1,262 @@
+#ifndef EASY_FLIPPER_H
+#define EASY_FLIPPER_H
+
+#include <malloc.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/modules/submenu.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/menu.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/widget.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/text_box.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/modules/dialog_ex.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/loading.h>
+#include <text_input/uart_text_input.h>
+#include <stdio.h>
+#include <string.h>
+#include <jsmn/jsmn.h>
+
+#define EASY_TAG "EasyFlipper"
+
+/**
+ * @brief Navigation callback for exiting the application
+ * @param context The context - unused
+ * @return next view id (VIEW_NONE to exit the app)
+ */
+uint32_t easy_flipper_callback_exit_app(void *context);
+/**
+ * @brief Initialize a buffer
+ * @param buffer The buffer to initialize
+ * @param buffer_size The size of the buffer
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_buffer(char **buffer, uint32_t buffer_size);
+/**
+ * @brief Initialize a View object
+ * @param view The View object to initialize
+ * @param view_id The ID/Index of the view
+ * @param draw_callback The draw callback function (set to NULL if not needed)
+ * @param input_callback The input callback function (set to NULL if not needed)
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_view(
+    View **view,
+    int32_t view_id,
+    void draw_callback(Canvas *, void *),
+    bool input_callback(InputEvent *, void *),
+    uint32_t (*previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
+
+/**
+ * @brief Initialize a ViewDispatcher object
+ * @param view_dispatcher The ViewDispatcher object to initialize
+ * @param gui The GUI object
+ * @param context The context to pass to the event callback
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_view_dispatcher(ViewDispatcher **view_dispatcher, Gui *gui, void *context);
+
+/**
+ * @brief Initialize a Submenu object
+ * @note This does not set the items in the submenu
+ * @param submenu The Submenu object to initialize
+ * @param view_id The ID/Index of the view
+ * @param title The title/header of the submenu
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_submenu(
+    Submenu **submenu,
+    int32_t view_id,
+    char *title,
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher);
+
+/**
+ * @brief Initialize a Menu object
+ * @note This does not set the items in the menu
+ * @param menu The Menu object to initialize
+ * @param view_id The ID/Index of the view
+ * @param item_callback The item callback function
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_menu(
+    Menu **menu,
+    int32_t view_id,
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher);
+
+/**
+ * @brief Initialize a Widget object
+ * @param widget The Widget object to initialize
+ * @param view_id The ID/Index of the view
+ * @param text The text to display in the widget
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_widget(
+    Widget **widget,
+    int32_t view_id,
+    char *text,
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher);
+
+/**
+ * @brief Initialize a VariableItemList object
+ * @note This does not set the items in the VariableItemList
+ * @param variable_item_list The VariableItemList object to initialize
+ * @param view_id The ID/Index of the view
+ * @param enter_callback The enter callback function (can be set to NULL)
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @param context The context to pass to the enter callback (usually the app)
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_variable_item_list(
+    VariableItemList **variable_item_list,
+    int32_t view_id,
+    void (*enter_callback)(void *, uint32_t),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
+
+/**
+ * @brief Initialize a TextInput object
+ * @param text_input The TextInput object to initialize
+ * @param view_id The ID/Index of the view
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_text_input(
+    TextInput **text_input,
+    int32_t view_id,
+    char *header_text,
+    char *text_input_temp_buffer,
+    uint32_t text_input_buffer_size,
+    void (*result_callback)(void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
+
+/**
+ * @brief Initialize a UART_TextInput object
+ * @param uart_text_input The UART_TextInput object to initialize
+ * @param view_id The ID/Index of the view
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_uart_text_input(
+    UART_TextInput **uart_text_input,
+    int32_t view_id,
+    char *header_text,
+    char *uart_text_input_temp_buffer,
+    uint32_t uart_text_input_buffer_size,
+    void (*result_callback)(void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
+
+/**
+ * @brief Initialize a DialogEx object
+ * @param dialog_ex The DialogEx object to initialize
+ * @param view_id The ID/Index of the view
+ * @param header The header of the dialog
+ * @param header_x The x coordinate of the header
+ * @param header_y The y coordinate of the header
+ * @param text The text of the dialog
+ * @param text_x The x coordinate of the dialog
+ * @param text_y The y coordinate of the dialog
+ * @param left_button_text The text of the left button
+ * @param right_button_text The text of the right button
+ * @param center_button_text The text of the center button
+ * @param result_callback The result callback function
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @param context The context to pass to the result callback
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_dialog_ex(
+    DialogEx **dialog_ex,
+    int32_t view_id,
+    char *header,
+    uint16_t header_x,
+    uint16_t header_y,
+    char *text,
+    uint16_t text_x,
+    uint16_t text_y,
+    char *left_button_text,
+    char *right_button_text,
+    char *center_button_text,
+    void (*result_callback)(DialogExResult, void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
+
+/**
+ * @brief Initialize a Popup object
+ * @param popup The Popup object to initialize
+ * @param view_id The ID/Index of the view
+ * @param header The header of the dialog
+ * @param header_x The x coordinate of the header
+ * @param header_y The y coordinate of the header
+ * @param text The text of the dialog
+ * @param text_x The x coordinate of the dialog
+ * @param text_y The y coordinate of the dialog
+ * @param result_callback The result callback function
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @param context The context to pass to the result callback
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_popup(
+    Popup **popup,
+    int32_t view_id,
+    char *header,
+    uint16_t header_x,
+    uint16_t header_y,
+    char *text,
+    uint16_t text_x,
+    uint16_t text_y,
+    void (*result_callback)(void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
+
+/**
+ * @brief Initialize a Loading object
+ * @param loading The Loading object to initialize
+ * @param view_id The ID/Index of the view
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_loading(
+    Loading **loading,
+    int32_t view_id,
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher);
+
+/**
+ * @brief Set a char butter to a FuriString
+ * @param furi_string The FuriString object
+ * @param buffer The buffer to copy the string to
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_char_to_furi_string(FuriString **furi_string, char *buffer);
+
+#endif

+ 4 - 16
flip_store_storage.h → flip_storage/flip_store_storage.c

@@ -1,12 +1,6 @@
-#ifndef FLIP_STORE_STORAGE_H
-#define FLIP_STORE_STORAGE_H
+#include "flip_storage/flip_store_storage.h"
 
-#include <furi.h>
-#include <storage/storage.h>
-
-#define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/settings.bin"
-
-static void save_settings(
+void save_settings(
     const char *ssid,
     const char *password)
 {
@@ -49,7 +43,7 @@ static void save_settings(
     furi_record_close(RECORD_STORAGE);
 }
 
-static bool load_settings(
+bool load_settings(
     char *ssid,
     size_t ssid_size,
     char *password,
@@ -119,10 +113,6 @@ bool delete_app(const char *app_id, const char *app_category)
     return true;
 }
 
-#define BUFFER_SIZE 64
-#define MAX_KEY_LENGTH 32
-#define MAX_VALUE_LENGTH 64
-
 // Function to parse JSON incrementally from a file
 bool parse_json_incrementally(const char *file_path, const char *target_key, char *value_buffer, size_t value_buffer_size)
 {
@@ -312,6 +302,4 @@ cleanup:
         furi_record_close(RECORD_STORAGE);
     }
     return false;
-}
-
-#endif
+}

+ 29 - 0
flip_storage/flip_store_storage.h

@@ -0,0 +1,29 @@
+#ifndef FLIP_STORE_STORAGE_H
+#define FLIP_STORE_STORAGE_H
+
+#include <furi.h>
+#include <storage/storage.h>
+#include <flip_store.h>
+
+#define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/settings.bin"
+#define BUFFER_SIZE 64
+#define MAX_KEY_LENGTH 32
+#define MAX_VALUE_LENGTH 64
+
+void save_settings(
+    const char *ssid,
+    const char *password);
+
+bool load_settings(
+    char *ssid,
+    size_t ssid_size,
+    char *password,
+    size_t password_size);
+
+// future implenetation because we need the app category
+bool delete_app(const char *app_id, const char *app_category);
+
+// Function to parse JSON incrementally from a file
+bool parse_json_incrementally(const char *file_path, const char *target_key, char *value_buffer, size_t value_buffer_size);
+
+#endif

+ 17 - 8
flip_store_free.h → flip_store.c

@@ -1,8 +1,22 @@
-#ifndef FLIP_STORE_FREE_H
-#define FLIP_STORE_FREE_H
+#include <flip_store.h>
+
+// define the list of categories
+char *categories[] = {
+    "Bluetooth",
+    "Games",
+    "GPIO",
+    "Infrared",
+    "iButton",
+    "Media",
+    "NFC",
+    "RFID",
+    "Sub-GHz",
+    "Tools",
+    "USB",
+};
 
 // Function to free the resources used by FlipStoreApp
-static void flip_store_app_free(FlipStoreApp *app)
+void flip_store_app_free(FlipStoreApp *app)
 {
     if (!app)
     {
@@ -129,9 +143,6 @@ static void flip_store_app_free(FlipStoreApp *app)
         dialog_ex_free(app->dialog_delete);
     }
 
-    // Free the flip catalog
-    flip_catalog_free();
-
     // deinitalize flipper http
     flipper_http_deinit();
 
@@ -144,5 +155,3 @@ static void flip_store_app_free(FlipStoreApp *app)
     // free the app
     free(app);
 }
-
-#endif // FLIP_STORE_FREE_H

+ 7 - 35
flip_store_e.h → flip_store.h

@@ -1,8 +1,8 @@
 #ifndef FLIP_STORE_E_H
 #define FLIP_STORE_E_H
-#include <uart_text_input.h>
-#include <flipper_http.h>
-#include <easy_flipper.h>
+#include <text_input/uart_text_input.h>
+#include <flipper_http/flipper_http.h>
+#include <easy_flipper/easy_flipper.h>
 #include <furi.h>
 #include <furi_hal.h>
 #include <gui/gui.h>
@@ -11,23 +11,12 @@
 #include <gui/view_dispatcher.h>
 #include <notification/notification.h>
 #include <dialogs/dialogs.h>
-#include <jsmn.h>
+#include <jsmn/jsmn.h>
+#include <flip_store_icons.h>
 #define TAG "FlipStore"
 
 // define the list of categories
-char *categories[] = {
-    "Bluetooth",
-    "Games",
-    "GPIO",
-    "Infrared",
-    "iButton",
-    "Media",
-    "NFC",
-    "RFID",
-    "Sub-GHz",
-    "Tools",
-    "USB",
-};
+extern char *categories[];
 
 // Define the submenu items for our FlipStore application
 typedef enum
@@ -122,23 +111,6 @@ typedef struct
     uint32_t uart_text_input_buffer_size_pass; // Size of the text input buffer
 } FlipStoreApp;
 
-// include strndup (otherwise NULL pointer dereference)
-char *strndup(const char *s, size_t n)
-{
-    char *result;
-    size_t len = strlen(s);
-
-    if (n < len)
-        len = n;
-
-    result = (char *)malloc(len + 1);
-    if (!result)
-        return NULL;
-
-    result[len] = '\0';
-    return (char *)memcpy(result, s, len);
-}
-
-static void callback_submenu_choices(void *context, uint32_t index);
+void flip_store_app_free(FlipStoreApp *app);
 
 #endif // FLIP_STORE_E_H

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 424 - 290
flipper_http/flipper_http.c


+ 363 - 0
flipper_http/flipper_http.h

@@ -0,0 +1,363 @@
+// flipper_http.h
+#ifndef FLIPPER_HTTP_H
+#define FLIPPER_HTTP_H
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <furi_hal_gpio.h>
+#include <furi_hal_serial.h>
+#include <storage/storage.h>
+
+// STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
+
+#define HTTP_TAG "FlipStore"              // change this to your app name
+#define http_tag "flip_store"             // change this to your app id
+#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_LINE_BUFFER_SIZE 8192          // UART RX line buffer size (increase for large responses)
+#define MAX_FILE_SHOW 8192                // Maximum data from file to show
+#define FILE_BUFFER_SIZE 512              // File buffer size
+
+// Forward declaration for callback
+typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
+
+// State variable to track the UART state
+typedef enum
+{
+    INACTIVE,  // Inactive state
+    IDLE,      // Default state
+    RECEIVING, // Receiving data
+    SENDING,   // Sending data
+    ISSUE,     // Issue with connection
+} SerialState;
+
+// Event Flags for UART Worker Thread
+typedef enum
+{
+    WorkerEvtStop = (1 << 0),
+    WorkerEvtRxDone = (1 << 1),
+} WorkerEvtFlags;
+
+// FlipperHTTP Structure
+typedef struct
+{
+    FuriStreamBuffer *flipper_http_stream;  // Stream buffer for UART communication
+    FuriHalSerialHandle *serial_handle;     // Serial handle for UART communication
+    FuriThread *rx_thread;                  // Worker thread for UART
+    FuriThreadId rx_thread_id;              // Worker thread ID
+    FlipperHTTP_Callback handle_rx_line_cb; // Callback for received lines
+    void *callback_context;                 // Context for the callback
+    SerialState state;                      // State of the UART
+
+    // variable to store the last received data from the UART
+    char *last_response;
+    char file_path[256]; // Path to save the received data
+
+    // Timer-related members
+    FuriTimer *get_timeout_timer; // Timer for HTTP request timeout
+
+    bool started_receiving_get; // Indicates if a GET request has started
+    bool just_started_get;      // Indicates if GET data reception has just started
+
+    bool started_receiving_post; // Indicates if a POST request has started
+    bool just_started_post;      // Indicates if POST data reception has just started
+
+    bool started_receiving_put; // Indicates if a PUT request has started
+    bool just_started_put;      // Indicates if PUT data reception has just started
+
+    bool started_receiving_delete; // Indicates if a DELETE request has started
+    bool just_started_delete;      // Indicates if DELETE data reception has just started
+
+    // Buffer to hold the raw bytes received from the UART
+    uint8_t *received_bytes;
+    size_t received_bytes_len; // Length of the received bytes
+    bool is_bytes_request;     // Flag to indicate if the request is for bytes
+    bool save_bytes;           // Flag to save the received data to a file
+    bool save_received_data;   // Flag to save the received data to a file
+} FlipperHTTP;
+
+extern FlipperHTTP fhttp;
+// Global static array for the line buffer
+extern char rx_line_buffer[RX_LINE_BUFFER_SIZE];
+extern uint8_t file_buffer[FILE_BUFFER_SIZE];
+
+// fhttp.last_response holds the last received data from the UART
+
+// Function to append received data to file
+// make sure to initialize the file path before calling this function
+bool flipper_http_append_to_file(
+    const void *data,
+    size_t data_size,
+    bool start_new_file,
+    char *file_path);
+
+FuriString *flipper_http_load_from_file(char *file_path);
+
+// UART worker thread
+/**
+ * @brief      Worker thread to handle UART data asynchronously.
+ * @return     0
+ * @param      context   The context to pass to the callback.
+ * @note       This function will handle received data asynchronously via the callback.
+ */
+// UART worker thread
+int32_t flipper_http_worker(void *context);
+
+// Timer callback function
+/**
+ * @brief      Callback function for the GET timeout timer.
+ * @return     0
+ * @param      context   The context to pass to the callback.
+ * @note       This function will be called when the GET request times out.
+ */
+void get_timeout_timer_callback(void *context);
+
+// UART RX Handler Callback (Interrupt Context)
+/**
+ * @brief      A private callback function to handle received data asynchronously.
+ * @return     void
+ * @param      handle    The UART handle.
+ * @param      event     The event type.
+ * @param      context   The context to pass to the callback.
+ * @note       This function will handle received data asynchronously via the callback.
+ */
+void _flipper_http_rx_callback(
+    FuriHalSerialHandle *handle,
+    FuriHalSerialRxEvent event,
+    void *context);
+
+// 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.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_init(FlipperHTTP_Callback callback, void *context);
+
+// Deinitialize UART
+/**
+ * @brief      Deinitialize UART.
+ * @return     void
+ * @note       This function will stop the asynchronous RX, release the serial handle, and free the resources.
+ */
+void flipper_http_deinit();
+
+// 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      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);
+
+// 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.
+ * @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();
+
+// Function to list available commands
+/**
+ * @brief      Send a command to list available commands.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_list_commands();
+
+// Function to turn on the LED
+/**
+ * @brief      Allow the LED to display while processing.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_led_on();
+
+// Function to turn off the LED
+/**
+ * @brief      Disable the LED from displaying while processing.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_led_off();
+
+// Function to parse JSON data
+/**
+ * @brief      Parse JSON data.
+ * @return     true if the JSON data was parsed successfully, false otherwise.
+ * @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);
+
+// Function to parse JSON array data
+/**
+ * @brief      Parse JSON array data.
+ * @return     true if the JSON array data was parsed successfully, false otherwise.
+ * @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);
+
+// Function to scan for WiFi networks
+/**
+ * @brief      Send a command to scan for WiFi networks.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_scan_wifi();
+
+// 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.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_save_wifi(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.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_ip_address();
+
+// 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.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_ip_wifi();
+
+// 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.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_disconnect_wifi();
+
+// 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.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_connect_wifi();
+
+// 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      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);
+
+// 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      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);
+
+// 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      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);
+
+// 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      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(
+    const char *url,
+    const char *headers,
+    const char *payload);
+
+// Function to send a POST request with headers and return bytes
+/**
+ * @brief      Send a POST request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @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);
+
+// 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      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(
+    const char *url,
+    const char *headers,
+    const char *payload);
+
+// Function to send a DELETE request with headers
+/**
+ * @brief      Send a DELETE request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @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(
+    const char *url,
+    const char *headers,
+    const char *payload);
+
+// Function to handle received data asynchronously
+/**
+ * @brief      Callback function to handle received data asynchronously.
+ * @return     void
+ * @param      line     The received line.
+ * @param      context  The context passed to the callback.
+ * @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);
+
+// Function to trim leading and trailing spaces and newlines from a constant string
+char *trim(const char *str);
+/**
+ * @brief Process requests and parse JSON data asynchronously
+ * @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));
+
+#endif // FLIPPER_HTTP_H

+ 0 - 863
jsmn.h

@@ -1,863 +0,0 @@
-/*
- * 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:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#ifndef JSMN_H
-#define JSMN_H
-
-#include <stddef.h>
-
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-
-#ifdef JSMN_STATIC
-#define JSMN_API static
-#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 jsmntok
-  {
-    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 jsmn_parser
-  {
-    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
-   */
-  JSMN_API void jsmn_init(jsmn_parser *parser);
-
-  /**
-   * Run JSON parser. It parses a JSON data string into and array of tokens, each
-   * describing
-   * a single JSON object.
-   */
-  JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
-                          jsmntok_t *tokens, const unsigned int num_tokens);
-
-#ifndef JSMN_HEADER
-  /**
-   * 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)
-  {
-    jsmntok_t *tok;
-    if (parser->toknext >= num_tokens)
-    {
-      return NULL;
-    }
-    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.
-   */
-  static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
-                                  const size_t len, jsmntok_t *tokens,
-                                  const size_t num_tokens)
-  {
-    jsmntok_t *token;
-    int start;
-
-    start = parser->pos;
-
-    for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
-    {
-      switch (js[parser->pos])
-      {
-#ifndef JSMN_STRICT
-      /* In strict mode primitive must be followed by "," or "}" or "]" */
-      case ':':
-#endif
-      case '\t':
-      case '\r':
-      case '\n':
-      case ' ':
-      case ',':
-      case ']':
-      case '}':
-        goto found;
-      default:
-        /* to quiet a warning from gcc*/
-        break;
-      }
-      if (js[parser->pos] < 32 || js[parser->pos] >= 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;
-    }
-    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.
-   */
-  static int jsmn_parse_string(jsmn_parser *parser, const char *js,
-                               const size_t len, jsmntok_t *tokens,
-                               const size_t num_tokens)
-  {
-    jsmntok_t *token;
-
-    int start = parser->pos;
-
-    /* Skip starting quote */
-    parser->pos++;
-
-    for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
-    {
-      char c = js[parser->pos];
-
-      /* Quote: end of string */
-      if (c == '\"')
-      {
-        if (tokens == NULL)
-        {
-          return 0;
-        }
-        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;
-      }
-
-      /* Backslash: Quoted symbol expected */
-      if (c == '\\' && parser->pos + 1 < len)
-      {
-        int i;
-        parser->pos++;
-        switch (js[parser->pos])
-        {
-        /* Allowed escaped symbols */
-        case '\"':
-        case '/':
-        case '\\':
-        case 'b':
-        case 'f':
-        case 'r':
-        case 'n':
-        case 't':
-          break;
-        /* Allows escaped symbol \uXXXX */
-        case 'u':
-          parser->pos++;
-          for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
-               i++)
-          {
-            /* If it isn't a hex character we have an error */
-            if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
-                  (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
-                  (js[parser->pos] >= 97 && js[parser->pos] <= 102)))
-            { /* a-f */
-              parser->pos = start;
-              return JSMN_ERROR_INVAL;
-            }
-            parser->pos++;
-          }
-          parser->pos--;
-          break;
-        /* Unexpected symbol */
-        default:
-          parser->pos = start;
-          return JSMN_ERROR_INVAL;
-        }
-      }
-    }
-    parser->pos = start;
-    return JSMN_ERROR_PART;
-  }
-
-  /**
-   * Parse JSON string and fill tokens.
-   */
-  JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
-                          jsmntok_t *tokens, const unsigned int num_tokens)
-  {
-    int r;
-    int i;
-    jsmntok_t *token;
-    int count = parser->toknext;
-
-    for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
-    {
-      char c;
-      jsmntype_t type;
-
-      c = js[parser->pos];
-      switch (c)
-      {
-      case '{':
-      case '[':
-        count++;
-        if (tokens == NULL)
-        {
-          break;
-        }
-        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
-          /* In strict mode an object or array can't become a key */
-          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;
-        }
-        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
-        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;
-          }
-        }
-        /* Error if unmatched closing bracket */
-        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, len, 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 ' ':
-        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
-      /* In strict mode primitives are: numbers and booleans */
-      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':
-        /* And they must not be keys of the object */
-        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
-      /* In non-strict mode every unquoted value is a primitive */
-      default:
-#endif
-        r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
-        if (r < 0)
-        {
-          return r;
-        }
-        count++;
-        if (parser->toksuper != -1 && tokens != NULL)
-        {
-          tokens[parser->toksuper].size++;
-        }
-        break;
-
-#ifdef JSMN_STRICT
-      /* Unexpected char in strict mode */
-      default:
-        return JSMN_ERROR_INVAL;
-#endif
-      }
-    }
-
-    if (tokens != NULL)
-    {
-      for (i = parser->toknext - 1; i >= 0; i--)
-      {
-        /* Unmatched opened object or array */
-        if (tokens[i].start != -1 && tokens[i].end == -1)
-        {
-          return JSMN_ERROR_PART;
-        }
-      }
-    }
-
-    return count;
-  }
-
-  /**
-   * Creates a new parser based over a given buffer with an array of tokens
-   * available.
-   */
-  JSMN_API void jsmn_init(jsmn_parser *parser)
-  {
-    parser->pos = 0;
-    parser->toknext = 0;
-    parser->toksuper = -1;
-  }
-
-#endif /* JSMN_HEADER */
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* JSMN_H */
-
-#ifndef JB_JSMN_EDIT
-#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)
-{
-  int length = strlen(key) + strlen(value) + 8;         // Calculate required length
-  char *result = (char *)malloc(length * sizeof(char)); // Allocate memory
-  if (result == NULL)
-  {
-    return NULL; // Handle memory allocation failure
-  }
-  snprintf(result, length, "{\"%s\":\"%s\"}", key, value);
-  return result; // Caller is responsible for freeing this memory
-}
-
-// Helper function to compare JSON keys
-int jsoneq(const char *json, jsmntok_t *tok, const char *s)
-{
-  if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
-      strncmp(json + tok->start, s, tok->end - tok->start) == 0)
-  {
-    return 0;
-  }
-  return -1;
-}
-
-// return the value of the key in the JSON data
-char *get_json_value(char *key, char *json_data, uint32_t max_tokens)
-{
-  // Parse the JSON feed
-  if (json_data != NULL)
-  {
-    jsmn_parser parser;
-    jsmn_init(&parser);
-
-    // Allocate tokens array on the heap
-    jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
-    if (tokens == NULL)
-    {
-      FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
-      return NULL;
-    }
-
-    int ret = jsmn_parse(&parser, json_data, strlen(json_data), tokens, max_tokens);
-    if (ret < 0)
-    {
-      // Handle parsing errors
-      FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
-      free(tokens);
-      return NULL;
-    }
-
-    // Ensure that the root element is an object
-    if (ret < 1 || tokens[0].type != JSMN_OBJECT)
-    {
-      FURI_LOG_E("JSMM.H", "Root element is not an object.");
-      free(tokens);
-      return NULL;
-    }
-
-    // Loop through the tokens to find the key
-    for (int i = 1; i < ret; i++)
-    {
-      if (jsoneq(json_data, &tokens[i], key) == 0)
-      {
-        // We found the key. Now, return the associated value.
-        int length = tokens[i + 1].end - tokens[i + 1].start;
-        char *value = malloc(length + 1);
-        if (value == NULL)
-        {
-          FURI_LOG_E("JSMM.H", "Failed to allocate memory for value.");
-          free(tokens);
-          return NULL;
-        }
-        strncpy(value, json_data + tokens[i + 1].start, length);
-        value[length] = '\0'; // Null-terminate the string
-
-        free(tokens); // Free the token array
-        return value; // Return the extracted value
-      }
-    }
-
-    // Free the token array if key was not found
-    free(tokens);
-  }
-  else
-  {
-    FURI_LOG_E("JSMM.H", "JSON data is NULL");
-  }
-  FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
-  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)
-{
-  // Retrieve the array string for the given key
-  char *array_str = get_json_value(key, json_data, max_tokens);
-  if (array_str == NULL)
-  {
-    FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
-    return NULL;
-  }
-
-  // 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)
-  {
-    FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
-    free(array_str);
-    return NULL;
-  }
-
-  // Parse the JSON array
-  int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
-  if (ret < 0)
-  {
-    FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
-    free(tokens);
-    free(array_str);
-    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);
-    free(tokens);
-    free(array_str);
-    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
-  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
-    {
-      // 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.");
-      free(tokens);
-      free(array_str);
-      return NULL;
-    }
-  }
-
-  // Extract the array element
-  jsmntok_t element = tokens[current_token];
-  int length = element.end - element.start;
-  char *value = malloc(length + 1);
-  if (value == NULL)
-  {
-    FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
-    free(tokens);
-    free(array_str);
-    return NULL;
-  }
-
-  // Copy the element value to a new string
-  strncpy(value, array_str + element.start, length);
-  value[length] = '\0'; // Null-terminate the string
-
-  // Clean up
-  free(tokens);
-  free(array_str);
-
-  return value;
-}
-
-// 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)
-{
-  // Retrieve the array string for the given key
-  char *array_str = get_json_value(key, json_data, max_tokens);
-  if (array_str == NULL)
-  {
-    FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
-    return NULL;
-  }
-
-  // Initialize the JSON parser
-  jsmn_parser parser;
-  jsmn_init(&parser);
-
-  // Allocate memory for JSON tokens
-  jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap
-  if (tokens == NULL)
-  {
-    FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
-    free(array_str);
-    return NULL;
-  }
-
-  // Parse the JSON array
-  int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
-  if (ret < 0)
-  {
-    FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
-    free(tokens);
-    free(array_str);
-    return NULL;
-  }
-
-  // Ensure the root element is an array
-  if (tokens[0].type != JSMN_ARRAY)
-  {
-    FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key);
-    free(tokens);
-    free(array_str);
-    return NULL;
-  }
-
-  // Allocate memory for the array of values (maximum possible)
-  int array_size = tokens[0].size;
-  char **values = malloc(array_size * sizeof(char *));
-  if (values == NULL)
-  {
-    FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
-    free(tokens);
-    free(array_str);
-    return NULL;
-  }
-
-  int actual_num_values = 0;
-
-  // Traverse the array and extract all object values
-  int current_token = 1; // Start after the array token
-  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];
-
-    if (element.type != JSMN_OBJECT)
-    {
-      FURI_LOG_E("JSMM.H", "Array element %d is not an object, skipping.", i);
-      // Skip this element
-      current_token += 1;
-      continue;
-    }
-
-    int length = element.end - element.start;
-
-    // Allocate a new string for the value and copy the data
-    char *value = malloc(length + 1);
-    if (value == NULL)
-    {
-      FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
-      for (int j = 0; j < actual_num_values; j++)
-      {
-        free(values[j]);
-      }
-      free(values);
-      free(tokens);
-      free(array_str);
-      return NULL;
-    }
-
-    strncpy(value, array_str + element.start, length);
-    value[length] = '\0'; // Null-terminate the string
-
-    values[actual_num_values] = value;
-    actual_num_values++;
-
-    // Skip all tokens related to this object to avoid misparsing
-    current_token += 1 + (2 * element.size); // Each key-value pair consumes two tokens
-  }
-
-  *num_values = actual_num_values;
-
-  // Reallocate the values array to actual_num_values if necessary
-  if (actual_num_values < array_size)
-  {
-    char **reduced_values = realloc(values, actual_num_values * sizeof(char *));
-    if (reduced_values != NULL)
-    {
-      values = reduced_values;
-    }
-
-    // Free the remaining values
-    for (int i = actual_num_values; i < array_size; i++)
-    {
-      free(values[i]);
-    }
-  }
-
-  // Clean up
-  free(tokens);
-  free(array_str);
-  return values;
-}
-
-#endif /* JB_JSMN_EDIT */

+ 747 - 0
jsmn/jsmn.c

@@ -0,0 +1,747 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * [License text continues...]
+ */
+
+#include <jsmn/jsmn.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * 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)
+{
+    jsmntok_t *tok;
+
+    if (parser->toknext >= num_tokens)
+    {
+        return NULL;
+    }
+    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.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+                                const size_t len, jsmntok_t *tokens,
+                                const size_t num_tokens)
+{
+    jsmntok_t *token;
+    int start;
+
+    start = parser->pos;
+
+    for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+    {
+        switch (js[parser->pos])
+        {
+#ifndef JSMN_STRICT
+        /* In strict mode primitive must be followed by "," or "}" or "]" */
+        case ':':
+#endif
+        case '\t':
+        case '\r':
+        case '\n':
+        case ' ':
+        case ',':
+        case ']':
+        case '}':
+            goto found;
+        default:
+            /* to quiet a warning from gcc*/
+            break;
+        }
+        if (js[parser->pos] < 32 || js[parser->pos] >= 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;
+    }
+    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.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+                             const size_t len, jsmntok_t *tokens,
+                             const size_t num_tokens)
+{
+    jsmntok_t *token;
+
+    int start = parser->pos;
+
+    /* Skip starting quote */
+    parser->pos++;
+
+    for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+    {
+        char c = js[parser->pos];
+
+        /* Quote: end of string */
+        if (c == '\"')
+        {
+            if (tokens == NULL)
+            {
+                return 0;
+            }
+            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;
+        }
+
+        /* Backslash: Quoted symbol expected */
+        if (c == '\\' && parser->pos + 1 < len)
+        {
+            int i;
+            parser->pos++;
+            switch (js[parser->pos])
+            {
+            /* Allowed escaped symbols */
+            case '\"':
+            case '/':
+            case '\\':
+            case 'b':
+            case 'f':
+            case 'r':
+            case 'n':
+            case 't':
+                break;
+            /* Allows escaped symbol \uXXXX */
+            case 'u':
+                parser->pos++;
+                for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++)
+                {
+                    /* If it isn't a hex character we have an error */
+                    if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
+                          (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
+                          (js[parser->pos] >= 97 && js[parser->pos] <= 102)))
+                    { /* a-f */
+                        parser->pos = start;
+                        return JSMN_ERROR_INVAL;
+                    }
+                    parser->pos++;
+                }
+                parser->pos--;
+                break;
+            /* Unexpected symbol */
+            default:
+                parser->pos = start;
+                return JSMN_ERROR_INVAL;
+            }
+        }
+    }
+    parser->pos = start;
+    return JSMN_ERROR_PART;
+}
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+void jsmn_init(jsmn_parser *parser)
+{
+    parser->pos = 0;
+    parser->toknext = 0;
+    parser->toksuper = -1;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+               jsmntok_t *tokens, const unsigned int num_tokens)
+{
+    int r;
+    int i;
+    jsmntok_t *token;
+    int count = parser->toknext;
+
+    for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+    {
+        char c;
+        jsmntype_t type;
+
+        c = js[parser->pos];
+        switch (c)
+        {
+        case '{':
+        case '[':
+            count++;
+            if (tokens == NULL)
+            {
+                break;
+            }
+            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
+                /* In strict mode an object or array can't become a key */
+                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;
+            }
+            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
+            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;
+                }
+            }
+            /* Error if unmatched closing bracket */
+            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, len, 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 ' ':
+            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
+        /* In strict mode primitives are: numbers and booleans */
+        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':
+            /* And they must not be keys of the object */
+            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
+        /* In non-strict mode every unquoted value is a primitive */
+        default:
+#endif
+            r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+            if (r < 0)
+            {
+                return r;
+            }
+            count++;
+            if (parser->toksuper != -1 && tokens != NULL)
+            {
+                tokens[parser->toksuper].size++;
+            }
+            break;
+
+#ifdef JSMN_STRICT
+        /* Unexpected char in strict mode */
+        default:
+            return JSMN_ERROR_INVAL;
+#endif
+        }
+    }
+
+    if (tokens != NULL)
+    {
+        for (i = parser->toknext - 1; i >= 0; i--)
+        {
+            /* Unmatched opened object or array */
+            if (tokens[i].start != -1 && tokens[i].end == -1)
+            {
+                return JSMN_ERROR_PART;
+            }
+        }
+    }
+
+    return count;
+}
+
+// Helper function to create a JSON object
+char *jsmn(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
+    if (result == NULL)
+    {
+        return NULL; // Handle memory allocation failure
+    }
+    snprintf(result, length, "{\"%s\":\"%s\"}", key, value);
+    return result; // Caller is responsible for freeing this memory
+}
+
+// Helper function to compare JSON keys
+int jsoneq(const char *json, jsmntok_t *tok, const char *s)
+{
+    if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
+        strncmp(json + tok->start, s, tok->end - tok->start) == 0)
+    {
+        return 0;
+    }
+    return -1;
+}
+
+// Return the value of the key in the JSON data
+char *get_json_value(char *key, char *json_data, uint32_t max_tokens)
+{
+    // Parse the JSON feed
+    if (json_data != NULL)
+    {
+        jsmn_parser parser;
+        jsmn_init(&parser);
+
+        // Allocate tokens array on the heap
+        jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
+        if (tokens == NULL)
+        {
+            FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+            return NULL;
+        }
+
+        int ret = jsmn_parse(&parser, json_data, strlen(json_data), tokens, max_tokens);
+        if (ret < 0)
+        {
+            // Handle parsing errors
+            FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
+            free(tokens);
+            return NULL;
+        }
+
+        // Ensure that the root element is an object
+        if (ret < 1 || tokens[0].type != JSMN_OBJECT)
+        {
+            FURI_LOG_E("JSMM.H", "Root element is not an object.");
+            free(tokens);
+            return NULL;
+        }
+
+        // Loop through the tokens to find the key
+        for (int i = 1; i < ret; i++)
+        {
+            if (jsoneq(json_data, &tokens[i], key) == 0)
+            {
+                // We found the key. Now, return the associated value.
+                int length = tokens[i + 1].end - tokens[i + 1].start;
+                char *value = malloc(length + 1);
+                if (value == NULL)
+                {
+                    FURI_LOG_E("JSMM.H", "Failed to allocate memory for value.");
+                    free(tokens);
+                    return NULL;
+                }
+                strncpy(value, json_data + tokens[i + 1].start, length);
+                value[length] = '\0'; // Null-terminate the string
+
+                free(tokens); // Free the token array
+                return value; // Return the extracted value
+            }
+        }
+
+        // Free the token array if key was not found
+        free(tokens);
+    }
+    else
+    {
+        FURI_LOG_E("JSMM.H", "JSON data is NULL");
+    }
+    FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
+    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)
+{
+    // Retrieve the array string for the given key
+    char *array_str = get_json_value(key, json_data, max_tokens);
+    if (array_str == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
+        return NULL;
+    }
+
+    // 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)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        free(array_str);
+        return NULL;
+    }
+
+    // Parse the JSON array
+    int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
+    if (ret < 0)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+        free(tokens);
+        free(array_str);
+        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);
+        free(tokens);
+        free(array_str);
+        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
+    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
+        {
+            // 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.");
+            free(tokens);
+            free(array_str);
+            return NULL;
+        }
+    }
+
+    // Extract the array element
+    jsmntok_t element = tokens[current_token];
+    int length = element.end - element.start;
+    char *value = malloc(length + 1);
+    if (value == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
+        free(tokens);
+        free(array_str);
+        return NULL;
+    }
+
+    // Copy the element value to a new string
+    strncpy(value, array_str + element.start, length);
+    value[length] = '\0'; // Null-terminate the string
+
+    // Clean up
+    free(tokens);
+    free(array_str);
+
+    return value;
+}
+
+// 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)
+{
+    // Retrieve the array string for the given key
+    char *array_str = get_json_value(key, json_data, max_tokens);
+    if (array_str == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
+        return NULL;
+    }
+
+    // Initialize the JSON parser
+    jsmn_parser parser;
+    jsmn_init(&parser);
+
+    // Allocate memory for JSON tokens
+    jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap
+    if (tokens == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        free(array_str);
+        return NULL;
+    }
+
+    // Parse the JSON array
+    int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
+    if (ret < 0)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+        free(tokens);
+        free(array_str);
+        return NULL;
+    }
+
+    // Ensure the root element is an array
+    if (tokens[0].type != JSMN_ARRAY)
+    {
+        FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key);
+        free(tokens);
+        free(array_str);
+        return NULL;
+    }
+
+    // Allocate memory for the array of values (maximum possible)
+    int array_size = tokens[0].size;
+    char **values = malloc(array_size * sizeof(char *));
+    if (values == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
+        free(tokens);
+        free(array_str);
+        return NULL;
+    }
+
+    int actual_num_values = 0;
+
+    // Traverse the array and extract all object values
+    int current_token = 1; // Start after the array token
+    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];
+
+        if (element.type != JSMN_OBJECT)
+        {
+            FURI_LOG_E("JSMM.H", "Array element %d is not an object, skipping.", i);
+            // Skip this element
+            current_token += 1;
+            continue;
+        }
+
+        int length = element.end - element.start;
+
+        // Allocate a new string for the value and copy the data
+        char *value = malloc(length + 1);
+        if (value == NULL)
+        {
+            FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
+            for (int j = 0; j < actual_num_values; j++)
+            {
+                free(values[j]);
+            }
+            free(values);
+            free(tokens);
+            free(array_str);
+            return NULL;
+        }
+
+        strncpy(value, array_str + element.start, length);
+        value[length] = '\0'; // Null-terminate the string
+
+        values[actual_num_values] = value;
+        actual_num_values++;
+
+        // Skip all tokens related to this object to avoid misparsing
+        current_token += 1 + (2 * element.size); // Each key-value pair consumes two tokens
+    }
+
+    *num_values = actual_num_values;
+
+    // Reallocate the values array to actual_num_values if necessary
+    if (actual_num_values < array_size)
+    {
+        char **reduced_values = realloc(values, actual_num_values * sizeof(char *));
+        if (reduced_values != NULL)
+        {
+            values = reduced_values;
+        }
+
+        // Free the remaining values
+        for (int i = actual_num_values; i < array_size; i++)
+        {
+            free(values[i]);
+        }
+    }
+
+    // Clean up
+    free(tokens);
+    free(array_str);
+    return values;
+}

+ 132 - 0
jsmn/jsmn.h

@@ -0,0 +1,132 @@
+/*
+ * 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_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#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
+     */
+    JSMN_API void jsmn_init(jsmn_parser *parser);
+
+    /**
+     * Run JSON parser. It parses a JSON data string into and array of tokens, each
+     * describing a single JSON object.
+     */
+    JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                            jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/* Implementation has been moved to jsmn.c */
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */
+
+/* Custom Helper Functions */
+#ifndef JB_JSMN_EDIT
+#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);
+// 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);
+
+// Revised get_json_array_value function
+char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t max_tokens);
+
+// 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);
+#endif /* JB_JSMN_EDIT */

+ 1 - 20
uart_text_input.h → text_input/uart_text_input.c

@@ -1,26 +1,9 @@
 // from https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/uart_terminal/uart_text_input.c
 // all credits to xMasterX for the code
-#ifndef UART_TEXT_INPUT_H
-#define UART_TEXT_INPUT_H
-
+#include "uart_text_input.h"
 #include <gui/elements.h>
 #include "flip_store_icons.h"
 #include <furi.h>
-#include <furi.h>
-#include <furi_hal.h>
-#include <gui/gui.h>
-#include <gui/view.h>
-#include <core/common_defines.h>
-
-/** Text input anonymous structure */
-typedef struct UART_TextInput UART_TextInput;
-typedef void (*UART_TextInputCallback)(void *context);
-typedef bool (*UART_TextInputValidatorCallback)(const char *text, FuriString *error, void *context);
-
-UART_TextInputValidatorCallback
-uart_text_input_get_validator_callback(UART_TextInput *uart_text_input);
-
-void uart_text_input_reset(UART_TextInput *uart_text_input);
 
 struct UART_TextInput
 {
@@ -799,5 +782,3 @@ void uart_text_input_set_header_text(UART_TextInput *uart_text_input, const char
     with_view_model(
         uart_text_input->view, UART_TextInputModel * model, { model->header = text; }, true);
 }
-
-#endif // UART_TEXT_INPUT_H

+ 83 - 0
text_input/uart_text_input.h

@@ -0,0 +1,83 @@
+#pragma once
+// from https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/uart_terminal/uart_text_input.c
+// all credits to xMasterX for the code
+#include <gui/view.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+    /** Text input anonymous structure */
+    typedef struct UART_TextInput UART_TextInput;
+    typedef void (*UART_TextInputCallback)(void *context);
+    typedef bool (*UART_TextInputValidatorCallback)(const char *text, FuriString *error, void *context);
+
+    /** Allocate and initialize text input
+     *
+     * This text input is used to enter string
+     *
+     * @return     UART_TextInput instance
+     */
+    UART_TextInput *uart_text_input_alloc();
+
+    /** Deinitialize and free text input
+     *
+     * @param      uart_text_input  UART_TextInput instance
+     */
+    void uart_text_input_free(UART_TextInput *uart_text_input);
+
+    /** Clean text input view Note: this function does not free memory
+     *
+     * @param      uart_text_input  Text input instance
+     */
+    void uart_text_input_reset(UART_TextInput *uart_text_input);
+
+    /** Get text input view
+     *
+     * @param      uart_text_input  UART_TextInput instance
+     *
+     * @return     View instance that can be used for embedding
+     */
+    View *uart_text_input_get_view(UART_TextInput *uart_text_input);
+
+    /** Set text input result callback
+     *
+     * @param      uart_text_input          UART_TextInput instance
+     * @param      callback            callback fn
+     * @param      callback_context    callback context
+     * @param      text_buffer         pointer to YOUR text buffer, that we going
+     *                                 to modify
+     * @param      text_buffer_size    YOUR text buffer size in bytes. Max string
+     *                                 length will be text_buffer_size-1.
+     * @param      clear_default_text  clear text from text_buffer on first OK
+     *                                 event
+     */
+    void uart_text_input_set_result_callback(
+        UART_TextInput *uart_text_input,
+        UART_TextInputCallback callback,
+        void *callback_context,
+        char *text_buffer,
+        size_t text_buffer_size,
+        bool clear_default_text);
+
+    void uart_text_input_set_validator(
+        UART_TextInput *uart_text_input,
+        UART_TextInputValidatorCallback callback,
+        void *callback_context);
+
+    UART_TextInputValidatorCallback
+    uart_text_input_get_validator_callback(UART_TextInput *uart_text_input);
+
+    void *uart_text_input_get_validator_callback_context(UART_TextInput *uart_text_input);
+
+    /** Set text input header text
+     *
+     * @param      uart_text_input  UART_TextInput instance
+     * @param      text        text to be shown
+     */
+    void uart_text_input_set_header_text(UART_TextInput *uart_text_input, const char *text);
+
+#ifdef __cplusplus
+}
+#endif

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است