diff --git a/.ci_files/devbuild_msg_discord.txt b/.ci_files/devbuild_msg_discord.txt index 1b7b7bd7f..41a70e45e 100644 --- a/.ci_files/devbuild_msg_discord.txt +++ b/.ci_files/devbuild_msg_discord.txt @@ -9,8 +9,7 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blo [Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz&channel=dev-cfw&version=(buildnum)) > ` ` [Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz&channel=dev-cfw&version=(buildnum)e) > `e` [No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz&channel=dev-cfw&version=(buildnum)c) > `c` -[RGB patch - only for hardware mod!](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)r.tgz&channel=dev-cfw&version=(buildnum)r) > `r` -What ` `, `e`, `c`, `r` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-n-r-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) +What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) ### Direct tgz download links: -[Default](https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz) > ` ` - [Extra apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz) > `e` - [No apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz) > `c` - [RGB patch - only for hardware mod!](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)r.tgz) > `r` +[Default](https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz) > ` ` - [Extra apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz) > `e` - [No apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz) > `c` diff --git a/.ci_files/devbuild_msg_telegram.txt b/.ci_files/devbuild_msg_telegram.txt index e03d19c6c..13d0545dd 100644 --- a/.ci_files/devbuild_msg_telegram.txt +++ b/.ci_files/devbuild_msg_telegram.txt @@ -10,13 +10,11 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blo [Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz&channel=dev-cfw&version=(buildnum)) > ` ` [Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz&channel=dev-cfw&version=(buildnum)e) > `e` [No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz&channel=dev-cfw&version=(buildnum)c) > `c` -[RGB patch - only for hardware mod!](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)r.tgz&channel=dev-cfw&version=(buildnum)r) > `r` -What ` `, `e`, `c`, `r` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-n-r-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) +What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) **Direct tgz download links:** [Default](https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz) > ` ` [Extra apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz) > `e` [No apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz) > `c` -[RGB patch - only for hardware mod!](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)r.tgz) > `r` diff --git a/.ci_files/release_msg_discord.txt b/.ci_files/release_msg_discord.txt index 079135f40..8eadaaf1f 100644 --- a/.ci_files/release_msg_discord.txt +++ b/.ci_files/release_msg_discord.txt @@ -9,8 +9,7 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blo [Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz&channel=release-cfw&version=(releasever)) > ` ` [Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz&channel=release-cfw&version=(releasever)e) > `e` [No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz&channel=release-cfw&version=(releasever)c) > `c` -[RGB patch - only for hardware mod!](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)r.tgz&channel=release-cfw&version=(releasever)r) > `r` -What ` `, `e`, `c`, `r` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-n-r-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) +What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) ### Direct tgz download links: -[Default](https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz) > ` ` - [Extra apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz) > `e` - [No apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz) > `c` - [RGB patch - only for hardware mod!](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)r.tgz) > `r` +[Default](https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz) > ` ` - [Extra apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz) > `e` - [No apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz) > `c` diff --git a/.ci_files/release_msg_telegram.txt b/.ci_files/release_msg_telegram.txt index f561fee21..6d527970f 100644 --- a/.ci_files/release_msg_telegram.txt +++ b/.ci_files/release_msg_telegram.txt @@ -10,13 +10,11 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blo [Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz&channel=release-cfw&version=(releasever)) > ` ` [Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz&channel=release-cfw&version=(releasever)e) > `e` [No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz&channel=release-cfw&version=(releasever)c) > `c` -[RGB patch - only for hardware mod!](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)r.tgz&channel=release-cfw&version=(releasever)r) > `r` -What ` `, `e`, `c`, `r` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-n-r-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) +What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) **Direct tgz download links:** [Default](https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz) > ` ` [Extra apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz) > `e` [No apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz) > `c` -[RGB patch - only for hardware mod!](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)r.tgz) > `r` diff --git a/.ci_files/rgb.patch b/.ci_files/rgb.patch deleted file mode 100644 index 51a305aec..000000000 --- a/.ci_files/rgb.patch +++ /dev/null @@ -1,675 +0,0 @@ -diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c -index 35d2fe6..1af97e2 100644 ---- a/applications/services/notification/notification_app.c -+++ b/applications/services/notification/notification_app.c -@@ -9,6 +9,7 @@ - #include "notification.h" - #include "notification_messages.h" - #include "notification_app.h" -+#include "applications/settings/notification_settings/rgb_backlight.h" - - #define TAG "NotificationSrv" - -@@ -616,6 +617,7 @@ int32_t notification_srv(void* p) { - break; - case SaveSettingsMessage: - notification_save_settings(app); -+ rgb_backlight_save_settings(); - break; - case LoadSettingsMessage: - notification_load_settings(app); -diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c -index 2462b32..8e045ce 100644 ---- a/applications/settings/notification_settings/notification_settings_app.c -+++ b/applications/settings/notification_settings/notification_settings_app.c -@@ -3,6 +3,7 @@ - #include - #include - #include -+#include - - #define MAX_NOTIFICATION_SETTINGS 4 - -@@ -13,6 +14,8 @@ typedef struct { - VariableItemList* variable_item_list; - } NotificationAppSettings; - -+static VariableItem* temp_item; -+ - static const NotificationSequence sequence_note_c = { - &message_note_c5, - &message_delay_100, -@@ -168,6 +171,59 @@ static void vibro_changed(VariableItem* item) { - notification_message(app->notification, &sequence_single_vibro); - } - -+// Set RGB backlight color -+static void color_changed(VariableItem* item) { -+ NotificationAppSettings* app = variable_item_get_context(item); -+ uint8_t index = variable_item_get_current_value_index(item); -+ rgb_backlight_set_color(index); -+ variable_item_set_current_value_text(item, rgb_backlight_get_color_text(index)); -+ notification_message(app->notification, &sequence_display_backlight_on); -+} -+ -+// TODO: refactor and fix this -+static void color_set_custom_red(VariableItem* item) { -+ NotificationAppSettings* app = variable_item_get_context(item); -+ uint8_t index = variable_item_get_current_value_index(item); -+ rgb_backlight_set_custom_color(index, 0); -+ char valtext[4] = {}; -+ snprintf(valtext, sizeof(valtext), "%d", index); -+ variable_item_set_current_value_text(item, valtext); -+ rgb_backlight_set_color(13); -+ rgb_backlight_update(app->notification->settings.display_brightness * 0xFF, true); -+ // Set to custom color explicitly -+ variable_item_set_current_value_index(temp_item, 13); -+ variable_item_set_current_value_text(temp_item, rgb_backlight_get_color_text(13)); -+ notification_message(app->notification, &sequence_display_backlight_on); -+} -+static void color_set_custom_green(VariableItem* item) { -+ NotificationAppSettings* app = variable_item_get_context(item); -+ uint8_t index = variable_item_get_current_value_index(item); -+ rgb_backlight_set_custom_color(index, 1); -+ char valtext[4] = {}; -+ snprintf(valtext, sizeof(valtext), "%d", index); -+ variable_item_set_current_value_text(item, valtext); -+ rgb_backlight_set_color(13); -+ rgb_backlight_update(app->notification->settings.display_brightness * 0xFF, true); -+ // Set to custom color explicitly -+ variable_item_set_current_value_index(temp_item, 13); -+ variable_item_set_current_value_text(temp_item, rgb_backlight_get_color_text(13)); -+ notification_message(app->notification, &sequence_display_backlight_on); -+} -+static void color_set_custom_blue(VariableItem* item) { -+ NotificationAppSettings* app = variable_item_get_context(item); -+ uint8_t index = variable_item_get_current_value_index(item); -+ rgb_backlight_set_custom_color(index, 2); -+ char valtext[4] = {}; -+ snprintf(valtext, sizeof(valtext), "%d", index); -+ variable_item_set_current_value_text(item, valtext); -+ rgb_backlight_set_color(13); -+ rgb_backlight_update(app->notification->settings.display_brightness * 0xFF, true); -+ // Set to custom color explicitly -+ variable_item_set_current_value_index(temp_item, 13); -+ variable_item_set_current_value_text(temp_item, rgb_backlight_get_color_text(13)); -+ notification_message(app->notification, &sequence_display_backlight_on); -+} -+ - static uint32_t notification_app_settings_exit(void* context) { - UNUSED(context); - return VIEW_NONE; -@@ -192,8 +248,40 @@ static NotificationAppSettings* alloc_settings(void) { - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, contrast_text[value_index]); - -+ // RGB Colors -+ item = variable_item_list_add( -+ app->variable_item_list, "LCD Color", rgb_backlight_get_color_count(), color_changed, app); -+ value_index = rgb_backlight_get_settings()->display_color_index; -+ variable_item_set_current_value_index(item, value_index); -+ variable_item_set_current_value_text(item, rgb_backlight_get_color_text(value_index)); -+ temp_item = item; -+ -+ // Custom Color - REFACTOR THIS -+ item = variable_item_list_add( -+ app->variable_item_list, "Custom Red", 255, color_set_custom_red, app); -+ value_index = rgb_backlight_get_settings()->custom_r; -+ variable_item_set_current_value_index(item, value_index); -+ char valtext[4] = {}; -+ snprintf(valtext, sizeof(valtext), "%d", value_index); -+ variable_item_set_current_value_text(item, valtext); -+ -+ item = variable_item_list_add( -+ app->variable_item_list, "Custom Green", 255, color_set_custom_green, app); -+ value_index = rgb_backlight_get_settings()->custom_g; -+ variable_item_set_current_value_index(item, value_index); -+ snprintf(valtext, sizeof(valtext), "%d", value_index); -+ variable_item_set_current_value_text(item, valtext); -+ -+ item = variable_item_list_add( -+ app->variable_item_list, "Custom Blue", 255, color_set_custom_blue, app); -+ value_index = rgb_backlight_get_settings()->custom_b; -+ variable_item_set_current_value_index(item, value_index); -+ snprintf(valtext, sizeof(valtext), "%d", value_index); -+ variable_item_set_current_value_text(item, valtext); -+ // End of RGB -+ - item = variable_item_list_add( -- app->variable_item_list, "LCD Backlight", BACKLIGHT_COUNT, backlight_changed, app); -+ app->variable_item_list, "LCD Brightness", BACKLIGHT_COUNT, backlight_changed, app); - value_index = value_index_float( - app->notification->settings.display_brightness, backlight_value, BACKLIGHT_COUNT); - variable_item_set_current_value_index(item, value_index); -diff --git a/applications/settings/notification_settings/rgb_backlight.c b/applications/settings/notification_settings/rgb_backlight.c -new file mode 100644 -index 0000000..4edd775 ---- /dev/null -+++ b/applications/settings/notification_settings/rgb_backlight.c -@@ -0,0 +1,217 @@ -+/* -+ RGB backlight FlipperZero driver -+ Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n) -+ -+ This program is free software: you can redistribute it and/or modify -+ it under the terms of the GNU General Public License as published by -+ the Free Software Foundation, either version 3 of the License, or -+ (at your option) any later version. -+ -+ This program is distributed in the hope that it will be useful, -+ but WITHOUT ANY WARRANTY; without even the implied warranty of -+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ GNU General Public License for more details. -+ -+ You should have received a copy of the GNU General Public License -+ along with this program. If not, see . -+*/ -+ -+#include "rgb_backlight.h" -+#include -+#include -+ -+#define RGB_BACKLIGHT_SETTINGS_VERSION 6 -+#define RGB_BACKLIGHT_SETTINGS_FILE_NAME ".rgb_backlight.settings" -+#define RGB_BACKLIGHT_SETTINGS_PATH INT_PATH(RGB_BACKLIGHT_SETTINGS_FILE_NAME) -+ -+#define COLOR_COUNT (sizeof(colors) / sizeof(RGBBacklightColor)) -+ -+#define TAG "RGB Backlight" -+ -+static RGBBacklightSettings rgb_settings = { -+ .version = RGB_BACKLIGHT_SETTINGS_VERSION, -+ .display_color_index = 0, -+ .custom_r = 254, -+ .custom_g = 254, -+ .custom_b = 254, -+ .settings_is_loaded = false}; -+ -+static const RGBBacklightColor colors[] = { -+ {"Orange", 255, 60, 0}, -+ {"Yellow", 255, 144, 0}, -+ {"Spring", 167, 255, 0}, -+ {"Lime", 0, 255, 0}, -+ {"Aqua", 0, 255, 127}, -+ {"Cyan", 0, 210, 210}, -+ {"Azure", 0, 127, 255}, -+ {"Blue", 0, 0, 255}, -+ {"Purple", 127, 0, 255}, -+ {"Magenta", 210, 0, 210}, -+ {"Pink", 255, 0, 127}, -+ {"Red", 255, 0, 0}, -+ {"White", 254, 210, 200}, -+ {"Custom", 0, 0, 0}, -+}; -+ -+uint8_t rgb_backlight_get_color_count(void) { -+ return COLOR_COUNT; -+} -+ -+const char* rgb_backlight_get_color_text(uint8_t index) { -+ return colors[index].name; -+} -+ -+void rgb_backlight_load_settings(void) { -+ // Do not load settings if we are in other boot modes than normal -+ if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { -+ rgb_settings.settings_is_loaded = true; -+ return; -+ } -+ -+ // Wait for all required services to start and create their records -+ uint8_t timeout = 0; -+ while(!furi_record_exists(RECORD_STORAGE)) { -+ timeout++; -+ if(timeout > 150) { -+ rgb_settings.settings_is_loaded = true; -+ return; -+ } -+ furi_delay_ms(5); -+ } -+ -+ RGBBacklightSettings settings; -+ File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); -+ const size_t settings_size = sizeof(RGBBacklightSettings); -+ -+ FURI_LOG_D(TAG, "loading settings from \"%s\"", RGB_BACKLIGHT_SETTINGS_PATH); -+ bool fs_result = -+ storage_file_open(file, RGB_BACKLIGHT_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING); -+ -+ if(fs_result) { -+ uint16_t bytes_count = storage_file_read(file, &settings, settings_size); -+ -+ if(bytes_count != settings_size) { -+ fs_result = false; -+ } -+ } -+ -+ if(fs_result) { -+ FURI_LOG_D(TAG, "load success"); -+ if(settings.version != RGB_BACKLIGHT_SETTINGS_VERSION) { -+ FURI_LOG_E( -+ TAG, -+ "version(%d != %d) mismatch", -+ settings.version, -+ RGB_BACKLIGHT_SETTINGS_VERSION); -+ } else { -+ memcpy(&rgb_settings, &settings, settings_size); -+ } -+ } else { -+ FURI_LOG_E(TAG, "load failed, %s", storage_file_get_error_desc(file)); -+ } -+ -+ storage_file_close(file); -+ storage_file_free(file); -+ furi_record_close(RECORD_STORAGE); -+ rgb_settings.settings_is_loaded = true; -+} -+ -+void rgb_backlight_save_settings(void) { -+ RGBBacklightSettings settings; -+ File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); -+ const size_t settings_size = sizeof(RGBBacklightSettings); -+ -+ FURI_LOG_D(TAG, "saving settings to \"%s\"", RGB_BACKLIGHT_SETTINGS_PATH); -+ -+ memcpy(&settings, &rgb_settings, settings_size); -+ -+ bool fs_result = -+ storage_file_open(file, RGB_BACKLIGHT_SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS); -+ -+ if(fs_result) { -+ uint16_t bytes_count = storage_file_write(file, &settings, settings_size); -+ -+ if(bytes_count != settings_size) { -+ fs_result = false; -+ } -+ } -+ -+ if(fs_result) { -+ FURI_LOG_D(TAG, "save success"); -+ } else { -+ FURI_LOG_E(TAG, "save failed, %s", storage_file_get_error_desc(file)); -+ } -+ -+ storage_file_close(file); -+ storage_file_free(file); -+ furi_record_close(RECORD_STORAGE); -+} -+ -+RGBBacklightSettings* rgb_backlight_get_settings(void) { -+ if(!rgb_settings.settings_is_loaded) { -+ rgb_backlight_load_settings(); -+ } -+ return &rgb_settings; -+} -+ -+void rgb_backlight_set_color(uint8_t color_index) { -+ if(color_index > (rgb_backlight_get_color_count() - 1)) color_index = 0; -+ rgb_settings.display_color_index = color_index; -+} -+ -+void rgb_backlight_set_custom_color(uint8_t color, uint8_t index) { -+ if(index > 2) return; -+ if(index == 0) { -+ rgb_settings.custom_r = color; -+ } else if(index == 1) { -+ rgb_settings.custom_g = color; -+ } else if(index == 2) { -+ rgb_settings.custom_b = color; -+ } -+} -+ -+void rgb_backlight_update(uint8_t brightness, bool bypass) { -+ if(!rgb_settings.settings_is_loaded) { -+ rgb_backlight_load_settings(); -+ } -+ -+ if(!bypass) { -+ static uint8_t last_color_index = 255; -+ static uint8_t last_brightness = 123; -+ -+ if(last_brightness == brightness && last_color_index == rgb_settings.display_color_index) { -+ return; -+ } -+ -+ last_brightness = brightness; -+ last_color_index = rgb_settings.display_color_index; -+ } -+ -+ for(uint8_t i = 0; i < SK6805_get_led_count(); i++) { -+ if(rgb_settings.display_color_index == 13) { -+ uint8_t r = rgb_settings.custom_r * (brightness / 255.0f); -+ uint8_t g = rgb_settings.custom_g * (brightness / 255.0f); -+ uint8_t b = rgb_settings.custom_b * (brightness / 255.0f); -+ -+ SK6805_set_led_color(i, r, g, b); -+ } else { -+ if((colors[rgb_settings.display_color_index].red == 0) && -+ (colors[rgb_settings.display_color_index].green == 0) && -+ (colors[rgb_settings.display_color_index].blue == 0)) { -+ uint8_t r = colors[0].red * (brightness / 255.0f); -+ uint8_t g = colors[0].green * (brightness / 255.0f); -+ uint8_t b = colors[0].blue * (brightness / 255.0f); -+ -+ SK6805_set_led_color(i, r, g, b); -+ } else { -+ uint8_t r = colors[rgb_settings.display_color_index].red * (brightness / 255.0f); -+ uint8_t g = colors[rgb_settings.display_color_index].green * (brightness / 255.0f); -+ uint8_t b = colors[rgb_settings.display_color_index].blue * (brightness / 255.0f); -+ -+ SK6805_set_led_color(i, r, g, b); -+ } -+ } -+ } -+ -+ SK6805_update(); -+} -diff --git a/applications/settings/notification_settings/rgb_backlight.h b/applications/settings/notification_settings/rgb_backlight.h -new file mode 100644 -index 0000000..f215ed3 ---- /dev/null -+++ b/applications/settings/notification_settings/rgb_backlight.h -@@ -0,0 +1,91 @@ -+/* -+ RGB backlight FlipperZero driver -+ Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n) -+ -+ This program is free software: you can redistribute it and/or modify -+ it under the terms of the GNU General Public License as published by -+ the Free Software Foundation, either version 3 of the License, or -+ (at your option) any later version. -+ -+ This program is distributed in the hope that it will be useful, -+ but WITHOUT ANY WARRANTY; without even the implied warranty of -+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ GNU General Public License for more details. -+ -+ You should have received a copy of the GNU General Public License -+ along with this program. If not, see . -+*/ -+ -+#include -+#include "SK6805.h" -+ -+typedef struct { -+ char* name; -+ uint8_t red; -+ uint8_t green; -+ uint8_t blue; -+} RGBBacklightColor; -+ -+typedef struct { -+ uint8_t version; -+ uint8_t display_color_index; -+ uint8_t custom_r; -+ uint8_t custom_g; -+ uint8_t custom_b; -+ bool settings_is_loaded; -+} RGBBacklightSettings; -+ -+/** -+ * @brief Получить текущие настройки RGB-подсветки -+ * -+ * @return Указатель на структуру настроек -+ */ -+RGBBacklightSettings* rgb_backlight_get_settings(void); -+ -+/** -+ * @brief Загрузить настройки подсветки с SD-карты -+ */ -+void rgb_backlight_load_settings(void); -+ -+/** -+ * @brief Сохранить текущие настройки RGB-подсветки -+ */ -+void rgb_backlight_save_settings(void); -+ -+/** -+ * @brief Применить текущие настройки RGB-подсветки -+ * -+ * @param brightness Яркость свечения (0-255) -+ * @param bypass Применить настройки принудительно -+ */ -+void rgb_backlight_update(uint8_t brightness, bool bypass); -+ -+/** -+ * @brief Установить цвет RGB-подсветки -+ * -+ * @param color_index Индекс цвета (0 - rgb_backlight_get_color_count()) -+ */ -+void rgb_backlight_set_color(uint8_t color_index); -+ -+/** -+ * @brief Set custom color values by index - 0=R 1=G 2=B -+ * -+ * @param color - color value (0-255) -+ * @param index - color index (0-2) 0=R 1=G 2=B -+ */ -+void rgb_backlight_set_custom_color(uint8_t color, uint8_t index); -+ -+/** -+ * @brief Получить количество доступных цветов -+ * -+ * @return Число доступных вариантов цвета -+ */ -+uint8_t rgb_backlight_get_color_count(void); -+ -+/** -+ * @brief Получить текстовое название цвета -+ * -+ * @param index Индекс из доступных вариантов цвета -+ * @return Указатель на строку с названием цвета -+ */ -+const char* rgb_backlight_get_color_text(uint8_t index); -diff --git a/lib/drivers/SK6805.c b/lib/drivers/SK6805.c -new file mode 100644 -index 0000000..b89f82a ---- /dev/null -+++ b/lib/drivers/SK6805.c -@@ -0,0 +1,101 @@ -+/* -+ SK6805 FlipperZero driver -+ Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n) -+ -+ This program is free software: you can redistribute it and/or modify -+ it under the terms of the GNU General Public License as published by -+ the Free Software Foundation, either version 3 of the License, or -+ (at your option) any later version. -+ -+ This program is distributed in the hope that it will be useful, -+ but WITHOUT ANY WARRANTY; without even the implied warranty of -+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ GNU General Public License for more details. -+ -+ You should have received a copy of the GNU General Public License -+ along with this program. If not, see . -+*/ -+ -+#include "SK6805.h" -+#include -+ -+/* Настройки */ -+#define SK6805_LED_COUNT 3 //Количество светодиодов на плате подсветки -+#define SK6805_LED_PIN &led_pin //Порт подключения светодиодов -+ -+#ifdef FURI_DEBUG -+#define DEBUG_PIN &gpio_ext_pa7 -+#define DEBUG_INIT() \ -+ furi_hal_gpio_init(DEBUG_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh) -+#define DEBUG_SET_HIGH() furi_hal_gpio_write(DEBUG_PIN, true) -+#define DEBUG_SET_LOW() furi_hal_gpio_write(DEBUG_PIN, false) -+#else -+#define DEBUG_INIT() -+#define DEBUG_SET_HIGH() -+#define DEBUG_SET_LOW() -+#endif -+ -+static const GpioPin led_pin = {.port = GPIOA, .pin = LL_GPIO_PIN_8}; -+static uint8_t led_buffer[SK6805_LED_COUNT][3]; -+ -+void SK6805_init(void) { -+ DEBUG_INIT(); -+ furi_hal_gpio_write(SK6805_LED_PIN, false); -+ furi_hal_gpio_init(SK6805_LED_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); -+} -+ -+uint8_t SK6805_get_led_count(void) { -+ return (const uint8_t)SK6805_LED_COUNT; -+} -+void SK6805_set_led_color(uint8_t led_index, uint8_t r, uint8_t g, uint8_t b) { -+ furi_check(led_index < SK6805_LED_COUNT); -+ -+ led_buffer[led_index][0] = g; -+ led_buffer[led_index][1] = r; -+ led_buffer[led_index][2] = b; -+} -+ -+void SK6805_update(void) { -+ SK6805_init(); -+ FURI_CRITICAL_ENTER(); -+ uint32_t end; -+ /* Последовательная отправка цветов светодиодов */ -+ for(uint8_t lednumber = 0; lednumber < SK6805_LED_COUNT; lednumber++) { -+ //Последовательная отправка цветов светодиода -+ for(uint8_t color = 0; color < 3; color++) { -+ //Последовательная отправка битов цвета -+ uint8_t i = 0b10000000; -+ while(i != 0) { -+ if(led_buffer[lednumber][color] & (i)) { -+ furi_hal_gpio_write(SK6805_LED_PIN, true); -+ DEBUG_SET_HIGH(); -+ end = DWT->CYCCNT + 30; -+ //T1H 600 us (615 us) -+ while(DWT->CYCCNT < end) { -+ } -+ furi_hal_gpio_write(SK6805_LED_PIN, false); -+ DEBUG_SET_LOW(); -+ end = DWT->CYCCNT + 26; -+ //T1L 600 us (587 us) -+ while(DWT->CYCCNT < end) { -+ } -+ } else { -+ furi_hal_gpio_write(SK6805_LED_PIN, true); -+ DEBUG_SET_HIGH(); -+ end = DWT->CYCCNT + 11; -+ //T0H 300 ns (312 ns) -+ while(DWT->CYCCNT < end) { -+ } -+ furi_hal_gpio_write(SK6805_LED_PIN, false); -+ DEBUG_SET_LOW(); -+ end = DWT->CYCCNT + 43; -+ //T0L 900 ns (890 ns) -+ while(DWT->CYCCNT < end) { -+ } -+ } -+ i >>= 1; -+ } -+ } -+ } -+ FURI_CRITICAL_EXIT(); -+} -diff --git a/lib/drivers/SK6805.h b/lib/drivers/SK6805.h -new file mode 100644 -index 0000000..c97054f ---- /dev/null -+++ b/lib/drivers/SK6805.h -@@ -0,0 +1,51 @@ -+/* -+ SK6805 FlipperZero driver -+ Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n) -+ -+ This program is free software: you can redistribute it and/or modify -+ it under the terms of the GNU General Public License as published by -+ the Free Software Foundation, either version 3 of the License, or -+ (at your option) any later version. -+ -+ This program is distributed in the hope that it will be useful, -+ but WITHOUT ANY WARRANTY; without even the implied warranty of -+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ GNU General Public License for more details. -+ -+ You should have received a copy of the GNU General Public License -+ along with this program. If not, see . -+*/ -+ -+#ifndef SK6805_H_ -+#define SK6805_H_ -+ -+#include -+ -+/** -+ * @brief Инициализация линии управления подсветкой -+ */ -+void SK6805_init(void); -+ -+/** -+ * @brief Получить количество светодиодов в подсветке -+ * -+ * @return Количество светодиодов -+ */ -+uint8_t SK6805_get_led_count(void); -+ -+/** -+ * @brief Установить цвет свечения светодиода -+ * -+ * @param led_index номер светодиода (от 0 до SK6805_get_led_count()) -+ * @param r значение красного (0-255) -+ * @param g значение зелёного (0-255) -+ * @param b значение синего (0-255) -+ */ -+void SK6805_set_led_color(uint8_t led_index, uint8_t r, uint8_t g, uint8_t b); -+ -+/** -+ * @brief Обновление состояния подсветки дисплея -+ */ -+void SK6805_update(void); -+ -+#endif /* SK6805_H_ */ -diff --git a/targets/f7/furi_hal/furi_hal_light.c b/targets/f7/furi_hal/furi_hal_light.c -index 621478d..ef15153 100644 ---- a/targets/f7/furi_hal/furi_hal_light.c -+++ b/targets/f7/furi_hal/furi_hal_light.c -@@ -3,6 +3,7 @@ - #include - #include - #include -+#include - - #define LED_CURRENT_RED (50u) - #define LED_CURRENT_GREEN (50u) -@@ -31,22 +32,21 @@ void furi_hal_light_init(void) { - } - - void furi_hal_light_set(Light light, uint8_t value) { -- furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); -- if(light & LightRed) { -- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value); -- } -- if(light & LightGreen) { -- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value); -- } -- if(light & LightBlue) { -- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value); -- } - if(light & LightBacklight) { -- uint8_t prev = lp5562_get_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite); -- lp5562_execute_ramp( -- &furi_hal_i2c_handle_power, LP5562Engine1, LP5562ChannelWhite, prev, value, 100); -+ rgb_backlight_update(value, false); -+ } else { -+ furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); -+ if(light & LightRed) { -+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value); -+ } -+ if(light & LightGreen) { -+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value); -+ } -+ if(light & LightBlue) { -+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value); -+ } -+ furi_hal_i2c_release(&furi_hal_i2c_handle_power); - } -- furi_hal_i2c_release(&furi_hal_i2c_handle_power); - } - - void furi_hal_light_blink_start(Light light, uint8_t brightness, uint16_t on_time, uint16_t period) { diff --git a/assets/dolphin/external/L1_Halloween_128x64/frame_0.png b/.ci_files/season_anims/L1_Halloween_128x64/frame_0.png similarity index 100% rename from assets/dolphin/external/L1_Halloween_128x64/frame_0.png rename to .ci_files/season_anims/L1_Halloween_128x64/frame_0.png diff --git a/assets/dolphin/external/L1_Halloween_128x64/frame_1.png b/.ci_files/season_anims/L1_Halloween_128x64/frame_1.png similarity index 100% rename from assets/dolphin/external/L1_Halloween_128x64/frame_1.png rename to .ci_files/season_anims/L1_Halloween_128x64/frame_1.png diff --git a/assets/dolphin/external/L1_Halloween_128x64/frame_2.png b/.ci_files/season_anims/L1_Halloween_128x64/frame_2.png similarity index 100% rename from assets/dolphin/external/L1_Halloween_128x64/frame_2.png rename to .ci_files/season_anims/L1_Halloween_128x64/frame_2.png diff --git a/assets/dolphin/external/L1_Halloween_128x64/frame_3.png b/.ci_files/season_anims/L1_Halloween_128x64/frame_3.png similarity index 100% rename from assets/dolphin/external/L1_Halloween_128x64/frame_3.png rename to .ci_files/season_anims/L1_Halloween_128x64/frame_3.png diff --git a/assets/dolphin/external/L1_Halloween_128x64/meta.txt b/.ci_files/season_anims/L1_Halloween_128x64/meta.txt similarity index 100% rename from assets/dolphin/external/L1_Halloween_128x64/meta.txt rename to .ci_files/season_anims/L1_Halloween_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_0.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_0.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_1.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_1.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_10.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_10.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_11.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_11.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_12.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_12.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_2.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_2.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_3.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_3.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_4.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_4.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_5.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_5.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_6.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_6.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_7.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_7.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_8.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_8.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png b/.ci_files/season_anims/L1_Happy_holidays_128x64/frame_9.png similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png rename to .ci_files/season_anims/L1_Happy_holidays_128x64/frame_9.png diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt b/.ci_files/season_anims/L1_Happy_holidays_128x64/meta.txt similarity index 100% rename from assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt rename to .ci_files/season_anims/L1_Happy_holidays_128x64/meta.txt diff --git a/assets/dolphin/external/L1_New_year_128x64/frame_0.png b/.ci_files/season_anims/L1_New_year_128x64/frame_0.png similarity index 100% rename from assets/dolphin/external/L1_New_year_128x64/frame_0.png rename to .ci_files/season_anims/L1_New_year_128x64/frame_0.png diff --git a/assets/dolphin/external/L1_New_year_128x64/frame_1.png b/.ci_files/season_anims/L1_New_year_128x64/frame_1.png similarity index 100% rename from assets/dolphin/external/L1_New_year_128x64/frame_1.png rename to .ci_files/season_anims/L1_New_year_128x64/frame_1.png diff --git a/assets/dolphin/external/L1_New_year_128x64/frame_2.png b/.ci_files/season_anims/L1_New_year_128x64/frame_2.png similarity index 100% rename from assets/dolphin/external/L1_New_year_128x64/frame_2.png rename to .ci_files/season_anims/L1_New_year_128x64/frame_2.png diff --git a/assets/dolphin/external/L1_New_year_128x64/frame_3.png b/.ci_files/season_anims/L1_New_year_128x64/frame_3.png similarity index 100% rename from assets/dolphin/external/L1_New_year_128x64/frame_3.png rename to .ci_files/season_anims/L1_New_year_128x64/frame_3.png diff --git a/assets/dolphin/external/L1_New_year_128x64/meta.txt b/.ci_files/season_anims/L1_New_year_128x64/meta.txt similarity index 100% rename from assets/dolphin/external/L1_New_year_128x64/meta.txt rename to .ci_files/season_anims/L1_New_year_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_0.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_0.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_1.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_1.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_10.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_10.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_11.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_11.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_12.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_12.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_13.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_13.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_14.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_14.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_15.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_15.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_16.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_16.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_17.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_17.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_18.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_18.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_19.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_19.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_2.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_2.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_20.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_20.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_21.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_21.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_22.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_22.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_23.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_23.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_24.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_24.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_25.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_25.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_26.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_26.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_27.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_27.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_28.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_28.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_29.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_29.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_3.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_3.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_30.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_30.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_31.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_31.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_32.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_32.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_33.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_33.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_34.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_34.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_35.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_35.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_36.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_36.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_4.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_4.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_5.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_5.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_6.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_6.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_7.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_7.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_8.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_8.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png b/.ci_files/season_anims/L1_Sleigh_ride_128x64/frame_9.png similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/frame_9.png diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt b/.ci_files/season_anims/L1_Sleigh_ride_128x64/meta.txt similarity index 100% rename from assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt rename to .ci_files/season_anims/L1_Sleigh_ride_128x64/meta.txt diff --git a/.ci_files/season_anims/manifest.txt b/.ci_files/season_anims/manifest.txt new file mode 100644 index 000000000..e3c01eb0f --- /dev/null +++ b/.ci_files/season_anims/manifest.txt @@ -0,0 +1,30 @@ +Filetype: Flipper Animation Manifest +Version: 1 + +Name: L1_Happy_holidays_128x64 +Min butthurt: 0 +Max butthurt: 14 +Min level: 1 +Max level: 3 +Weight: 3 + +Name: L1_Sleigh_ride_128x64 +Min butthurt: 0 +Max butthurt: 14 +Min level: 1 +Max level: 3 +Weight: 4 + +Name: L1_New_year_128x64 +Min butthurt: 0 +Max butthurt: 14 +Min level: 1 +Max level: 3 +Weight: 4 + +Name: L1_Halloween_128x64 +Min butthurt: 0 +Max butthurt: 14 +Min level: 1 +Max level: 3 +Weight: 4 diff --git a/.drone.yml b/.drone.yml index 008713039..347ab1ce1 100644 --- a/.drone.yml +++ b/.drone.yml @@ -81,25 +81,6 @@ steps: - mv dist/f7-C/* artifacts-extra-apps/ - ls -laS artifacts-extra-apps - ls -laS artifacts-extra-apps/f7-update-${DRONE_TAG}e - environment: - FBT_TOOLS_CUSTOM_LINK: - from_secret: fbt_link - - - name: "Build with RGB patch" - image: hfdj/fztools - pull: never - commands: - - git apply .ci_files/rgb.patch - - export DIST_SUFFIX=${DRONE_TAG}r - - export WORKFLOW_BRANCH_OR_TAG=release-cfw-rgb - - export FORCE_NO_DIRTY=yes - - export FBT_GIT_SUBMODULE_SHALLOW=1 - - rm -f build/f7-firmware-C/toolbox/version.* - - ./fbt COMPACT=1 DEBUG=0 updater_package - - mkdir artifacts-rgb-patch - - mv dist/f7-C/* artifacts-rgb-patch/ - - ls -laS artifacts-rgb-patch - - ls -laS artifacts-rgb-patch/f7-update-${DRONE_TAG}r - sed -i 's/(version)/'${DRONE_TAG}'/g' CHANGELOG.md - echo '# Install FW via Web Updater:' >> CHANGELOG.md - echo '### [Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/${DRONE_TAG}/flipper-z-f7-update-'${DRONE_TAG}'.tgz&channel=release-cfw&version='${DRONE_TAG}') > ` `' >> CHANGELOG.md @@ -107,8 +88,6 @@ steps: - echo '### [Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'e.tgz&channel=release-cfw&version='${DRONE_TAG}'e) > `e`' >> CHANGELOG.md - echo '' >> CHANGELOG.md - echo '### [No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'c.tgz&channel=release-cfw&version='${DRONE_TAG}'c) > `c`' >> CHANGELOG.md - - echo '' >> CHANGELOG.md - - echo '### [RGB patch - only for hardware mod!](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'r.tgz&channel=release-cfw&version='${DRONE_TAG}'r) > `r`' >> CHANGELOG.md environment: FBT_TOOLS_CUSTOM_LINK: from_secret: fbt_link @@ -117,20 +96,16 @@ steps: image: joshkeegan/zip commands: - cp artifacts-extra-apps/flipper-z-f7-update-${DRONE_TAG}e.tgz . - - cp artifacts-rgb-patch/flipper-z-f7-update-${DRONE_TAG}r.tgz . - cp artifacts-clean/flipper-z-f7-update-${DRONE_TAG}c.tgz . - cp artifacts-default/flipper-z-f7-update-${DRONE_TAG}.tgz . - zip -r artifacts-extra-apps/flipper-z-f7-update-${DRONE_TAG}e.zip artifacts-extra-apps/f7-update-${DRONE_TAG}e - - zip -r artifacts-rgb-patch/flipper-z-f7-update-${DRONE_TAG}r.zip artifacts-rgb-patch/f7-update-${DRONE_TAG}r - zip -r artifacts-clean/flipper-z-f7-update-${DRONE_TAG}c.zip artifacts-clean/f7-update-${DRONE_TAG}c - zip -r artifacts-default/flipper-z-f7-update-${DRONE_TAG}.zip artifacts-default/f7-update-${DRONE_TAG} - tar czpf artifacts-default/flipper-z-any-scripts-${DRONE_TAG}.tgz scripts - rm -rf artifacts-extra-apps/f7-update-${DRONE_TAG} - - rm -rf artifacts-rgb-patch/f7-update-${DRONE_TAG} - rm -rf artifacts-clean/f7-update-${DRONE_TAG} - rm -rf artifacts-default/f7-update-${DRONE_TAG} - ls -laS artifacts-extra-apps - - ls -laS artifacts-rgb-patch - ls -laS artifacts-clean - ls -laS artifacts-default - mv artifacts-default/ ${DRONE_TAG} @@ -172,21 +147,6 @@ steps: from_secret: dep_target_extra source: flipper-z-f7-update-${DRONE_TAG}e.tgz - - name: "Upload rgb patch version to updates srv" - image: appleboy/drone-scp:linux-amd64 - settings: - host: - from_secret: dep_host - username: - from_secret: dep_user - password: - from_secret: dep_passwd - port: - from_secret: dep_port - target: - from_secret: dep_target_extra - source: flipper-z-f7-update-${DRONE_TAG}r.tgz - - name: "Upload clean version to updates srv" image: appleboy/drone-scp:linux-amd64 settings: @@ -215,7 +175,6 @@ steps: - ${DRONE_TAG}/*.tgz - ${DRONE_TAG}/*.zip - artifacts-extra-apps/*.tgz - - artifacts-rgb-patch/*.tgz - artifacts-clean/*.tgz title: ${DRONE_TAG} note: CHANGELOG.md @@ -245,7 +204,7 @@ steps: - wget "https://raw.githubusercontent.com/fieu/discord.sh/2253303efc0e7211ac2777d2535054cbb872f1e0/discord.sh" - chmod +x ./discord.sh - sed -n '/## Main changes/,/## Other changes/p' CHANGELOG.md | sed -e 's/## Main changes//' -e 's/## Other changes//' > changelogcut.txt - - head -c 1544 changelogcut.txt > changelogcutfin.txt + - head -c 1200 changelogcut.txt > changelogcutfin.txt - truncate -s -1 changelogcutfin.txt - tail -c +2 changelogcutfin.txt > changelogready.txt - rm -f changelogcut.txt @@ -399,30 +358,10 @@ steps: FBT_TOOLS_CUSTOM_LINK: from_secret: fbt_link - - name: "Build dev with rgb patch" - image: hfdj/fztools - pull: never - commands: - - git apply .ci_files/rgb.patch - - export DIST_SUFFIX=${DRONE_BUILD_NUMBER}r - - export WORKFLOW_BRANCH_OR_TAG=dev-cfw-rgb - - export FORCE_NO_DIRTY=yes - - export FBT_GIT_SUBMODULE_SHALLOW=1 - - rm -f build/f7-firmware-C/toolbox/version.* - - ./fbt COMPACT=1 DEBUG=0 updater_package - - mkdir artifacts-rgb-patch - - mv dist/f7-C/* artifacts-rgb-patch/ - - ls -laS artifacts-rgb-patch - - ls -laS artifacts-rgb-patch/f7-update-${DRONE_BUILD_NUMBER}r - environment: - FBT_TOOLS_CUSTOM_LINK: - from_secret: fbt_link - - name: "Bundle self-update packages" image: joshkeegan/zip commands: - cp artifacts-extra-apps/flipper-z-f7-update-${DRONE_BUILD_NUMBER}e.tgz . - - cp artifacts-rgb-patch/flipper-z-f7-update-${DRONE_BUILD_NUMBER}r.tgz . - cp artifacts-clean/flipper-z-f7-update-${DRONE_BUILD_NUMBER}c.tgz . - cp artifacts-default/flipper-z-f7-update-${DRONE_BUILD_NUMBER}.tgz . - rm -rf artifacts-default/f7-update-${DRONE_BUILD_NUMBER} @@ -481,21 +420,6 @@ steps: from_secret: dep_target_extra source: flipper-z-f7-update-${DRONE_BUILD_NUMBER}e.tgz - - name: "Upload rgb patch version to updates srv" - image: appleboy/drone-scp:linux-amd64 - settings: - host: - from_secret: dep_host - username: - from_secret: dep_user - password: - from_secret: dep_passwd - port: - from_secret: dep_port - target: - from_secret: dep_target_extra - source: flipper-z-f7-update-${DRONE_BUILD_NUMBER}r.tgz - - name: "Upload clean version to updates srv" image: appleboy/drone-scp:linux-amd64 settings: @@ -532,7 +456,7 @@ steps: - wget "https://raw.githubusercontent.com/fieu/discord.sh/2253303efc0e7211ac2777d2535054cbb872f1e0/discord.sh" - chmod +x ./discord.sh - sed -n '/## Main changes/,/## Other changes/p' CHANGELOG.md | sed -e 's/## Main changes//' -e 's/## Other changes//' > changelogcut.txt - - head -c 1544 changelogcut.txt > changelogcutfin.txt + - head -c 1200 changelogcut.txt > changelogcutfin.txt - truncate -s -1 changelogcutfin.txt - tail -c +2 changelogcutfin.txt > changelogready.txt - rm -f changelogcut.txt diff --git a/.gitignore b/.gitignore index cd919937a..9db9f41eb 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,7 @@ PVS-Studio.log # JS packages node_modules/ + +# cli_perf script output in case of errors +/block.bin +/return_block.bin diff --git a/.vscode/example/settings.json.tmpl b/.vscode/example/settings.json.tmpl index 5e5b5dcf4..b8e9f81cd 100644 --- a/.vscode/example/settings.json.tmpl +++ b/.vscode/example/settings.json.tmpl @@ -12,6 +12,7 @@ "SConstruct": "python", "*.fam": "python" }, + "clangd.checkUpdates": false, "clangd.path": "${workspaceFolder}/toolchain/current/bin/clangd@FBT_PLATFORM_EXECUTABLE_EXT@", "clangd.arguments": [ "--query-driver=**/arm-none-eabi-*", diff --git a/CHANGELOG.md b/CHANGELOG.md index 30c0c1f7d..0dd43e977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,34 +1,93 @@ ## Main changes -- Current API: 79.3 -* SubGHz: Jolly Motors support (with add manually) (Thanks @pkooiman !) -* Power: Auto Power Off Timer (by @Dmitry422 with some fixes by @xMasterX) -* OFW: **Fix lost BadBLE keystrokes** -* OFW: **Add the ability to send a signal once via RPC** -* OFW PR 4070: Infrared: increase max carrier limit (by @skotopes) -* OFW PR 4025: Increase system stack's reserved memory size (Fix USB UART Bridge Crash) (by @Astrrra) -* OFW: merged gsurkov/vcp_break_support branch for usb uart bridge (WIP!!!) +- Current API: 86.0 +**WARNING! After install of this version your Desktop (fav apps) and LCD & Notifications settings will be reset to default values, please configure them again after this update!** (this is required due to big updates on that parts and config struct changes) +* SubGHz: Add **Feron** protocol (static 32 bit) **full support** (by @xMasterX) +* SubGHz: Add **Revers RB2 / RB2M Protocol** (static 64 bit) **full support** with add manually (by @xMasterX) +* SubGHz: **Fix Hollarm protocol with more verification** +* SubGHz: **Fix GangQi protocol** (by @DoberBit and @mishamyte (who spent 2 weeks on this)) +* SubGHz: **Came Atomo button hold simulation with full cycle** simulation (to allow proper pairing with receiver) +* SubGHz: Add **Prastel (42bit static code)** support (OFW PR 4178 by @pmazzini) +* Desktop: **Add support for Favorite App - Ok Long** (Warning! Old favourites apps list will be reset!) (PR #886 | by @DrEverr) +* Display: **LCD Color Inversion** (Settings - LCD and Notifications - LCD Inversion) (PR #887 #893 | by @Dmitry422) +* Display: **Night Shift Feature** (dimming backlight in selected time interval) (PR #885 #896 | by @Dmitry422) +* Display: **Сombining RGB Backlight mod** (by @quen0n) and original backlight support **in one firmware** (+ Rainbow/Wave effect (based on @Willy-JL idea)) (PR #877 #881 #890 | by @Dmitry422) - (**To enable RGB Backlight support go into Notifications settings**) +* NFC: Use default UL/UL-C pwd/key as default value for key input (PR #891 | by @mishamyte) +* OFW: LFRFID - **EM4305 support** +* OFW: **Universal IR signal selection** +* OFW: **BadUSB: Mouse control** +* OFW: **Pinning of settings options** +* OFW: NFC app now can launch MFKey32 +* OFW: BadUSB arbitrary key combinations +* OFW PR 4136: BadUSB: Full USB/BLE parameter customization, UI improvements, and more (by @Willy-JL) +* OFW: NFC - Added naming for DESFire cards + fix MF3ICD40 cards unable to be read +* Apps: Add **FindMyFlipper to system apps and allow autostart** on system boot [app by @MatthewKuKanich](https://github.com/MatthewKuKanich/FindMyFlipper) and autoloader by @Willy-JL - to use app please check how to add keys in [app repo](https://github.com/MatthewKuKanich/FindMyFlipper) +* README Update: Enhanced Visuals & Navigation (PR #871 #872 | by @m-xim) +* Docs: Update FAQ.md (PR #865 | by @mi-lrn) +* Input: **Vibro on Button press option** (PR #867 | by @Dmitry422) +* Power: **Option to limit battery charging** (suppress charging on selected charge level) (PR #867 | by @Dmitry422) (idea and example by @oltenxyz) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* Power & Desktop: Add input events sub check & use event system for auto power off -* OFW: Rename FuriHalDebuging.md to FuriHalDebugging.md -* OFW: nfc: Fix MIFARE Plus detection -* OFW: u2f: Fix leaking message digest contexts -* OFW: nfc: Fix MFUL PWD_AUTH command creation -* OFW: Bump cross-spawn in /applications/system/js_app/packages/create-fz-app -* OFW: **Pipe** (new api funcs) -* OFW: Fix invalid path errors while deploying SDK by enforcing toolchain to use UTF-8 on initial SDK Extraction -* OFW: **Added flipper_format_write_empty_line(...)** -* OFW: Fix skylander ID reading -* OFW: Work around incorrect serial port handling by the OS -* OFW: Add winter animations -* OFW: FBT: Don't lint JS packages -* OFW: **Loader: Fix BusFault in handling of OOM** (was already included in previous UL release) -* OFW: **NFC Fix ISO15693 stucking in wrong mode.** -* OFW: Update `infrared_test.c` reference -* OFW: **FuriThread stdin** -* OFW: NFC: Plantain parser Last payment amount fix -* OFW: NFC clipper: BART station ids for San Lorenzo, Bay Fair -* OFW: Fix typo for mf_classic_key_cahce_get_next_key() function +* SubGHz: Move hardcoded extra modulations to user config - uncomment them in setting_user.example and remove .example from filename +* SubGHz: Various bugfixes and experimental options (rolling counter overflow) (by @xMasterX) +* Anims: Disable winter anims +* NFC: mfclassic poller fix early key reuse in dictionary attack state machine (by @noproto) +* OFW: RC fixes +* OFW: Desktop: Fix freeze on boot if PIN set +* OFW PR 4189: USB-UART bridge fix (by @portasynthinca3) +* OFW: FBT: Fix for Python 3.13 +* OFW: sdk: bump API to force re-upload for the catalog +* OFW: SDK: Fix missing RECORD_CLI define +* OFW: Fix NULL dereference in CLI completions +* OFW PR 4181: vcp, cli: Handle Tx/Rx events before Connect/Disconnect + extra fixes (by @portasynthinca3) +* OFW: BLE: Slightly increase mfg_data size +* OFW: fbt: Deterministic STARTUP order & additional checks +* OFW: JS: Update and fix docs, fix Number.toString() with decimals +* OFW: New JS value destructuring +* OFW: Docs: Fix doxygen references from PR 4168 +* OFW: BLE advertising improvements +* OFW: **New CLI architecture** +* OFW: **CLI autocomplete and other sugar** +* OFW: CLI commands in fals and threads +* OFW: cli: fixed `free_blocks` command +* OFW: docs: badusb arbitrary modkey chains +* OFW: Separate cli_shell into toolbox +* OFW: Move JS modules to new arg parser +* OFW: Application chaining +* OFW: Fix DWARF dead code elimination and linking +* OFW: NFC: Fix crash on ISO15693-3 save when memory is empty or cannot be read +* OFW: Reduced ieee754 parser size +* OFW: Added Doom animation (by @doomwastaken) +* OFW PR 4133: add nfc apdu cli command back (by @leommxj) +* OFW: NFC: Support DESFire Transaction MAC file type (by @Willy-JL) +* OFW: NFC: Fix NDEF parser for MIFARE Classic (by @Willy-JL) +* OFW: GUI: Fix widget text scroll with 256+ lines (by @Willy-JL) +* OFW: Infrared: Fix universals sending (by @Willy-JL) +* OFW: HID Ble: increased stack and improvements (by @doomwastaken) +* OFW: Stricter constness for const data (by @hedger) +* OFW PR 4017: Alarm improvements: Snooze, timeouts, and dismissing from the locked state (by @Astrrra) +* OFW: fix: flipper detected before it was rebooted +* OFW: NFC: FeliCa Protocol Expose Read Block API and Allow Specifying Service +* OFW: LFRFID: Fix Detection Conflict Between Securakey and Noralsy Format (by @zinongli) +* OFW: Stdio API improvements +* OFW: GUI: Widget view extra options for JS +* OFW: Update heap implementation +* OFW: Updated Button Panel +* OFW: UART framing mode selection +* OFW: gpio: clear irq status before calling user handler +* OFW: Fix 5V on GPIO +* OFW: Fixed repeat in subghz tx_from_file command +* OFW: LFRFID: Noralsy Format/Brand +* OFW: Faster di card reading +* OFW: vscode: disabled auto-update for clangd since correct version is in the toolchain +* OFW: Furi, USB, BLE, Debug: various bug fixes and improvements +* OFW: EventLoop unsubscribe fix +* OFW: nfc: Enable MFUL sync poller to be provided with passwords +* OFW: ST25TB poller mode check +* OFW: JS features & bugfixes (SDK 0.2) **Existing Widget JS module was removed and replaced with new ofw gui/widget module, old apps using widget may be incompatible now!** +* OFW: Infrared: increase max carrier limit +* OFW: Ensure that `furi_record_create` is passed a non-NULL data pointer +* OFW: Update mbedtls & expose AES +* OFW: Add the Showtime animation

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) @@ -41,21 +100,23 @@ [-> Download qFlipper (official link)](https://flipperzero.one/update) ## Please support development of the project -|Service|Remark|QR Code|Link/Wallet| -|-|-|-|-| -|**Patreon**||
QR image
|https://patreon.com/mmxdev| -|**Boosty**|patreon alternative|
QR image
|https://boosty.to/mmxdev| -|cloudtips|only RU payments accepted|
QR image
|https://pay.cloudtips.ru/p/7b3e9d65| -|YooMoney|only RU payments accepted|
QR image
|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209| -|USDT|(TRC20)|
QR image
|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`| -|ETH|(BSC/ERC20-Tokens)|
QR image
|`0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`| -|BTC||
QR image
|`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`| -|SOL|(Solana/Tokens)|
QR image
|`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`| -|DOGE||
QR image
|`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`| -|LTC||
QR image
|`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`| -|BCH||
QR image
|`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`| -|XMR|(Monero)|
QR image
|`41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`| -|TON||
QR image
|`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`| + +| Service | Remark | QR Code | Link/Wallet | +|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------| +| Patreon **Patreon** | |
QR image
| [patreon.com/mmxdev](https://patreon.com/mmxdev) | +| Boosty **Boosty** | patreon alternative |
QR image
| [boosty.to/mmxdev](https://boosty.to/mmxdev) | +| Cloudtips CloudTips | only RU payments accepted |
QR image
| [pay.cloudtips.ru/p/7b3e9d65](https://pay.cloudtips.ru/p/7b3e9d65) | +| YooMoney YooMoney | only RU payments accepted |
QR image
| [yoomoney.ru/fundraise/XA49mgQLPA0.221209](https://yoomoney.ru/fundraise/XA49mgQLPA0.221209) | +| USDT USDT | TRC20 |
QR image
| `TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs` | +| ETH ETH | BSC/ERC20-Tokens |
QR image
| `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a` | +| BTC BTC | |
QR image
| `bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9` | +| SOL SOL | Solana/Tokens |
QR image
| `DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8` | +| DOGE DOGE | |
QR image
| `D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv` | +| LTC LTC | |
QR image
| `ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9` | +| BCH BCH | |
QR image
| `qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3` | +| XMR XMR | Monero |
QR image
| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn` | +| TON TON | |
QR image
| `UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa` | + #### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis: @mishamyte, ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ... @@ -64,20 +125,22 @@ and all other great people who supported our project and me (xMasterX), thanks t ## **Recommended update option - Web Updater** -### What `r`, `e`, ` `, `c` means? What I need to download if I don't want to use Web updater? -What build I should download and what this name means - `flipper-z-f7-update-(version)(r / e / c).tgz` ?
+### What `e`, ` `, `c` means? What I need to download if I don't want to use Web updater? +What build I should download and what this name means - `flipper-z-f7-update-(version)(e / c).tgz` ?
`flipper-z` = for Flipper Zero device
`f7` = Hardware version - same for all flipper zero devices
`update` = Update package, contains updater, all assets (plugins, IR libs, etc.), and firmware itself
`(version)` = Firmware version
-| Designation | [Base Apps](https://github.com/xMasterX/all-the-plugins#default-pack) | [Extra Apps](https://github.com/xMasterX/all-the-plugins#extra-pack) | ⚠️RGB mode* | -|-----|:---:|:---:|:---:| -| ` ` | ✅ | | | -| `c` | | | | -| `e` | ✅ | ✅ | | -| `r` | ✅ | ✅ | ⚠️ | +| Designation | [Base Apps](https://github.com/xMasterX/all-the-plugins#default-pack) | [Extra Apps](https://github.com/xMasterX/all-the-plugins#extra-pack) | +|-----|:---:|:---:| +| ` ` | ✅ | | +| `c` | | | +| `e` | ✅ | ✅ | + +**To enable RGB Backlight support go into LCD & Notifications settings** + +⚠️RGB backlight [hardware mod](https://github.com/quen0n/flipperzero-firmware-rgb#readme), works only on modded flippers! do not enable on non modded device! -⚠️This is [hardware mod](https://github.com/quen0n/flipperzero-firmware-rgb#readme), works only on modded flippers! do not install on non modded device! Firmware Self-update package (update from microSD) - `flipper-z-f7-update-(version).tgz` for mobile app / qFlipper / web
Archive of `scripts` folder (contains scripts for FW/plugins development) - `flipper-z-any-scripts-(version).tgz`
diff --git a/ReadMe.md b/ReadMe.md index f01dd4898..6ee86e881 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,266 +1,304 @@

- -Unleashed Firmware Logo - + + Unleashed Firmware Logo +

- -### Welcome to the Flipper Zero Unleashed Firmware repo! +[![English Telegram Chat](https://img.shields.io/endpoint?color=neon&style=flat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fflipperzero_unofficial)](https://t.me/flipperzero_unofficial) +[![Russian Telegram Chat](https://img.shields.io/endpoint?color=neon&style=flat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fflipperzero_unofficial_ru)](https://t.me/flipperzero_unofficial_ru) +[![Ukraine Telegram Chat](https://img.shields.io/endpoint?color=neon&style=flat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fflipperzero_unofficial_ua)](https://t.me/flipperzero_unofficial_ua) +[![Discord Server](https://img.shields.io/discord/937479784148115456?style=flat&logo=discord&label=Discord&color=%237289DA&link=https%3A%2F%2Fdiscord.unleashedflip.com%2F)](https://discord.unleashedflip.com) -#### **This firmware is a fork from original (OFW) firmware** [flipperdevices/flipperzero-firmware](https://github.com/flipperdevices/flipperzero-firmware) +# Flipper Zero Unleashed Firmware +This firmware is a fork of the original (OFW) version of [flipperdevices/flipperzero-firmware](https://github.com/flipperdevices/flipperzero-firmware) and represents the **most stable** custom build, incorporating **new features** and **improvements** to the original components while remaining **fully compatible** with the API and applications of the original firmware. -
+> [!WARNING] +> This software is intended solely for experimental purposes and is not meant for any illegal activities. +> We do not condone unlawful behavior and strongly encourage you to use it only within the bounds of the law. +> +> This project is developed independently and is not affiliated with Flipper Devices. +> +> Also be aware, DarkFlippers/unleashed-firmware is the only official page of the project, there is no paid, premium or closed source versions and if someone contacts you and say they are from our team and try to offer something like that - they are scammers, block that users ASAP -### Most stable custom firmware focused on new features and improvements of original firmware components, keeping compatibility with original firmware API and Apps +
-
+## 🚀 Usage -##### This software is for experimental purposes only and is not meant for any illegal activity/purposes.
We do not condone illegal activity and strongly encourage keeping transmissions to legal/valid uses allowed by law.
Also, this software is made without any support from Flipper Devices and is in no way related to the official team. +Before getting started: +- **Review the Official Documentation:** [docs.flipper.net](https://docs.flipper.net) + +- **Installation Guide & Version Info:** + How to install the firmware by following the [Installation Guide](/documentation/HowToInstall.md) and check the [version information](/CHANGELOG.md#recommended-update-option---web-updater) (`e`, ` `, `c`) + +- **FAQ:** + Find answers to common questions in the [FAQ](/documentation/FAQ.md) -
+## 📦 Releases -## FAQ (frequently asked questions) -[Follow this link to find answers to most asked questions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/FAQ.md) +### Release builds (stable) +- Telegram Telegram: t.me/unleashed_fw +- GitHub GitHub Releases -## Our official domains -- https://flipperunleashed.com/ -> our main web page -- https://unleashedflip.com/ -> update server, direct .tgz update links for web updater or direct download +### Dev builds (unstable) +> [!NOTE] +> Built automatically from dev branch -## Dev builds (unstable) (built automatically from dev branch) -- https://dev.unleashedflip.com/ -- https://t.me/kotnehleb +- Web site: [dev.unleashedflip.com](https://dev.unleashedflip.com) +- Telegram Telegram: t.me/kotnehleb -## Releases in Telegram -- https://t.me/unleashed_fw -# What's changed -- **Sub-GHz** *lib & hal* - - Regional TX restrictions removed - - Extra Sub-GHz frequencies added - - Frequency range can be extended in settings file (Warning: It can damage Flipper's hardware) - - Many rolling code [protocols](https://github.com/DarkFlippers/unleashed-firmware#current-modified-and-new-sub-ghz-protocols-list) now have the ability to save & send captured signals - - FAAC SLH (Spa) & BFT Mitto (keeloq secure with seed) manual creation - - External CC1101 module support [(by quen0n)](https://github.com/DarkFlippers/unleashed-firmware/pull/307) -- **Sub-GHz** *Main App* - - Save last used settings [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77) - - New frequency analyzer [(by ClusterM)](https://github.com/DarkFlippers/unleashed-firmware/pull/43) - - Press OK in frequency analyzer to use detected frequency in Read modes [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77) - - Long press OK button in Sub-GHz Frequency analyzer to switch to Read menu [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/79) - - New option to use timestamps + protocol name when you saving file, instead of random name or timestamp only - Enable in `Radio Settings -> Protocol Names = ON` - - Read mode UI improvements (shows time when signal was received) (by @wosk) - - External CC1101 module support (Hardware SPI used) - - External CC1101 module amplifier control (or LED control) support (enabled by default) - - **Hold right in received signal list to delete selected signal** - - **Custom buttons for Keeloq / Alutech AT4N / Nice Flor S / Somfy Telis / Security+ 2.0 / CAME Atomo** - now you can use arrow buttons to send signal with different button code - - `Add manually` menu extended with new protocols - - FAAC SLH, BFT Mitto / Somfy Telis / Nice Flor S / CAME Atomo, etc.. manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis, (right arrow button for other protocols)) - - Debug mode counter increase settings (+1 -> +5, +10, default: +1) - - Debug PIN output settings for protocol development - -- **Sub-GHz apps** *by unleashed team* - - Sub-GHz Bruteforce - static code brute-force plugin | - - Time delay (between signals) setting (hold Up in main screen(says Up to Save)) + configure repeats in protocols list by pressing right button on selected protocol - - Load your own file and select bytes you want to bruteforce or use preconfigured options in protocols list - - Sub-GHz Remote - remote control for 5 sub-ghz files | bind one file for each button - - use the built-in constructor or make config file by following this [instruction](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemotePlugin.md) -- **Infrared** - - Recompiled IR TV Universal Remote for ALL buttons - - Universal remotes for Projectors, Fans, A/Cs and Audio(soundbars, etc.) -> Also always updated and verified by our team - - Infrared -> `RCA` Protocol - - Infrared -> External IR modules support (with autodetect by OFW) -- **NFC/RFID/iButton** - * LFRFID and iButton Fuzzer plugins - * Add DEZ 8 display form for EM4100 (by @korden32) - * Extra Mifare Classic keys in system dict - * EMV Protocol + Public data parser (by @Leptopt1los and @wosk) - * NFC `Add manually` -> Mifare Classic with custom UID - * NFC parsers: Umarsh, Zolotaya Korona, Kazan, Metromoney, Moscow Social Card, Troika (reworked) and [many others](https://github.com/DarkFlippers/unleashed-firmware/tree/dev/applications/main/nfc/plugins/supported_cards) (by @Leptopt1los and @assasinfil) -- **Quality of life & other features** - - Customizable Flipper name **Update! Now can be changed in Settings->Desktop** (by @xMasterX and @Willy-JL) - - Text Input UI element -> Cursor feature (by @Willy-JL) - - Byte Input Mini editor -> **Press UP** multiple times until the nibble editor appears (by @gid9798) - - Clock on Desktop -> `Settings -> Desktop -> Show Clock` (by @gid9798) - - Battery percentage display with different styles `Settings -> Desktop -> Battery View` - - More games in Dummy Mode -> click or hold any of arrow buttons - - Lock device with pin(or regular lock if pin not set) by holding UP button on main screen [(by an4tur0r)](https://github.com/DarkFlippers/unleashed-firmware/pull/107) - - **BadKB** plugin [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/Flipper-XFW/Xtreme-Firmware/tree/dev/applications/main/bad_kb) - (See in Applications->Tools) - (aka BadUSB via Bluetooth) - - BadUSB -> Keyboard layouts [(by rien > dummy-decoy)](https://github.com/dummy-decoy/flipperzero-firmware/tree/dummy_decoy/bad_usb_keyboard_layout) - - Custom community plugins and games added + all known working apps can be downloaded in extra pack in every release - - Other small fixes and changes throughout - - See other changes in readme below +## 🆕 What's New + +>
+> Sub‑GHz Library & HAL +>
+> +> - Regional TX restrictions removed +> - Extra Sub-GHz frequencies added +> - Frequency range can be extended in settings file _(warning: It can damage Flipper's hardware)_ +> - Many rolling code [protocols](#current-modified-and-new-sub-ghz-protocols-list) now have the ability to save & send captured signals +> - FAAC SLH (Spa) & BFT Mitto (keeloq secure with seed) manual creation +> - External CC1101 module support [(by quen0n)](https://github.com/DarkFlippers/unleashed-firmware/pull/307) +>
+ +>
+> Sub‑GHz Main App +>
+> +> - Save last used settings [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77) +> - New frequency analyzer [(by ClusterM)](https://github.com/DarkFlippers/unleashed-firmware/pull/43) +> - Press OK in frequency analyzer to use detected frequency in Read modes [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77) +> - Long press OK button in Sub-GHz Frequency analyzer to switch to Read menu [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/79) +> - New option to use timestamps + protocol name when you saving file, instead of random name or timestamp only - Enable in `Radio Settings -> Protocol Names = ON` +> - Read mode UI improvements (shows time when signal was received) (by @wosk) +> - External CC1101 module support (Hardware SPI used) +> - External CC1101 module amplifier control (or LED control) support (enabled by default) +> - **Hold right in received signal list to delete selected signal** +>- **Custom buttons for Keeloq / Alutech AT4N / Nice Flor S / Somfy Telis / Security+ 2.0 / CAME Atomo** - now you can use arrow buttons to send signal with different button code +> - `Add manually` menu extended with new protocols +> - FAAC SLH, BFT Mitto / Somfy Telis / Nice Flor S / CAME Atomo, etc. manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis, (right arrow button for other protocols)) +> - Debug mode counter increase settings (+1 → +5, +10, default: +1) +> - Debug PIN output settings for protocol development +>
+ +>
+> Sub‑GHz Apps (by Unleashed Team) +>
+> +> - Sub-GHz Bruteforce - static code brute-force plugin +> - Time delay (between signals) setting (hold Up in main screen (says Up to Save)) + configure repeats in protocols list by pressing right button on selected protocol +> - Load your own file and select bytes you want to bruteforce or use preconfigured options in protocols list +> - Sub-GHz Remote - remote control for 5 sub-ghz files | bind one file for each button +> - use the built-in constructor or make config file by following this [instruction](/documentation/SubGHzRemotePlugin.md) +>
+ +>
+> Infrared (IR) +>
+> +> - Recompiled IR TV Universal Remote for ALL buttons +> - Universal remotes for Projectors, Fans, A/Cs and Audio(soundbars, etc.) → Also always updated and verified by our team +> - Infrared → `RCA` Protocol +> - Infrared → External IR modules support (with autodetect by OFW) +>
+ +>
+> NFC/RFID/iButton +>
+> +> - LFRFID and iButton Fuzzer plugins +> - Add DEZ 8 display form for EM4100 (by @korden32) +> - Extra Mifare Classic keys in system dict +> - EMV Protocol + Public data parser (by @Leptopt1los and @wosk) +> - NFC `Add manually` → Mifare Classic with custom UID +> - NFC parsers: Umarsh, Zolotaya Korona, Kazan, Metromoney, Moscow Social Card, Troika (reworked) and [many others](https://github.com/DarkFlippers/unleashed-firmware/tree/dev/applications/main/nfc/plugins/supported_cards) (by @Leptopt1los and @assasinfil) +>
+ +>
+> Quality of Life & Other Features +>
+> +> - Customizable Flipper name **Update! Now can be changed in Settings → Desktop** (by @xMasterX and @Willy-JL) +> - Text Input UI element → Cursor feature (by @Willy-JL) +> - Byte Input Mini editor → **Press UP** multiple times until the nibble editor appears (by @gid9798) +> - Clock on Desktop `Settings -> Desktop -> Show Clock` (by @gid9798) +> - Battery percentage display with different styles `Settings -> Desktop -> Battery View` +> - More games in Dummy Mode → click or hold any of arrow buttons +> - Lock device with pin (or regular lock if pin not set) by holding UP button on main screen [(by an4tur0r)](https://github.com/DarkFlippers/unleashed-firmware/pull/107) +> - **BadKB** plugin [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/Flipper-XFW/Xtreme-Firmware/tree/dev/applications/main/bad_kb) - (See in Applications → Tools) - (aka BadUSB via Bluetooth) +> - BadUSB → Keyboard layouts [(by rien > dummy-decoy)](https://github.com/dummy-decoy/flipperzero-firmware/tree/dummy_decoy/bad_usb_keyboard_layout) +> - Custom community plugins and games added + all known working apps can be downloaded in extra pack in every release +> - Other small fixes and changes throughout +> - See other changes in readme below +>
Also check the [changelog in releases](https://github.com/DarkFlippers/unleashed-firmware/releases) for latest updates! ### Current modified and new Sub-GHz protocols list: -Thanks to Official team (to their SubGHz Developer, Skorp) for implementing support (decoder + encoder / or decode only) for these protocols in OFW. +Thanks to Official team (to their SubGHz Developer, Skorp) for implementing support (decoder + encoder / or decode only) for these protocols in OFW. -Keeloq [Not ALL systems supported for decode or emulation!] - [Supported manufacturers list](https://pastes.io/raw/unuj9bhe4m) +> [!NOTE] +> Not all Keeloq systems are supported for decoding or emulation! +>
+> Supported Keeloq manufacturers include +>
+> +> | Column 1 | Column 2 | Column 3 | Column 4 | Column 5 | +> |-------------------|--------------|------------------|-------------------|------------------------| +> | Alligator | Comunello | GSN | Magic_4 | SL_A2-A4 | +> | Alligator_S-275 | Dea_Mio | Guard_RF-311A | Mongoose | SL_A6-A9/Tomahawk_9010 | +> | APS-1100_APS-2550 | DTM_Neo | Harpoon | Mutanco_Mutancode | SL_B6,B9_dop | +> | Aprimatic | DoorHan | IronLogic | NICE_MHOUSE | Sommer(fsk476) | +> | Beninca | EcoStar | JCM_Tech | NICE_Smilo | Stilmatic | +> | BFT | Elmes_Poland | KEY | Normstahl | Teco | +> | Came_Space | FAAC_RC,XT | Kingates_Stylo4k | Pantera | Tomahawk_TZ-9030 | +> | Cenmax | FAAC_SLH | KGB/Subaru | Pantera_CLK | Tomahawk_Z,X_3-5 | +> | Cenmax_St-5 | Faraon | Leopard | Pantera_XS/Jaguar | ZX-730-750-1055 | +> | Cenmax_St-7 | Genius_Bravo | Magic_1 | Partisan_RX | | +> | Centurion | Gibidi | Magic_2 | Reff | | +> | Monarch | Jolly Motors | Magic_3 | Sheriff | | +>
+
-Decoders/Encoders or emulation (+ programming mode) support made by @xMasterX: +
+Decoders/Encoders or emulation (+ programming mode) support made by @xMasterX +
+ +- Feron (static 32 bit) +- ReversRB2 / RB2M (static 64 bit) with add manually support - Marantec24 (static 24 bit) with add manually support - GangQi (static 34 bit) with button parsing and add manually support (thanks to @mishamyte for captures and testing, thanks @Skorpionm for help) - Hollarm (static 42 bit) with button parsing and add manually support (thanks to @mishamyte for captures, thanks @Skorpionm for help) - Hay21 (dynamic 21 bit) with button parsing - Nero Radio 57bit (+ 56bit support) - CAME 12bit/24bit encoder fixes (Fixes are now merged in OFW) -- Keeloq: Dea Mio, Genius Bravo, GSN, HCS101, AN-Motors, JCM Tech, MHouse, Nice Smilo, DTM Neo, FAAC RC,XT, Mutancode, Normstahl, Beninca + Allmatic, Stilmatic, CAME Space, Aprimatic (model TR and similar), Centurion Nova (thanks Carlos !), Hormann EcoStar, Novoferm, Sommer, Monarch (thanks @ashphx !) +- Keeloq: Dea Mio, Genius Bravo, GSN, HCS101, AN-Motors, JCM Tech, MHouse, Nice Smilo, DTM Neo, FAAC RC,XT, Mutancode, Normstahl, Beninca + Allmatic, Stilmatic, CAME Space, Aprimatic (model TR and similar), Centurion Nova (thanks Carlos !), Hormann EcoStar, Novoferm, Sommer, Monarch (thanks @ashphx !), Jolly Motors (thanks @pkooiman !) +
-Protocols support made by Skorp (original implementation) and @xMasterX (current version): -- CAME Atomo -> Update! check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) -- Nice Flor S -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) -- FAAC SLH (Spa) -> Update!!! (Programming mode!) Check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) -- Keeloq: BFT Mitto -> Update! Check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) +
+Protocols support made by Skorp (original implementation) and @xMasterX (current version) +
+ +- CAME Atomo → Update! check out new [instructions](/documentation/SubGHzRemoteProg.md) +- Nice Flor S → How to create new remote - [instructions](/documentation/SubGHzRemoteProg.md) +- FAAC SLH (Spa) → Update!!! (Programming mode!) Check out new [instructions](/documentation/SubGHzRemoteProg.md) +- Keeloq: BFT Mitto → Update! Check out new [instructions](/documentation/SubGHzRemoteProg.md) - Star Line - Security+ v1 & v2 +
-Encoders made by @assasinfil and @xMasterX: -- Somfy Telis -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) +
+Encoders made by @assasinfil and @xMasterX +
+ +- Somfy Telis → How to create new remote - [instructions](/documentation/SubGHzRemoteProg.md) - Somfy Keytis - KingGates Stylo 4k -- Alutech AT-4N -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) -- Nice ON2E (Nice One) -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) +- Alutech AT-4N → How to create new remote - [instructions](/documentation/SubGHzRemoteProg.md) +- Nice ON2E (Nice One) → How to create new remote - [instructions](/documentation/SubGHzRemoteProg.md) +
+ + +## ❤️ Please support development of the project -## Please support development of the project The majority of this project is developed and maintained by me, @xMasterX. -Our team is small and the guys are working on this project as much as they can solely based on the enthusiasm they have for this project and the community. -- @Leptopt1los - NFC, RFID, Plugins, and many other things -- @gid9798 - SubGHz, Plugins, many other things - currently offline :( -- @assasinfil - SubGHz protocols, NFC parsers -- @Svaarich - UI design and animations -- @amec0e - Infrared assets +Our team is small and the guys are working on this project as much as they can solely based on the enthusiasm they have for this project and the community. +- `@Leptopt1los` - NFC, RFID, Plugins, and many other things +- `@gid9798` - SubGHz, Plugins, many other things - currently offline :( +- `@assasinfil` - SubGHz protocols, NFC parsers +- `@Svaarich` - UI design and animations +- `@amec0e` - Infrared assets - Community moderators in Telegram, Discord, and Reddit - And of course our GitHub community. Your PRs are a very important part of this firmware and open-source development. The amount of work done on this project is huge and we need your support, no matter how large or small. Even if you just say, "Thank you Unleashed firmware developers!" somewhere. Doing so will help us continue our work and will help drive us to make the firmware better every time. -Also, regarding our releases, every build has and always will be free and open-source. There will be no paywall releases or closed-source apps within the firmware. As long as I am working on this project it will never happen. +Also, regarding our releases, every build has and always will be free and open-source. There will be no paywall releases or closed-source apps within the firmware. As long as I am working on this project it will never happen. You can support us by using links or addresses below: -|Service|Remark|QR Code|Link/Wallet| -|-|-|-|-| -|**Patreon**||
QR image
|https://patreon.com/mmxdev| -|**Boosty**|patreon alternative|
QR image
|https://boosty.to/mmxdev| -|cloudtips|only RU payments accepted|
QR image
|https://pay.cloudtips.ru/p/7b3e9d65| -|YooMoney|only RU payments accepted|
QR image
|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209| -|USDT|(TRC20)|
QR image
|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`| -|ETH|(BSC/ERC20-Tokens)|
QR image
|`0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`| -|BTC||
QR image
|`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`| -|SOL|(Solana/Tokens)|
QR image
|`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`| -|DOGE||
QR image
|`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`| -|LTC||
QR image
|`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`| -|BCH||
QR image
|`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`| -|XMR|(Monero)|
QR image
|`41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`| -|TON||
QR image
|`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`| -## Community apps included - -### [🎲 Download Extra plugins for Unleashed](https://github.com/xMasterX/all-the-plugins/releases/latest) -### [List of Extra pack](https://github.com/xMasterX/all-the-plugins/tree/dev#extra-pack) | [List of Base *(Default)* pack](https://github.com/xMasterX/all-the-plugins/tree/dev#default-pack) - -See full list and sources here: [xMasterX/all-the-plugins](https://github.com/xMasterX/all-the-plugins/tree/dev) - -### Official Flipper Zero Apps Catalog [web version](https://lab.flipper.net/apps) or mobile app - -# Instructions -## First look at official docs [docs.flipper.net](https://docs.flipper.net/) -## [How to install](/documentation/HowToInstall.md) - [versions info](/CHANGELOG.md#recommended-update-option---web-updater): `r`,` `,`e`... -## Firmware & Development - -### - **Developer Documentation** - [developer.flipper.net](https://developer.flipper.net/flipperzero/doxygen) - -### - **[How to build](/documentation/HowToBuild.md#how-to-build-by-yourself) | [Project-structure](#project-structure)** - -### - **CLion IDE** - How to setup workspace for flipper firmware development [by Savely Krasovsky](https://krasovs.ky/2022/11/01/flipper-zero-clion.html) - -### - **"Hello world!"** - plugin tutorial [English by DroomOne ](https://github.com/DroomOne/Flipper-Plugin-Tutorial) | [Russian by Pavel Yakovlev](https://yakovlev.me/hello-flipper-zero/) - -### - [How to write your own app](https://flipper.atmanos.com/docs/overview/intro). Docs by atmanos **⚠️outdated API** - -## Firmware & main Apps feature - -### - System: [How to change Flipper name](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/CustomFlipperName.md) - -### - BadUSB: [How to add new keyboard layouts](https://github.com/dummy-decoy/flipperzero_badusb_kl) - -### - Infrared: [How to make captures to add them into Universal IR remotes](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/InfraredCaptures.md) - -## **Sub-GHz** - -### - [How to use Flipper as new remote (Nice FlorS, BFT Mitto, Somfy Telis, Aprimatic, AN-Motors, etc..)](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) - -### - External Radio: [How to connect CC1101 module](https://github.com/quen0n/flipperzero-ext-cc1101) - -### - Transmission is blocked? [How to extend Sub-GHz frequency range](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/DangerousSettings.md) - -### - [How to add extra Sub-GHz frequencies](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzSettings.md) - -### - [~~Configure Sub-GHz Remote App~~](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemotePlugin.md) Not recommended, please use embedded configurator - -## **Plugins** - -### - TOTP (Authenticator): [config description](https://github.com/akopachov/flipper-zero_authenticator/blob/master/docs/conf-file_description.md) - -### - Barcode Generator: [How to use](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/BarcodeGenerator.md) - -### - Multi Converter: [How to use](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/MultiConverter.md) - -### - WAV Player: [sample files & how to convert](https://github.com/UberGuidoZ/Flipper/tree/main/Wav_Player#readme) - -### - Sub-GHz playlist: [generator script](https://github.com/darmiel/flipper-scripts/blob/main/playlist/playlist_creator_by_chunk.py) - -## **Plugins that works with external hardware** [GPIO] - -### - Unitemp - Temperature sensors reader: [How to use & supported sensors](https://github.com/quen0n/unitemp-flipperzero#readme) - -### - [NMEA] GPS: [How to use](https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/gps_nmea_uart/README.md) - -### - i2c Tools [How to use](https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/flipper_i2ctools/README.md) - -### - [NRF24] plugins: [How to use](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/NRF24.md) +| Service | Remark | QR Code | Link/Wallet | +|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------| +| Patreon **Patreon** | |
QR image
| [patreon.com/mmxdev](https://patreon.com/mmxdev) | +| Boosty **Boosty** | patreon alternative |
QR image
| [boosty.to/mmxdev](https://boosty.to/mmxdev) | +| Cloudtips CloudTips | only RU payments accepted |
QR image
| [pay.cloudtips.ru/p/7b3e9d65](https://pay.cloudtips.ru/p/7b3e9d65) | +| YooMoney YooMoney | only RU payments accepted |
QR image
| [yoomoney.ru/fundraise/XA49mgQLPA0.221209](https://yoomoney.ru/fundraise/XA49mgQLPA0.221209) | +| USDT USDT | TRC20 |
QR image
| `TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs` | +| ETH ETH | BSC/ERC20-Tokens |
QR image
| `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a` | +| BTC BTC | |
QR image
| `bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9` | +| SOL SOL | Solana/Tokens |
QR image
| `DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8` | +| DOGE DOGE | |
QR image
| `D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv` | +| LTC LTC | |
QR image
| `ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9` | +| BCH BCH | |
QR image
| `qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3` | +| XMR XMR | Monero |
QR image
| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn` | +| TON TON | |
QR image
| `UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa` | -### - [WiFi] Scanner: [How to use](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module#readme) | [Web Flasher](https://sequoiasan.github.io/FlipperZero-WiFi-Scanner_Module/) +## 📱 Community Apps -### - [ESP8266] Deauther: [How to use](https://github.com/SequoiaSan/FlipperZero-Wifi-ESP8266-Deauther-Module#readme) | [Web Flasher](https://sequoiasan.github.io/FlipperZero-Wifi-ESP8266-Deauther-Module/) +Enhance your Flipper Zero with apps and plugins created by the community: -### - [ESP32] WiFi Marauder: [How to use](https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard) docs by UberGuidoZ | [Marauder repo](https://github.com/justcallmekoko/ESP32Marauder) +- **Extra Plugins & Packs:** + Check out the latest extra plugins and plugin packs (Extra Pack and Base Pack) on [GitHub](https://github.com/xMasterX/all-the-plugins/releases/latest). -### - [ESP32-CAM] Camera Suite: [How to use](https://github.com/CodyTolene/Flipper-Zero-Camera-Suite) +- **Source Code & Full List:** + Find the complete list and source code at [xMasterX/all-the-plugins](https://github.com/xMasterX/all-the-plugins/tree/dev). -### - How to Upload `.bin` to ESP32/ESP8266: [Windows](https://github.com/SequoiaSan/Guide-How-To-Upload-bin-to-ESP8266-ESP32) | [FAP "ESP flasher"](https://github.com/0xchocolate/flipperzero-esp-flasher) +- **Official Apps Catalog:** + Browse the official Flipper Zero Apps Catalog on the [web](https://lab.flipper.net/apps) or via the [mobile app](https://flipperzero.one/downloads). -### - [GPIO] SentrySafe plugin: [How to use](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SentrySafe.md) -
-
+## 📁 Where I can find IR, Sub-GHz, ... files, DBs, and other stuff? +- [UberGuidoZ Playground - Large collection of files - Github](https://github.com/UberGuidoZ/Flipper) +- [Awesome Flipper Zero - Github](https://github.com/djsime1/awesome-flipperzero) -# Where I can find IR, Sub-GHz, ... files, DBs, and other stuff? -## [UberGuidoZ Playground - Large collection of files - Github](https://github.com/UberGuidoZ/Flipper) -## [Awesome Flipper Zero - Github](https://github.com/djsime1/awesome-flipperzero) -
-
+## 📘 Instructions -# Links +### ![Tools Icon Badge] Firmware & main Apps feature -* Official Docs: [docs.flipper.net](https://docs.flipper.net/) -* Official Forum: [forum.flipperzero.one](https://forum.flipperzero.one/) +- System: [How to change Flipper name](/documentation/CustomFlipperName.md) +- BadUSB: [How to add new keyboard layouts](https://github.com/dummy-decoy/flipperzero_badusb_kl) +- Infrared: [How to make captures to add them into Universal IR remotes](/documentation/InfraredCaptures.md) -* Update! Developer Documentation [developer.flipper.net](https://developer.flipper.net/flipperzero/doxygen) +### ![SubGhz Icon Badge] Sub-GHz -# Project structure +- [How to use Flipper as new remote (Nice FlorS, BFT Mitto, Somfy Telis, Aprimatic, AN-Motors, etc..)](/documentation/SubGHzRemoteProg.md) +- External Radio: [How to connect CC1101 module](https://github.com/quen0n/flipperzero-ext-cc1101) +- Transmission is blocked? [How to extend Sub-GHz frequency range](/documentation/DangerousSettings.md) +- [How to add extra Sub-GHz frequencies](/documentation/SubGHzSettings.md) +- [~~Configure Sub-GHz Remote App~~](/documentation/SubGHzRemotePlugin.md) ⚠️ Not recommended, please use embedded configurator + +### ![Plugins Icon Badge] Plugins + +- TOTP (Authenticator): [config description](https://github.com/akopachov/flipper-zero_authenticator/blob/master/docs/conf-file_description.md) +- Barcode Generator: [How to use](/documentation/BarcodeGenerator.md) +- Multi Converter: [How to use](/documentation/MultiConverter.md) +- WAV Player: [sample files & how to convert](https://github.com/UberGuidoZ/Flipper/tree/main/Wav_Player#readme) +- Sub-GHz playlist: [generator script](https://github.com/darmiel/flipper-scripts/blob/main/playlist/playlist_creator_by_chunk.py) + +### ![GPIO Icon Badge] GPIO - Plugins that works with external hardware + +- Unitemp - Temperature sensors reader: [How to use & supported sensors](https://github.com/quen0n/unitemp-flipperzero#readme) +- [NMEA] GPS: [How to use](https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/gps_nmea_uart/README.md) +- i2c Tools [How to use](https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/flipper_i2ctools/README.md) +- [NRF24] plugins: [How to use](/documentation/NRF24.md) +- [WiFi] Scanner: [How to use](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module#readme) | [Web Flasher](https://sequoiasan.github.io/FlipperZero-WiFi-Scanner_Module/) +- [ESP8266] Deauther: [How to use](https://github.com/SequoiaSan/FlipperZero-Wifi-ESP8266-Deauther-Module#readme) | [Web Flasher](https://sequoiasan.github.io/FlipperZero-Wifi-ESP8266-Deauther-Module/) +- [ESP32] WiFi Marauder: [How to use](https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard) docs by UberGuidoZ | [Marauder repo](https://github.com/justcallmekoko/ESP32Marauder) +- [ESP32-CAM] Camera Suite: [How to use](https://github.com/CodyTolene/Flipper-Zero-Camera-Suite) +- How to Upload `.bin` to ESP32/ESP8266: [Windows](https://github.com/SequoiaSan/Guide-How-To-Upload-bin-to-ESP8266-ESP32) | [FAP "ESP flasher"](https://github.com/0xchocolate/flipperzero-esp-flasher) +- [GPIO] SentrySafe plugin: [How to use](/documentation/SentrySafe.md) + + +## 👨‍💻 Firmware & Development + +- **Developer Documentation** - [developer.flipper.net](https://developer.flipper.net/flipperzero/doxygen) +- **[How to build](/documentation/HowToBuild.md#how-to-build-by-yourself) | [Project-structure](#project-structure)** +- **CLion IDE** - How to setup workspace for flipper firmware development [by Savely Krasovsky](https://krasovs.ky/2022/11/01/flipper-zero-clion.html) +- **"Hello world!"** - plugin tutorial [English by DroomOne ](https://github.com/DroomOne/Flipper-Plugin-Tutorial) | [Russian by Pavel Yakovlev](https://yakovlev.me/hello-flipper-zero) +- [How to write your own app](https://flipper.atmanos.com/docs/overview/intro). + +### Project structure - `applications` - Applications and services used in firmware - `assets` - Assets used by applications and services @@ -273,3 +311,34 @@ See full list and sources here: [xMasterX/all-the-plugins](https://github.com/xM - `scripts` - Supplementary scripts and python libraries home Also, pay attention to the `ReadMe.md` files inside those directories. + + +## 🔗 Links +- **Unleashed web page:** [flipperunleashed.com](https://flipperunleashed.com) +- **Unleashed update server, direct .tgz update links for web updater or direct download:** [unleashedflip.com](https://unleashedflip.com) + +- Official Docs: [docs.flipper.net](https://docs.flipper.net) +- Official Forum: [forum.flipperzero.one](https://forum.flipperzero.one) +- Update! Developer Documentation [developer.flipper.net](https://developer.flipper.net/flipperzero/doxygen) + + +[RFID Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(255,244,147)?style=flat&logo=fz-rfid&logoColor=black +[iButton Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(225,187,166)?style=flat&logo=fz-ibutton&logoColor=black +[SubGhz Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(165,244,190)?style=flat&logo=fz-subghz&logoColor=black + +[GPIO Badge]: https://custom-icon-badges.demolab.com/badge/-GPIO-rgb(167,242,234)?style=flat&logo=fz-gpio&logoColor=black +[GPIO Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(167,242,234)?style=flat&logo=fz-gpio&logoColor=black + +[Tools Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(223,241,89)?style=flat&logo=fz-tools&logoColor=black +[Media Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(223,181,255)?style=flat&logo=fz-media&logoColor=black +[BT Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(139,172,255)?style=flat&logo=fz-bluetooth&logoColor=black +[NFC Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(152,206,254)?style=flat&logo=fz-nfc&logoColor=black +[USB Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(255,190,233)?style=flat&logo=fz-badusb&logoColor=black +[IR Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(254,147,140)?style=flat&logo=fz-infrared&logoColor=black +[Games Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(255,196,134)?style=flat&logo=fz-games&logoColor=black +[Plugins Icon Badge]: https://custom-icon-badges.demolab.com/badge/-rgb(226,78,178)?style=flat&logo=fz-plugins&logoColor=black + +[UFW Icon Badge]: https://img.shields.io/badge/by_UFW-%2314D411?style=flat-square +[Official Icon Badge]: https://img.shields.io/badge/by_OFW-%23FF8200?style=flat-square +[Author Icon Badge]: https://img.shields.io/badge/by_author-%23FFFF00?style=flat-square +[None Icon Badge]: https://img.shields.io/badge/None-%23FF0000?style=flat-square diff --git a/SConstruct b/SConstruct index 597e42c61..63643d2c1 100644 --- a/SConstruct +++ b/SConstruct @@ -412,6 +412,21 @@ distenv.PhonyTarget( ], ) + +# Measure CLI loopback performance +distenv.PhonyTarget( + "cli_perf", + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/serial_cli_perf.py", + "-p", + "${FLIP_PORT}", + "${ARGS}", + ] + ], +) + # Update WiFi devboard firmware with release channel distenv.PhonyTarget( "devboard_flash", diff --git a/applications/ReadMe.md b/applications/ReadMe.md index a3567df71..02469f252 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -67,6 +67,7 @@ Small applications providing configuration for basic firmware and its services. - `power_settings_app` - Basic power options - `storage_settings` - Storage settings app - `system` - System settings +- `input_settings_app` - Basic input options ## system diff --git a/applications/debug/accessor/accessor_app.cpp b/applications/debug/accessor/accessor_app.cpp index 8d43acc13..59f5d6cc7 100644 --- a/applications/debug/accessor/accessor_app.cpp +++ b/applications/debug/accessor/accessor_app.cpp @@ -2,6 +2,7 @@ #include #include #include +#include void AccessorApp::run(void) { AccessorEvent event; @@ -35,16 +36,18 @@ AccessorApp::AccessorApp() : text_store{0} { notification = static_cast(furi_record_open(RECORD_NOTIFICATION)); expansion = static_cast(furi_record_open(RECORD_EXPANSION)); + power = static_cast(furi_record_open(RECORD_POWER)); onewire_host = onewire_host_alloc(&gpio_ibutton); expansion_disable(expansion); - furi_hal_power_enable_otg(); + power_enable_otg(power, true); } AccessorApp::~AccessorApp() { - furi_hal_power_disable_otg(); + power_enable_otg(power, false); expansion_enable(expansion); furi_record_close(RECORD_EXPANSION); furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_POWER); onewire_host_free(onewire_host); } diff --git a/applications/debug/accessor/accessor_app.h b/applications/debug/accessor/accessor_app.h index 890552f5f..1961f9cbf 100644 --- a/applications/debug/accessor/accessor_app.h +++ b/applications/debug/accessor/accessor_app.h @@ -7,6 +7,7 @@ #include #include #include +#include class AccessorApp { public: @@ -53,4 +54,5 @@ private: NotificationApp* notification; Expansion* expansion; + Power* power; }; diff --git a/applications/debug/accessor/application.fam b/applications/debug/accessor/application.fam index 65a6c8666..4b24f98eb 100644 --- a/applications/debug/accessor/application.fam +++ b/applications/debug/accessor/application.fam @@ -6,6 +6,5 @@ App( entry_point="accessor_app", requires=["gui"], stack_size=4 * 1024, - order=40, fap_category="Debug", ) diff --git a/applications/debug/battery_test_app/application.fam b/applications/debug/battery_test_app/application.fam index 5f4acd83d..0ab68c086 100644 --- a/applications/debug/battery_test_app/application.fam +++ b/applications/debug/battery_test_app/application.fam @@ -8,7 +8,6 @@ App( "power", ], stack_size=1 * 1024, - order=130, fap_category="Debug", fap_libs=["assets"], ) diff --git a/applications/debug/blink_test/application.fam b/applications/debug/blink_test/application.fam index d7d873fb9..066e7a207 100644 --- a/applications/debug/blink_test/application.fam +++ b/applications/debug/blink_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="blink_test_app", requires=["gui"], stack_size=1 * 1024, - order=10, fap_category="Debug", ) diff --git a/applications/debug/bt_debug_app/application.fam b/applications/debug/bt_debug_app/application.fam index 8ed1ccc05..831b51ade 100644 --- a/applications/debug/bt_debug_app/application.fam +++ b/applications/debug/bt_debug_app/application.fam @@ -13,6 +13,5 @@ App( "bt_debug", ], stack_size=1 * 1024, - order=110, fap_category="Debug", ) diff --git a/applications/debug/ccid_test/application.fam b/applications/debug/ccid_test/application.fam index ad9076770..dfd6de05f 100644 --- a/applications/debug/ccid_test/application.fam +++ b/applications/debug/ccid_test/application.fam @@ -10,6 +10,5 @@ App( "ccid_test", ], stack_size=1 * 1024, - order=120, fap_category="Debug", ) diff --git a/applications/debug/crash_test/crash_test.c b/applications/debug/crash_test/crash_test.c index 2b2be13d6..4c5a53ceb 100644 --- a/applications/debug/crash_test/crash_test.c +++ b/applications/debug/crash_test/crash_test.c @@ -24,8 +24,49 @@ typedef enum { CrashTestSubmenuAssertMessage, CrashTestSubmenuCrash, CrashTestSubmenuHalt, + CrashTestSubmenuHeapUnderflow, + CrashTestSubmenuHeapOverflow, } CrashTestSubmenu; +static void crash_test_corrupt_heap_underflow(void) { + const size_t block_size = 1000; + const size_t underflow_size = 123; + uint8_t* block = malloc(block_size); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" // that's what we want! + memset(block - underflow_size, 0xDD, underflow_size); // -V769 +#pragma GCC diagnostic pop + + free(block); // should crash here (if compiled with DEBUG=1) + + // If we got here, the heap wasn't able to detect our corruption and crash + furi_crash("Test failed, should've crashed with \"FreeRTOS Assert\" error"); +} + +static void crash_test_corrupt_heap_overflow(void) { + const size_t block_size = 1000; + const size_t overflow_size = 123; + uint8_t* block1 = malloc(block_size); + uint8_t* block2 = malloc(block_size); + memset(block2, 12, 34); // simulate use to avoid optimization // -V597 // -V1086 + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" // that's what we want! + memset(block1 + block_size, 0xDD, overflow_size); // -V769 // -V512 +#pragma GCC diagnostic pop + + uint8_t* block3 = malloc(block_size); + memset(block3, 12, 34); // simulate use to avoid optimization // -V597 // -V1086 + + free(block3); // should crash here (if compiled with DEBUG=1) + free(block2); + free(block1); + + // If we got here, the heap wasn't able to detect our corruption and crash + furi_crash("Test failed, should've crashed with \"FreeRTOS Assert\" error"); +} + static void crash_test_submenu_callback(void* context, uint32_t index) { CrashTest* instance = (CrashTest*)context; UNUSED(instance); @@ -49,6 +90,12 @@ static void crash_test_submenu_callback(void* context, uint32_t index) { case CrashTestSubmenuHalt: furi_halt("Crash test: furi_halt"); break; + case CrashTestSubmenuHeapUnderflow: + crash_test_corrupt_heap_underflow(); + break; + case CrashTestSubmenuHeapOverflow: + crash_test_corrupt_heap_overflow(); + break; default: furi_crash(); } @@ -94,6 +141,18 @@ CrashTest* crash_test_alloc(void) { instance->submenu, "Crash", CrashTestSubmenuCrash, crash_test_submenu_callback, instance); submenu_add_item( instance->submenu, "Halt", CrashTestSubmenuHalt, crash_test_submenu_callback, instance); + submenu_add_item( + instance->submenu, + "Heap underflow", + CrashTestSubmenuHeapUnderflow, + crash_test_submenu_callback, + instance); + submenu_add_item( + instance->submenu, + "Heap overflow", + CrashTestSubmenuHeapOverflow, + crash_test_submenu_callback, + instance); return instance; } diff --git a/applications/debug/direct_draw/application.fam b/applications/debug/direct_draw/application.fam index 11b3bc6ba..1e7d4b1c4 100644 --- a/applications/debug/direct_draw/application.fam +++ b/applications/debug/direct_draw/application.fam @@ -5,6 +5,5 @@ App( entry_point="direct_draw_app", requires=["gui", "input"], stack_size=2 * 1024, - order=70, fap_category="Debug", ) diff --git a/applications/debug/display_test/application.fam b/applications/debug/display_test/application.fam index 7b2357b01..1e0d3f775 100644 --- a/applications/debug/display_test/application.fam +++ b/applications/debug/display_test/application.fam @@ -6,6 +6,5 @@ App( requires=["gui"], fap_libs=["u8g2"], stack_size=1 * 1024, - order=120, fap_category="Debug", ) diff --git a/applications/debug/event_loop_blink_test/application.fam b/applications/debug/event_loop_blink_test/application.fam index 7d42ad339..6e4aaa48d 100644 --- a/applications/debug/event_loop_blink_test/application.fam +++ b/applications/debug/event_loop_blink_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="event_loop_blink_test_app", requires=["input"], stack_size=1 * 1024, - order=20, fap_category="Debug", ) diff --git a/applications/debug/expansion_test/application.fam b/applications/debug/expansion_test/application.fam index 9bc4b2fc2..30f325a92 100644 --- a/applications/debug/expansion_test/application.fam +++ b/applications/debug/expansion_test/application.fam @@ -6,7 +6,6 @@ App( requires=["expansion_start"], fap_libs=["assets"], stack_size=1 * 1024, - order=20, fap_category="Debug", fap_file_assets="assets", ) diff --git a/applications/debug/file_browser_test/application.fam b/applications/debug/file_browser_test/application.fam index bb08ad2c5..b610558e9 100644 --- a/applications/debug/file_browser_test/application.fam +++ b/applications/debug/file_browser_test/application.fam @@ -5,7 +5,6 @@ App( entry_point="file_browser_app", requires=["gui"], stack_size=2 * 1024, - order=150, fap_category="Debug", fap_icon_assets="icons", ) diff --git a/applications/debug/keypad_test/application.fam b/applications/debug/keypad_test/application.fam index 90851950b..ed7408e71 100644 --- a/applications/debug/keypad_test/application.fam +++ b/applications/debug/keypad_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="keypad_test_app", requires=["gui"], stack_size=1 * 1024, - order=30, fap_category="Debug", ) diff --git a/applications/debug/lfrfid_debug/application.fam b/applications/debug/lfrfid_debug/application.fam index 323f77818..d312dbda2 100644 --- a/applications/debug/lfrfid_debug/application.fam +++ b/applications/debug/lfrfid_debug/application.fam @@ -11,6 +11,5 @@ App( "lfrfid_debug", ], stack_size=1 * 1024, - order=100, fap_category="Debug", ) diff --git a/applications/debug/loader_chaining_a/application.fam b/applications/debug/loader_chaining_a/application.fam new file mode 100644 index 000000000..408efdcb1 --- /dev/null +++ b/applications/debug/loader_chaining_a/application.fam @@ -0,0 +1,8 @@ +App( + appid="loader_chaining_a", + name="Loader Chaining Test: App A", + apptype=FlipperAppType.DEBUG, + entry_point="chaining_test_app_a", + stack_size=1 * 1024, + fap_category="Debug", +) diff --git a/applications/debug/loader_chaining_a/loader_chaining_a.c b/applications/debug/loader_chaining_a/loader_chaining_a.c new file mode 100644 index 000000000..b3f303e2d --- /dev/null +++ b/applications/debug/loader_chaining_a/loader_chaining_a.c @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include +#include + +#define TAG "LoaderChainingA" +#define CHAINING_TEST_B "/ext/apps/Debug/loader_chaining_b.fap" +#define NONEXISTENT_APP "Some nonexistent app" + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + Submenu* submenu; + + Loader* loader; + + DialogsApp* dialogs; +} LoaderChainingA; + +typedef enum { + LoaderChainingASubmenuLaunchB, + LoaderChainingASubmenuLaunchBThenA, + LoaderChainingASubmenuLaunchNonexistentSilent, + LoaderChainingASubmenuLaunchNonexistentGui, + LoaderChainingASubmenuLaunchNonexistentGuiThenA, +} LoaderChainingASubmenu; + +static void loader_chaining_a_submenu_callback(void* context, uint32_t index) { + LoaderChainingA* app = context; + + FuriString* self_path = furi_string_alloc(); + furi_check(loader_get_application_launch_path(app->loader, self_path)); + + switch(index) { + case LoaderChainingASubmenuLaunchB: + loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui); + view_dispatcher_stop(app->view_dispatcher); + break; + + case LoaderChainingASubmenuLaunchBThenA: + loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui); + loader_enqueue_launch( + app->loader, + furi_string_get_cstr(self_path), + "Hello to you from the future", + LoaderDeferredLaunchFlagGui); + + break; + + case LoaderChainingASubmenuLaunchNonexistentSilent: + loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagNone); + break; + + case LoaderChainingASubmenuLaunchNonexistentGui: + loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui); + break; + + case LoaderChainingASubmenuLaunchNonexistentGuiThenA: + loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui); + loader_enqueue_launch( + app->loader, + furi_string_get_cstr(self_path), + "Hello to you from the future", + LoaderDeferredLaunchFlagGui); + break; + } + + furi_string_free(self_path); + view_dispatcher_stop(app->view_dispatcher); +} + +static bool loader_chaining_a_nav_callback(void* context) { + LoaderChainingA* app = context; + view_dispatcher_stop(app->view_dispatcher); + return true; +} + +LoaderChainingA* loader_chaining_a_alloc(void) { + LoaderChainingA* app = malloc(sizeof(LoaderChainingA)); + app->gui = furi_record_open(RECORD_GUI); + app->loader = furi_record_open(RECORD_LOADER); + app->dialogs = furi_record_open(RECORD_DIALOGS); + app->view_dispatcher = view_dispatcher_alloc(); + app->submenu = submenu_alloc(); + + submenu_add_item( + app->submenu, + "Launch B", + LoaderChainingASubmenuLaunchB, + loader_chaining_a_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Launch B, then A", + LoaderChainingASubmenuLaunchBThenA, + loader_chaining_a_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Trigger error: silent", + LoaderChainingASubmenuLaunchNonexistentSilent, + loader_chaining_a_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Trigger error: GUI", + LoaderChainingASubmenuLaunchNonexistentGui, + loader_chaining_a_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Error, then launch A", + LoaderChainingASubmenuLaunchNonexistentGuiThenA, + loader_chaining_a_submenu_callback, + app); + + view_dispatcher_add_view(app->view_dispatcher, 0, submenu_get_view(app->submenu)); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, loader_chaining_a_nav_callback); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_switch_to_view(app->view_dispatcher, 0); + + return app; +} + +void loader_chaining_a_free(LoaderChainingA* app) { + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_LOADER); + furi_record_close(RECORD_GUI); + view_dispatcher_remove_view(app->view_dispatcher, 0); + submenu_free(app->submenu); + view_dispatcher_free(app->view_dispatcher); + free(app); +} + +int32_t chaining_test_app_a(const char* arg) { + LoaderChainingA* app = loader_chaining_a_alloc(); + + if(arg) { + if(strlen(arg)) { + DialogMessage* message = dialog_message_alloc(); + FuriString* text; + + dialog_message_set_header(message, "Hi, I am A", 64, 0, AlignCenter, AlignTop); + text = furi_string_alloc_printf("Me from the past says:\n%s", arg); + dialog_message_set_buttons(message, NULL, "ok!", NULL); + + dialog_message_set_text( + message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter); + dialog_message_show(app->dialogs, message); + dialog_message_free(message); + furi_string_free(text); + } + } + + view_dispatcher_run(app->view_dispatcher); + + loader_chaining_a_free(app); + return 0; +} diff --git a/applications/debug/loader_chaining_b/application.fam b/applications/debug/loader_chaining_b/application.fam new file mode 100644 index 000000000..5b8767e50 --- /dev/null +++ b/applications/debug/loader_chaining_b/application.fam @@ -0,0 +1,8 @@ +App( + appid="loader_chaining_b", + name="Loader Chaining Test: App B", + apptype=FlipperAppType.DEBUG, + entry_point="chaining_test_app_b", + stack_size=1 * 1024, + fap_category="Debug", +) diff --git a/applications/debug/loader_chaining_b/loader_chaining_b.c b/applications/debug/loader_chaining_b/loader_chaining_b.c new file mode 100644 index 000000000..439e6e25e --- /dev/null +++ b/applications/debug/loader_chaining_b/loader_chaining_b.c @@ -0,0 +1,27 @@ +#include +#include +#include + +int32_t chaining_test_app_b(const char* arg) { + if(!arg) return 0; + + Loader* loader = furi_record_open(RECORD_LOADER); + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Hi, I am B", 64, 0, AlignCenter, AlignTop); + FuriString* text = furi_string_alloc_printf("And A told me:\n%s", arg); + dialog_message_set_text(message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter); + dialog_message_set_buttons(message, "Just quit", NULL, "Launch A"); + DialogMessageButton result = dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_string_free(text); + + if(result == DialogMessageButtonRight) + loader_enqueue_launch( + loader, "/ext/apps/Debug/loader_chaining_a.fap", NULL, LoaderDeferredLaunchFlagGui); + + furi_record_close(RECORD_LOADER); + furi_record_close(RECORD_DIALOGS); + return 0; +} diff --git a/applications/debug/locale_test/application.fam b/applications/debug/locale_test/application.fam index d341122f9..757be7155 100644 --- a/applications/debug/locale_test/application.fam +++ b/applications/debug/locale_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="locale_test_app", requires=["gui", "locale"], stack_size=2 * 1024, - order=70, fap_category="Debug", ) diff --git a/applications/debug/rpc_debug_app/application.fam b/applications/debug/rpc_debug_app/application.fam index d71065afa..795f83287 100644 --- a/applications/debug/rpc_debug_app/application.fam +++ b/applications/debug/rpc_debug_app/application.fam @@ -5,6 +5,5 @@ App( entry_point="rpc_debug_app", requires=["gui", "rpc_start", "notification"], stack_size=2 * 1024, - order=10, fap_category="Debug", ) diff --git a/applications/debug/speaker_debug/application.fam b/applications/debug/speaker_debug/application.fam index 68d8b188b..c7f5629a7 100644 --- a/applications/debug/speaker_debug/application.fam +++ b/applications/debug/speaker_debug/application.fam @@ -5,7 +5,6 @@ App( entry_point="speaker_debug_app", requires=["gui", "notification"], stack_size=2 * 1024, - order=10, fap_category="Debug", fap_libs=["music_worker"], ) diff --git a/applications/debug/speaker_debug/speaker_debug.c b/applications/debug/speaker_debug/speaker_debug.c index 3f685ab30..6a9956b07 100644 --- a/applications/debug/speaker_debug/speaker_debug.c +++ b/applications/debug/speaker_debug/speaker_debug.c @@ -1,8 +1,10 @@ #include #include #include -#include #include +#include +#include +#include #define TAG "SpeakerDebug" @@ -19,14 +21,14 @@ typedef struct { typedef struct { MusicWorker* music_worker; FuriMessageQueue* message_queue; - Cli* cli; + CliRegistry* cli_registry; } SpeakerDebugApp; static SpeakerDebugApp* speaker_app_alloc(void) { SpeakerDebugApp* app = (SpeakerDebugApp*)malloc(sizeof(SpeakerDebugApp)); app->music_worker = music_worker_alloc(); app->message_queue = furi_message_queue_alloc(8, sizeof(SpeakerDebugAppMessage)); - app->cli = furi_record_open(RECORD_CLI); + app->cli_registry = furi_record_open(RECORD_CLI); return app; } @@ -37,8 +39,8 @@ static void speaker_app_free(SpeakerDebugApp* app) { free(app); } -static void speaker_app_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void speaker_app_cli(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); SpeakerDebugApp* app = (SpeakerDebugApp*)context; SpeakerDebugAppMessage message; @@ -95,7 +97,8 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) { return; } - cli_add_command(app->cli, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app); + cli_registry_add_command( + app->cli_registry, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app); SpeakerDebugAppMessage message; FuriStatus status; @@ -110,7 +113,7 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) { } } - cli_delete_command(app->cli, CLI_COMMAND); + cli_registry_delete_command(app->cli_registry, CLI_COMMAND); } int32_t speaker_debug_app(void* arg) { diff --git a/applications/debug/subghz_test/application.fam b/applications/debug/subghz_test/application.fam index 1b3e19d73..927ca7f89 100644 --- a/applications/debug/subghz_test/application.fam +++ b/applications/debug/subghz_test/application.fam @@ -6,7 +6,6 @@ App( entry_point="subghz_test_app", requires=["gui"], stack_size=4 * 1024, - order=50, fap_icon="subghz_test_10px.png", fap_category="Debug", fap_icon_assets="images", diff --git a/applications/debug/text_box_element_test/application.fam b/applications/debug/text_box_element_test/application.fam index 5e1abcddc..78dfe75f6 100644 --- a/applications/debug/text_box_element_test/application.fam +++ b/applications/debug/text_box_element_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="text_box_element_test_app", requires=["gui"], stack_size=1 * 1024, - order=140, fap_category="Debug", ) diff --git a/applications/debug/text_box_view_test/application.fam b/applications/debug/text_box_view_test/application.fam index e356a278e..6a3225d88 100644 --- a/applications/debug/text_box_view_test/application.fam +++ b/applications/debug/text_box_view_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="text_box_view_test_app", requires=["gui"], stack_size=1 * 1024, - order=140, fap_category="Debug", ) diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index 1d5a473ca..87e213d06 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -16,6 +16,9 @@ #define LINES_ON_SCREEN 6 #define COLUMNS_ON_SCREEN 21 #define DEFAULT_BAUD_RATE 230400 +#define DEFAULT_DATA_BITS FuriHalSerialDataBits8 +#define DEFAULT_PARITY FuriHalSerialParityNone +#define DEFAULT_STOP_BITS FuriHalSerialStopBits1 typedef struct UartDumpModel UartDumpModel; @@ -49,11 +52,12 @@ typedef enum { WorkerEventRxOverrunError = (1 << 4), WorkerEventRxFramingError = (1 << 5), WorkerEventRxNoiseError = (1 << 6), + WorkerEventRxParityError = (1 << 7), } WorkerEventFlags; #define WORKER_EVENTS_MASK \ (WorkerEventStop | WorkerEventRxData | WorkerEventRxIdle | WorkerEventRxOverrunError | \ - WorkerEventRxFramingError | WorkerEventRxNoiseError) + WorkerEventRxFramingError | WorkerEventRxNoiseError | WorkerEventRxParityError) const NotificationSequence sequence_notification = { &message_display_backlight_on, @@ -62,6 +66,13 @@ const NotificationSequence sequence_notification = { NULL, }; +const NotificationSequence sequence_error = { + &message_display_backlight_on, + &message_red_255, + &message_delay_10, + NULL, +}; + static void uart_echo_view_draw_callback(Canvas* canvas, void* _model) { UartDumpModel* model = _model; @@ -133,6 +144,9 @@ static void if(event & FuriHalSerialRxEventOverrunError) { flag |= WorkerEventRxOverrunError; } + if(event & FuriHalSerialRxEventParityError) { + flag |= WorkerEventRxParityError; + } furi_thread_flags_set(furi_thread_get_id(app->worker_thread), flag); } @@ -227,13 +241,21 @@ static int32_t uart_echo_worker(void* context) { if(events & WorkerEventRxNoiseError) { furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect NE\r\n", 13); } + if(events & WorkerEventRxParityError) { + furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect PE\r\n", 13); + } + notification_message(app->notification, &sequence_error); } } return 0; } -static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) { +static UartEchoApp* uart_echo_app_alloc( + uint32_t baudrate, + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits) { UartEchoApp* app = malloc(sizeof(UartEchoApp)); app->rx_stream = furi_stream_buffer_alloc(2048, 1); @@ -275,6 +297,7 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) { app->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart); furi_check(app->serial_handle); furi_hal_serial_init(app->serial_handle, baudrate); + furi_hal_serial_configure_framing(app->serial_handle, data_bits, parity, stop_bits); furi_hal_serial_async_rx_start(app->serial_handle, uart_echo_on_irq_cb, app, true); @@ -319,19 +342,76 @@ static void uart_echo_app_free(UartEchoApp* app) { free(app); } +// silences "same-assignment" false positives in the arg parser below +// -V::1048 + int32_t uart_echo_app(void* p) { uint32_t baudrate = DEFAULT_BAUD_RATE; + FuriHalSerialDataBits data_bits = DEFAULT_DATA_BITS; + FuriHalSerialParity parity = DEFAULT_PARITY; + FuriHalSerialStopBits stop_bits = DEFAULT_STOP_BITS; + if(p) { - const char* baudrate_str = p; - if(strint_to_uint32(baudrate_str, NULL, &baudrate, 10) != StrintParseNoError) { - FURI_LOG_E(TAG, "Invalid baudrate: %s", baudrate_str); - baudrate = DEFAULT_BAUD_RATE; + // parse argument + char* parse_ptr = p; + bool parse_success = false; + + do { + if(strint_to_uint32(parse_ptr, &parse_ptr, &baudrate, 10) != StrintParseNoError) break; + + if(*(parse_ptr++) != '_') break; + + uint16_t data_bits_int; + if(strint_to_uint16(parse_ptr, &parse_ptr, &data_bits_int, 10) != StrintParseNoError) + break; + if(data_bits_int == 6) + data_bits = FuriHalSerialDataBits6; + else if(data_bits_int == 7) + data_bits = FuriHalSerialDataBits7; + else if(data_bits_int == 8) + data_bits = FuriHalSerialDataBits8; + else if(data_bits_int == 9) + data_bits = FuriHalSerialDataBits9; + else + break; + + char parity_char = *(parse_ptr++); + if(parity_char == 'N') + parity = FuriHalSerialParityNone; + else if(parity_char == 'E') + parity = FuriHalSerialParityEven; + else if(parity_char == 'O') + parity = FuriHalSerialParityOdd; + else + break; + + uint16_t stop_bits_int; + if(strint_to_uint16(parse_ptr, &parse_ptr, &stop_bits_int, 10) != StrintParseNoError) + break; + if(stop_bits_int == 5) + stop_bits = FuriHalSerialStopBits0_5; + else if(stop_bits_int == 1) + stop_bits = FuriHalSerialStopBits1; + else if(stop_bits_int == 15) + stop_bits = FuriHalSerialStopBits1_5; + else if(stop_bits_int == 2) + stop_bits = FuriHalSerialStopBits2; + else + break; + + parse_success = true; + } while(0); + + if(!parse_success) { + FURI_LOG_I( + TAG, + "Couldn't parse baud rate and framing (%s). Applying defaults (%d_8N1)", + (const char*)p, + DEFAULT_BAUD_RATE); } } - FURI_LOG_I(TAG, "Using baudrate: %lu", baudrate); - - UartEchoApp* app = uart_echo_app_alloc(baudrate); + UartEchoApp* app = uart_echo_app_alloc(baudrate, data_bits, parity, stop_bits); view_dispatcher_run(app->view_dispatcher); uart_echo_app_free(app); return 0; diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index f92d7e66f..72b8cafcb 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -4,10 +4,10 @@ App( entry_point="unit_tests_on_system_start", sources=["unit_tests.c", "test_runner.c", "unit_test_api_table.cpp"], cdefines=["APP_UNIT_TESTS"], - requires=["system_settings", "subghz_start"], + requires=["system_settings", "cli_subghz"], provides=["delay_test"], resources="resources", - order=100, + order=30, ) App( @@ -18,7 +18,7 @@ App( entry_point="delay_test_app", stack_size=1 * 1024, requires=["unit_tests"], - order=110, + order=30, ) App( diff --git a/applications/debug/unit_tests/resources/unit_tests/js/basic.js b/applications/debug/unit_tests/resources/unit_tests/js/basic.js index a08041e9f..0ef904ecb 100644 --- a/applications/debug/unit_tests/resources/unit_tests/js/basic.js +++ b/applications/debug/unit_tests/resources/unit_tests/js/basic.js @@ -12,4 +12,4 @@ tests.assert_eq(false, doesSdkSupport(["abobus", "other-nonexistent-feature"])); tests.assert_eq("flipperdevices", flipper.firmwareVendor); tests.assert_eq(0, flipper.jsSdkVersion[0]); -tests.assert_eq(1, flipper.jsSdkVersion[1]); +tests.assert_eq(3, flipper.jsSdkVersion[1]); diff --git a/applications/debug/unit_tests/test_runner.c b/applications/debug/unit_tests/test_runner.c index de29e91b3..ad7d31b02 100644 --- a/applications/debug/unit_tests/test_runner.c +++ b/applications/debug/unit_tests/test_runner.c @@ -2,8 +2,9 @@ #include "tests/test_api.h" -#include +#include #include +#include #include #include #include @@ -25,7 +26,7 @@ struct TestRunner { NotificationApp* notification; // Temporary used things - Cli* cli; + PipeSide* pipe; FuriString* args; // ELF related stuff @@ -38,14 +39,14 @@ struct TestRunner { int minunit_status; }; -TestRunner* test_runner_alloc(Cli* cli, FuriString* args) { +TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args) { TestRunner* instance = malloc(sizeof(TestRunner)); instance->storage = furi_record_open(RECORD_STORAGE); instance->loader = furi_record_open(RECORD_LOADER); instance->notification = furi_record_open(RECORD_NOTIFICATION); - instance->cli = cli; + instance->pipe = pipe; instance->args = args; instance->composite_resolver = composite_api_resolver_alloc(); @@ -147,7 +148,7 @@ static void test_runner_run_internal(TestRunner* instance) { } while(true) { - if(cli_cmd_interrupt_received(instance->cli)) { + if(cli_is_pipe_broken_or_is_etx_next_char(instance->pipe)) { break; } diff --git a/applications/debug/unit_tests/test_runner.h b/applications/debug/unit_tests/test_runner.h index 43aba8bb1..0e9495263 100644 --- a/applications/debug/unit_tests/test_runner.h +++ b/applications/debug/unit_tests/test_runner.h @@ -1,12 +1,12 @@ #pragma once #include +#include typedef struct TestRunner TestRunner; -typedef struct Cli Cli; -TestRunner* test_runner_alloc(Cli* cli, FuriString* args); +TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args); -void test_runner_free(TestRunner* isntance); +void test_runner_free(TestRunner* instance); -void test_runner_run(TestRunner* isntance); +void test_runner_run(TestRunner* instance); diff --git a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c index 73f38ab77..08e0e8ad2 100644 --- a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c @@ -446,6 +446,55 @@ static int32_t test_furi_event_loop_consumer(void* p) { return 0; } +typedef struct { + FuriEventLoop* event_loop; + FuriSemaphore* semaphore; + size_t counter; +} SelfUnsubTestTimerContext; + +static void test_self_unsub_semaphore_callback(FuriEventLoopObject* object, void* context) { + furi_event_loop_unsubscribe(context, object); // shouldn't crash here +} + +static void test_self_unsub_timer_callback(void* arg) { + SelfUnsubTestTimerContext* context = arg; + + if(context->counter == 0) { + furi_semaphore_release(context->semaphore); + } else if(context->counter == 1) { + furi_event_loop_stop(context->event_loop); + } + + context->counter++; +} + +void test_furi_event_loop_self_unsubscribe(void) { + FuriEventLoop* event_loop = furi_event_loop_alloc(); + + FuriSemaphore* semaphore = furi_semaphore_alloc(1, 0); + furi_event_loop_subscribe_semaphore( + event_loop, + semaphore, + FuriEventLoopEventIn, + test_self_unsub_semaphore_callback, + event_loop); + + SelfUnsubTestTimerContext timer_context = { + .event_loop = event_loop, + .semaphore = semaphore, + .counter = 0, + }; + FuriEventLoopTimer* timer = furi_event_loop_timer_alloc( + event_loop, test_self_unsub_timer_callback, FuriEventLoopTimerTypePeriodic, &timer_context); + furi_event_loop_timer_start(timer, furi_ms_to_ticks(20)); + + furi_event_loop_run(event_loop); + + furi_event_loop_timer_free(timer); + furi_semaphore_free(semaphore); + furi_event_loop_free(event_loop); +} + void test_furi_event_loop(void) { TestFuriEventLoopData data = {}; diff --git a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c index 94e2f613b..d80bd7bd5 100644 --- a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c @@ -30,7 +30,9 @@ static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context } void test_stdin(void) { - FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback(); + FuriThreadStdinReadCallback in_cb; + void* in_ctx; + furi_thread_get_stdin_callback(&in_cb, &in_ctx); furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC); char buf[256]; @@ -63,13 +65,14 @@ void test_stdin(void) { fgets(buf, sizeof(buf), stdin); mu_assert_string_eq(" World!\n", buf); - furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC); + furi_thread_set_stdin_callback(in_cb, in_ctx); } // stdout static FuriString* mock_out; -FuriThreadStdoutWriteCallback original_out_cb; +static FuriThreadStdoutWriteCallback original_out_cb; +static void* original_out_ctx; static void mock_out_cb(const char* data, size_t size, void* context) { furi_check(context == CONTEXT_MAGIC); @@ -83,7 +86,7 @@ static void assert_and_clear_mock_out(const char* expected) { // return the original stdout callback for the duration of the check // if the check fails, we don't want the error to end up in our buffer, // we want to be able to see it! - furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); + furi_thread_set_stdout_callback(original_out_cb, original_out_ctx); mu_assert_string_eq(expected, furi_string_get_cstr(mock_out)); furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); @@ -91,7 +94,7 @@ static void assert_and_clear_mock_out(const char* expected) { } void test_stdout(void) { - original_out_cb = furi_thread_get_stdout_callback(); + furi_thread_get_stdout_callback(&original_out_cb, &original_out_ctx); furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); mock_out = furi_string_alloc(); @@ -104,5 +107,5 @@ void test_stdout(void) { assert_and_clear_mock_out("Hello!"); furi_string_free(mock_out); - furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); + furi_thread_set_stdout_callback(original_out_cb, original_out_ctx); } diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index f23be37a9..03d49aa7e 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -8,6 +8,7 @@ void test_furi_concurrent_access(void); void test_furi_pubsub(void); void test_furi_memmgr(void); void test_furi_event_loop(void); +void test_furi_event_loop_self_unsubscribe(void); void test_errno_saving(void); void test_furi_primitives(void); void test_stdin(void); @@ -46,6 +47,10 @@ MU_TEST(mu_test_furi_event_loop) { test_furi_event_loop(); } +MU_TEST(mu_test_furi_event_loop_self_unsubscribe) { + test_furi_event_loop_self_unsubscribe(); +} + MU_TEST(mu_test_errno_saving) { test_errno_saving(); } @@ -68,6 +73,7 @@ MU_TEST_SUITE(test_suite) { MU_RUN_TEST(mu_test_furi_pubsub); MU_RUN_TEST(mu_test_furi_memmgr); MU_RUN_TEST(mu_test_furi_event_loop); + MU_RUN_TEST(mu_test_furi_event_loop_self_unsubscribe); MU_RUN_TEST(mu_test_stdio); MU_RUN_TEST(mu_test_errno_saving); MU_RUN_TEST(mu_test_furi_primitives); diff --git a/applications/debug/unit_tests/tests/js/js_test.c b/applications/debug/unit_tests/tests/js/js_test.c index af590e899..dd695a3a1 100644 --- a/applications/debug/unit_tests/tests/js/js_test.c +++ b/applications/debug/unit_tests/tests/js/js_test.c @@ -6,9 +6,12 @@ #include #include +#include #include +#define TAG "JsUnitTests" + #define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js") typedef enum { @@ -73,7 +76,311 @@ MU_TEST(js_test_storage) { js_test_run(JS_SCRIPT_PATH("storage")); } +static void js_value_test_compatibility_matrix(struct mjs* mjs) { + static const JsValueType types[] = { + JsValueTypeAny, + JsValueTypeAnyArray, + JsValueTypeAnyObject, + JsValueTypeFunction, + JsValueTypeRawPointer, + JsValueTypeInt32, + JsValueTypeDouble, + JsValueTypeString, + JsValueTypeBool, + }; + + mjs_val_t values[] = { + mjs_mk_undefined(), + mjs_mk_foreign(mjs, (void*)0xDEADBEEF), + mjs_mk_array(mjs), + mjs_mk_object(mjs), + mjs_mk_number(mjs, 123.456), + mjs_mk_string(mjs, "test", ~0, false), + mjs_mk_boolean(mjs, true), + }; + +// for proper matrix formatting and better readability +#define YES true +#define NO_ false + static const bool success_matrix[COUNT_OF(types)][COUNT_OF(values)] = { + // types: + {YES, YES, YES, YES, YES, YES, YES}, // any + {NO_, NO_, YES, NO_, NO_, NO_, NO_}, // array + {NO_, NO_, YES, YES, NO_, NO_, NO_}, // obj + {NO_, NO_, NO_, NO_, NO_, NO_, NO_}, // fn + {NO_, YES, NO_, NO_, NO_, NO_, NO_}, // ptr + {NO_, NO_, NO_, NO_, YES, NO_, NO_}, // int32 + {NO_, NO_, NO_, NO_, YES, NO_, NO_}, // double + {NO_, NO_, NO_, NO_, NO_, YES, NO_}, // str + {NO_, NO_, NO_, NO_, NO_, NO_, YES}, // bool + // + //und ptr arr obj num str bool <- values + }; +#undef NO_ +#undef YES + + for(size_t i = 0; i < COUNT_OF(types); i++) { + for(size_t j = 0; j < COUNT_OF(values); j++) { + const JsValueDeclaration declaration = { + .type = types[i], + .n_children = 0, + }; + // we only care about the status, not the result. double has the largest size out of + // all the results + uint8_t result[sizeof(double)]; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&declaration), + JsValueParseFlagNone, + &status, + &values[j], + result); + if((status == JsValueParseStatusOk) != success_matrix[i][j]) { + FURI_LOG_E(TAG, "type %zu, value %zu", i, j); + mu_fail("see serial logs"); + } + } + } +} + +static void js_value_test_literal(struct mjs* mjs) { + static const JsValueType types[] = { + JsValueTypeAny, + JsValueTypeAnyArray, + JsValueTypeAnyObject, + }; + + mjs_val_t values[] = { + mjs_mk_undefined(), + mjs_mk_array(mjs), + mjs_mk_object(mjs), + }; + + mu_assert_int_eq(COUNT_OF(types), COUNT_OF(values)); + for(size_t i = 0; i < COUNT_OF(types); i++) { + const JsValueDeclaration declaration = { + .type = types[i], + .n_children = 0, + }; + mjs_val_t result; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&declaration), + JsValueParseFlagNone, + &status, + &values[i], + &result); + mu_assert_int_eq(JsValueParseStatusOk, status); + mu_assert(result == values[i], "wrong result"); + } +} + +static void js_value_test_primitive( + struct mjs* mjs, + JsValueType type, + const void* c_value, + size_t c_value_size, + mjs_val_t js_val) { + const JsValueDeclaration declaration = { + .type = type, + .n_children = 0, + }; + uint8_t result[c_value_size]; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&declaration), + JsValueParseFlagNone, + &status, + &js_val, + result); + mu_assert_int_eq(JsValueParseStatusOk, status); + if(type == JsValueTypeString) { + const char* result_str = *(const char**)&result; + mu_assert_string_eq(c_value, result_str); + } else { + mu_assert_mem_eq(c_value, result, c_value_size); + } +} + +static void js_value_test_primitives(struct mjs* mjs) { + int32_t i32 = 123; + js_value_test_primitive(mjs, JsValueTypeInt32, &i32, sizeof(i32), mjs_mk_number(mjs, i32)); + + double dbl = 123.456; + js_value_test_primitive(mjs, JsValueTypeDouble, &dbl, sizeof(dbl), mjs_mk_number(mjs, dbl)); + + const char* str = "test"; + js_value_test_primitive( + mjs, JsValueTypeString, str, strlen(str) + 1, mjs_mk_string(mjs, str, ~0, false)); + + bool boolean = true; + js_value_test_primitive( + mjs, JsValueTypeBool, &boolean, sizeof(boolean), mjs_mk_boolean(mjs, boolean)); +} + +static uint32_t + js_value_test_enum(struct mjs* mjs, const JsValueDeclaration* decl, const char* value) { + mjs_val_t str = mjs_mk_string(mjs, value, ~0, false); + uint32_t result; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, JS_VALUE_PARSE_SOURCE_VALUE(decl), JsValueParseFlagNone, &status, &str, &result); + if(status != JsValueParseStatusOk) return 0; + return result; +} + +static void js_value_test_enums(struct mjs* mjs) { + static const JsValueEnumVariant enum_1_variants[] = { + {"variant 1", 1}, + {"variant 2", 2}, + {"variant 3", 3}, + }; + static const JsValueDeclaration enum_1 = JS_VALUE_ENUM(uint32_t, enum_1_variants); + + static const JsValueEnumVariant enum_2_variants[] = { + {"read", 4}, + {"write", 8}, + }; + static const JsValueDeclaration enum_2 = JS_VALUE_ENUM(uint32_t, enum_2_variants); + + mu_assert_int_eq(1, js_value_test_enum(mjs, &enum_1, "variant 1")); + mu_assert_int_eq(2, js_value_test_enum(mjs, &enum_1, "variant 2")); + mu_assert_int_eq(3, js_value_test_enum(mjs, &enum_1, "variant 3")); + mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_1, "not a thing")); + + mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 1")); + mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 2")); + mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 3")); + mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "not a thing")); + mu_assert_int_eq(4, js_value_test_enum(mjs, &enum_2, "read")); + mu_assert_int_eq(8, js_value_test_enum(mjs, &enum_2, "write")); +} + +static void js_value_test_object(struct mjs* mjs) { + static const JsValueDeclaration int_decl = JS_VALUE_SIMPLE(JsValueTypeInt32); + + static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString); + + static const JsValueEnumVariant enum_variants[] = { + {"variant 1", 1}, + {"variant 2", 2}, + {"variant 3", 3}, + }; + static const JsValueDeclaration enum_decl = JS_VALUE_ENUM(uint32_t, enum_variants); + + static const JsValueObjectField fields[] = { + {"int", &int_decl}, + {"str", &str_decl}, + {"enum", &enum_decl}, + }; + static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields); + + mjs_val_t object = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, object) { + JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false)); + JS_FIELD("int", mjs_mk_number(mjs, 123)); + JS_FIELD("enum", mjs_mk_string(mjs, "variant 2", ~0, false)); + } + + const char* result_str; + int32_t result_int; + uint32_t result_enum; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&object_decl), + JsValueParseFlagNone, + &status, + &object, + &result_int, + &result_str, + &result_enum); + mu_assert_int_eq(JsValueParseStatusOk, status); + mu_assert_string_eq("Helloooo!", result_str); + mu_assert_int_eq(123, result_int); + mu_assert_int_eq(2, result_enum); +} + +static void js_value_test_default(struct mjs* mjs) { + static const JsValueDeclaration int_decl = + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 123); + static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString); + + static const JsValueObjectField fields[] = { + {"int", &int_decl}, + {"str", &str_decl}, + }; + static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields); + + mjs_val_t object = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, object) { + JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false)); + JS_FIELD("int", mjs_mk_undefined()); + } + + const char* result_str; + int32_t result_int; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&object_decl), + JsValueParseFlagNone, + &status, + &object, + &result_int, + &result_str); + mu_assert_string_eq("Helloooo!", result_str); + mu_assert_int_eq(123, result_int); +} + +static void js_value_test_args_fn(struct mjs* mjs) { + static const JsValueDeclaration arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments args = JS_VALUE_ARGS(arg_list); + + int32_t a, b, c; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &args, &a, &b, &c); + + mu_assert_int_eq(123, a); + mu_assert_int_eq(456, b); + mu_assert_int_eq(-420, c); +} + +static void js_value_test_args(struct mjs* mjs) { + mjs_val_t function = MJS_MK_FN(js_value_test_args_fn); + + mjs_val_t result; + mjs_val_t args[] = { + mjs_mk_number(mjs, 123), + mjs_mk_number(mjs, 456), + mjs_mk_number(mjs, -420), + }; + mu_assert_int_eq( + MJS_OK, mjs_apply(mjs, &result, function, MJS_UNDEFINED, COUNT_OF(args), args)); +} + +MU_TEST(js_value_test) { + struct mjs* mjs = mjs_create(NULL); + + js_value_test_compatibility_matrix(mjs); + js_value_test_literal(mjs); + js_value_test_primitives(mjs); + js_value_test_enums(mjs); + js_value_test_object(mjs); + js_value_test_default(mjs); + js_value_test_args(mjs); + + mjs_destroy(mjs); +} + MU_TEST_SUITE(test_js) { + MU_RUN_TEST(js_value_test); MU_RUN_TEST(js_test_basic); MU_RUN_TEST(js_test_math); MU_RUN_TEST(js_test_event_loop); diff --git a/applications/debug/unit_tests/tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h index 9ca3bb403..c854c4673 100644 --- a/applications/debug/unit_tests/tests/minunit.h +++ b/applications/debug/unit_tests/tests/minunit.h @@ -396,6 +396,8 @@ void minunit_printf_warning(const char* format, ...); return; \ } else { minunit_print_progress(); }) +//-V:mu_assert_string_eq:526, 547 + #define mu_assert_string_eq(expected, result) \ MU__SAFE_BLOCK( \ const char* minunit_tmp_e = expected; const char* minunit_tmp_r = result; \ @@ -416,6 +418,8 @@ void minunit_printf_warning(const char* format, ...); return; \ } else { minunit_print_progress(); }) +//-V:mu_assert_mem_eq:526 + #define mu_assert_mem_eq(expected, result, size) \ MU__SAFE_BLOCK( \ const void* minunit_tmp_e = expected; const void* minunit_tmp_r = result; \ diff --git a/applications/debug/unit_tests/tests/nfc/nfc_test.c b/applications/debug/unit_tests/tests/nfc/nfc_test.c index 4ba934b6d..e028b8041 100644 --- a/applications/debug/unit_tests/tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/tests/nfc/nfc_test.c @@ -262,7 +262,7 @@ static void mf_ultralight_reader_test(const char* path) { nfc_listener_start(mfu_listener, NULL, NULL); MfUltralightData* mfu_data = mf_ultralight_alloc(); - MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data); + MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL); mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed"); nfc_listener_stop(mfu_listener); @@ -315,7 +315,7 @@ MU_TEST(ntag_213_locked_reader) { nfc_listener_start(mfu_listener, NULL, NULL); MfUltralightData* mfu_data = mf_ultralight_alloc(); - MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data); + MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL); mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed"); nfc_listener_stop(mfu_listener); @@ -353,7 +353,7 @@ static void mf_ultralight_write(void) { MfUltralightData* mfu_data = mf_ultralight_alloc(); // Initial read - MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data); + MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL); mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed"); mu_assert( @@ -371,7 +371,7 @@ static void mf_ultralight_write(void) { } // Verification read - error = mf_ultralight_poller_sync_read_card(poller, mfu_data); + error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL); mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed"); nfc_listener_stop(mfu_listener); diff --git a/applications/debug/unit_tests/tests/pipe/pipe_test.c b/applications/debug/unit_tests/tests/pipe/pipe_test.c index d440a04ee..f0227b353 100644 --- a/applications/debug/unit_tests/tests/pipe/pipe_test.c +++ b/applications/debug/unit_tests/tests/pipe/pipe_test.c @@ -25,16 +25,13 @@ MU_TEST(pipe_test_trivial) { mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(alice)); mu_assert_int_eq(i, pipe_bytes_available(bob)); - if(pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } + if(pipe_spaces_available(alice) == 0) break; + furi_check(pipe_send(alice, &i, sizeof(uint8_t)) == sizeof(uint8_t)); mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(bob)); mu_assert_int_eq(i, pipe_bytes_available(alice)); - if(pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } + furi_check(pipe_send(bob, &i, sizeof(uint8_t)) == sizeof(uint8_t)); } pipe_free(alice); @@ -43,10 +40,9 @@ MU_TEST(pipe_test_trivial) { for(uint8_t i = 0;; ++i) { mu_assert_int_eq(PIPE_SIZE - i, pipe_bytes_available(bob)); + if(pipe_bytes_available(bob) == 0) break; uint8_t value; - if(pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } + furi_check(pipe_receive(bob, &value, sizeof(uint8_t)) == sizeof(uint8_t)); mu_assert_int_eq(i, value); } @@ -68,16 +64,15 @@ typedef struct { static void on_data_arrived(PipeSide* pipe, void* context) { AncillaryThreadContext* ctx = context; ctx->flag |= TestFlagDataArrived; - uint8_t buffer[PIPE_SIZE]; - size_t size = pipe_receive(pipe, buffer, sizeof(buffer), 0); - pipe_send(pipe, buffer, size, 0); + uint8_t input; + size_t size = pipe_receive(pipe, &input, sizeof(input)); + pipe_send(pipe, &input, size); } static void on_space_freed(PipeSide* pipe, void* context) { + UNUSED(pipe); AncillaryThreadContext* ctx = context; ctx->flag |= TestFlagSpaceFreed; - const char* message = "Hi!"; - pipe_send(pipe, message, strlen(message), 0); } static void on_became_broken(PipeSide* pipe, void* context) { @@ -117,22 +112,16 @@ MU_TEST(pipe_test_event_loop) { furi_thread_start(thread); const char* message = "Hello!"; - pipe_send(alice, message, strlen(message), FuriWaitForever); + pipe_send(alice, message, strlen(message)); char buffer_1[16]; - size_t size = pipe_receive(alice, buffer_1, sizeof(buffer_1), FuriWaitForever); + size_t size = pipe_receive(alice, buffer_1, strlen(message)); buffer_1[size] = 0; - char buffer_2[16]; - const char* expected_reply = "Hi!"; - size = pipe_receive(alice, buffer_2, sizeof(buffer_2), FuriWaitForever); - buffer_2[size] = 0; - pipe_free(alice); furi_thread_join(thread); mu_assert_string_eq(message, buffer_1); - mu_assert_string_eq(expected_reply, buffer_2); mu_assert_int_eq( TestFlagDataArrived | TestFlagSpaceFreed | TestFlagBecameBroken, furi_thread_get_return_code(thread)); diff --git a/applications/debug/unit_tests/tests/rpc/rpc_test.c b/applications/debug/unit_tests/tests/rpc/rpc_test.c index 5d26bdb30..82ab872ce 100644 --- a/applications/debug/unit_tests/tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/tests/rpc/rpc_test.c @@ -3,7 +3,6 @@ #include #include -#include #include #include #include diff --git a/applications/debug/unit_tests/tests/storage/storage_test.c b/applications/debug/unit_tests/tests/storage/storage_test.c index 75c52ef9a..9d5c68a44 100644 --- a/applications/debug/unit_tests/tests/storage/storage_test.c +++ b/applications/debug/unit_tests/tests/storage/storage_test.c @@ -521,11 +521,6 @@ MU_TEST(test_storage_data_path) { // check that appsdata folder exists mu_check(storage_dir_exists(storage, APPS_DATA_PATH)); - // check that cli folder exists - mu_check(storage_dir_exists(storage, APPSDATA_APP_PATH("cli"))); - - storage_simply_remove(storage, APPSDATA_APP_PATH("cli")); - furi_record_close(RECORD_STORAGE); } diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 10b089022..4f0e4dec9 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -8,6 +8,7 @@ #include #include #include +#include static constexpr auto unit_tests_api_table = sort(create_array_t( API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)), @@ -38,4 +39,16 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( JsThread*, (const char* script_path, JsThreadCallback callback, void* context)), API_METHOD(js_thread_stop, void, (JsThread * worker)), + API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)), + API_METHOD( + js_value_parse, + JsValueParseStatus, + (struct mjs * mjs, + const JsValueParseDeclaration declaration, + JsValueParseFlag flags, + mjs_val_t* buffer, + size_t buf_size, + mjs_val_t* source, + size_t n_c_vals, + ...)), API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/debug/unit_tests/unit_tests.c b/applications/debug/unit_tests/unit_tests.c index 237cb9080..8605cb781 100644 --- a/applications/debug/unit_tests/unit_tests.c +++ b/applications/debug/unit_tests/unit_tests.c @@ -1,21 +1,24 @@ #include -#include +#include +#include +#include +#include #include "test_runner.h" -void unit_tests_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void unit_tests_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); - TestRunner* test_runner = test_runner_alloc(cli, args); + TestRunner* test_runner = test_runner_alloc(pipe, args); test_runner_run(test_runner); test_runner_free(test_runner); } void unit_tests_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command( + registry, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL); furi_record_close(RECORD_CLI); #endif } diff --git a/applications/debug/usb_mouse/application.fam b/applications/debug/usb_mouse/application.fam index 165a72b03..38c1d2e55 100644 --- a/applications/debug/usb_mouse/application.fam +++ b/applications/debug/usb_mouse/application.fam @@ -5,6 +5,5 @@ App( entry_point="usb_mouse_app", requires=["gui"], stack_size=1 * 1024, - order=60, fap_category="Debug", ) diff --git a/applications/debug/usb_test/application.fam b/applications/debug/usb_test/application.fam index 463bb4a26..6481518b4 100644 --- a/applications/debug/usb_test/application.fam +++ b/applications/debug/usb_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="usb_test_app", requires=["gui"], stack_size=1 * 1024, - order=50, fap_category="Debug", ) diff --git a/applications/debug/vibro_test/application.fam b/applications/debug/vibro_test/application.fam index c35a7223f..dafa83eac 100644 --- a/applications/debug/vibro_test/application.fam +++ b/applications/debug/vibro_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="vibro_test_app", requires=["gui"], stack_size=1 * 1024, - order=20, fap_category="Debug", ) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 27af3e557..b57ace0b3 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -88,7 +88,7 @@ typedef struct { volatile SubGhzDeviceCC1101ExtState state; volatile SubGhzDeviceCC1101ExtRegulation regulation; const GpioPin* async_mirror_pin; - FuriHalSpiBusHandle* spi_bus_handle; + const FuriHalSpiBusHandle* spi_bus_handle; const GpioPin* g0_pin; SubGhzDeviceCC1101ExtAsyncTx async_tx; SubGhzDeviceCC1101ExtAsyncRx async_rx; diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c index e5af819e9..4b225b70c 100644 --- a/applications/examples/example_thermo/example_thermo.c +++ b/applications/examples/example_thermo/example_thermo.c @@ -22,7 +22,7 @@ #include #include -#include +#include #define UPDATE_PERIOD_MS 1000UL #define TEXT_STORE_SIZE 64U @@ -76,6 +76,7 @@ typedef struct { FuriThread* reader_thread; FuriMessageQueue* event_queue; OneWireHost* onewire; + Power* power; float temp_celsius; bool has_device; } ExampleThermoContext; @@ -273,7 +274,7 @@ static void example_thermo_input_callback(InputEvent* event, void* ctx) { /* Starts the reader thread and handles the input */ static void example_thermo_run(ExampleThermoContext* context) { /* Enable power on external pins */ - furi_hal_power_enable_otg(); + power_enable_otg(context->power, true); /* Configure the hardware in host mode */ onewire_host_start(context->onewire); @@ -309,7 +310,7 @@ static void example_thermo_run(ExampleThermoContext* context) { onewire_host_stop(context->onewire); /* Disable power on external pins */ - furi_hal_power_disable_otg(); + power_enable_otg(context->power, false); } /******************** Initialisation & startup *****************************/ @@ -334,6 +335,8 @@ static ExampleThermoContext* example_thermo_context_alloc(void) { context->onewire = onewire_host_alloc(&THERMO_GPIO_PIN); + context->power = furi_record_open(RECORD_POWER); + return context; } @@ -348,6 +351,7 @@ static void example_thermo_context_free(ExampleThermoContext* context) { view_port_free(context->view_port); furi_record_close(RECORD_GUI); + furi_record_close(RECORD_POWER); } /* The application's entry point. Execution starts from here. */ diff --git a/applications/main/application.fam b/applications/main/application.fam index ed3226e0d..c8923a434 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -23,11 +23,6 @@ App( name="On start hooks", apptype=FlipperAppType.METAPACKAGE, provides=[ - "ibutton_start", - "onewire_start", - "subghz_start", - "infrared_start", - "lfrfid_start", - "nfc_start", + "cli", ], ) diff --git a/applications/main/archive/application.fam b/applications/main/archive/application.fam index 309cee8d5..45ac11216 100644 --- a/applications/main/archive/application.fam +++ b/applications/main/archive/application.fam @@ -7,5 +7,5 @@ App( requires=["gui"], stack_size=6 * 1024, icon="A_FileManager_14", - order=0, + order=10, ) diff --git a/applications/main/archive/helpers/archive_apps.c b/applications/main/archive/helpers/archive_apps.c index 7aca29364..65d5a5af9 100644 --- a/applications/main/archive/helpers/archive_apps.c +++ b/applications/main/archive/helpers/archive_apps.c @@ -1,8 +1,9 @@ #include "archive_apps.h" #include "archive_browser.h" -static const char* known_apps[] = { +static const char* const known_apps[] = { [ArchiveAppTypeU2f] = "u2f", + [ArchiveAppTypeSetting] = "setting", }; ArchiveAppTypeEnum archive_get_app_type(const char* path) { @@ -36,6 +37,8 @@ bool archive_app_is_available(void* context, const char* path) { furi_record_close(RECORD_STORAGE); return file_exists; + } else if(app == ArchiveAppTypeSetting) { + return true; } else { return false; } @@ -53,6 +56,9 @@ bool archive_app_read_dir(void* context, const char* path) { if(app == ArchiveAppTypeU2f) { archive_add_app_item(browser, "/app:u2f/U2F Token"); return true; + } else if(app == ArchiveAppTypeSetting) { + archive_add_app_item(browser, path); + return true; } else { return false; } @@ -75,6 +81,8 @@ void archive_app_delete_file(void* context, const char* path) { if(archive_is_favorite("/app:u2f/U2F Token")) { archive_favorites_delete("/app:u2f/U2F Token"); } + } else if(app == ArchiveAppTypeSetting) { + // can't delete a setting! } if(res) { diff --git a/applications/main/archive/helpers/archive_apps.h b/applications/main/archive/helpers/archive_apps.h index d9d1dec34..f5d050387 100644 --- a/applications/main/archive/helpers/archive_apps.h +++ b/applications/main/archive/helpers/archive_apps.h @@ -4,12 +4,14 @@ typedef enum { ArchiveAppTypeU2f, + ArchiveAppTypeSetting, ArchiveAppTypeUnknown, ArchiveAppsTotal, } ArchiveAppTypeEnum; static const ArchiveFileTypeEnum app_file_types[] = { [ArchiveAppTypeU2f] = ArchiveFileTypeU2f, + [ArchiveAppTypeSetting] = ArchiveFileTypeSetting, [ArchiveAppTypeUnknown] = ArchiveFileTypeUnknown, }; diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index bdfeba035..a2330b494 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -7,7 +7,7 @@ #define TAB_DEFAULT ArchiveTabFavorites // Start tab #define FILE_LIST_BUF_LEN 50 -static const char* tab_default_paths[] = { +static const char* const tab_default_paths[] = { [ArchiveTabFavorites] = "/app:favorites", [ArchiveTabIButton] = EXT_PATH("ibutton"), [ArchiveTabNFC] = EXT_PATH("nfc"), @@ -22,7 +22,7 @@ static const char* tab_default_paths[] = { [ArchiveTabBrowser] = STORAGE_EXT_PATH_PREFIX, }; -static const char* known_ext[] = { +static const char* const known_ext[] = { [ArchiveFileTypeIButton] = ".ibtn", [ArchiveFileTypeNFC] = ".nfc", [ArchiveFileTypeSubGhz] = ".sub", @@ -37,6 +37,7 @@ static const char* known_ext[] = { [ArchiveFileTypeFolder] = "?", [ArchiveFileTypeUnknown] = "*", [ArchiveFileTypeAppOrJs] = ".fap|.js", + [ArchiveFileTypeSetting] = "?", }; static const ArchiveFileTypeEnum known_type[] = { diff --git a/applications/main/archive/helpers/archive_favorites.c b/applications/main/archive/helpers/archive_favorites.c index 351d68c1d..5ed3adfe3 100644 --- a/applications/main/archive/helpers/archive_favorites.c +++ b/applications/main/archive/helpers/archive_favorites.c @@ -1,9 +1,10 @@ - #include "archive_favorites.h" #include "archive_files.h" #include "archive_apps.h" #include "archive_browser.h" +#include + #define ARCHIVE_FAV_FILE_BUF_LEN 32 static bool archive_favorites_read_line(File* file, FuriString* str_result) { @@ -337,3 +338,46 @@ void archive_favorites_save(void* context) { storage_file_free(file); furi_record_close(RECORD_STORAGE); } + +void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting) { + DialogMessage* message = dialog_message_alloc(); + + FuriString* setting_path = furi_string_alloc_set_str(app_name); + if(setting) { + furi_string_push_back(setting_path, '/'); + furi_string_cat_str(setting_path, setting); + } + const char* setting_path_str = furi_string_get_cstr(setting_path); + + bool is_favorite = archive_is_favorite("/app:setting/%s", setting_path_str); + dialog_message_set_header( + message, + is_favorite ? "Unpin This Setting?" : "Pin This Setting?", + 64, + 0, + AlignCenter, + AlignTop); + dialog_message_set_text( + message, + is_favorite ? "It will no longer be\naccessible from the\nFavorites menu" : + "It will be accessible from the\nFavorites menu", + 64, + 32, + AlignCenter, + AlignCenter); + dialog_message_set_buttons( + message, is_favorite ? "Unpin" : "Go back", NULL, is_favorite ? "Keep pinned" : "Pin"); + + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessageButton button = dialog_message_show(dialogs, message); + furi_record_close(RECORD_DIALOGS); + + if(is_favorite && button == DialogMessageButtonLeft) { + archive_favorites_delete("/app:setting/%s", setting_path_str); + } else if(!is_favorite && button == DialogMessageButtonRight) { + archive_file_append(ARCHIVE_FAV_PATH, "/app:setting/%s\n", setting_path_str); + } + + furi_string_free(setting_path); + dialog_message_free(message); +} diff --git a/applications/main/archive/helpers/archive_favorites.h b/applications/main/archive/helpers/archive_favorites.h index 75070c44d..3a43a0e75 100644 --- a/applications/main/archive/helpers/archive_favorites.h +++ b/applications/main/archive/helpers/archive_favorites.h @@ -12,3 +12,13 @@ bool archive_is_favorite(const char* format, ...) _ATTRIBUTE((__format__(__print bool archive_favorites_rename(const char* src, const char* dst); void archive_add_to_favorites(const char* file_path); void archive_favorites_save(void* context); + +/** + * Intended to be called by settings apps to handle long presses, as well as + * internally from within the archive + * + * @param app_name name of the referring application + * @param setting name of the setting, which will be both displayed to the user + * and passed to the application as an argument upon recall + */ +void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting); diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index a4ef5920b..9dd1cbcde 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -23,6 +23,7 @@ typedef enum { ArchiveFileTypeFolder, ArchiveFileTypeUnknown, ArchiveFileTypeAppOrJs, + ArchiveFileTypeSetting, ArchiveFileTypeLoading, } ArchiveFileTypeEnum; diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index b7f7e0303..bd24b51ad 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -44,7 +44,7 @@ static void archive_loader_callback(const void* message, void* context) { const LoaderEvent* event = message; ArchiveApp* archive = (ArchiveApp*)context; - if(event->type == LoaderEventTypeApplicationStopped) { + if(event->type == LoaderEventTypeNoMoreAppsInQueue) { view_dispatcher_send_custom_event( archive->view_dispatcher, ArchiveBrowserEventListRefresh); } @@ -54,20 +54,37 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec UNUSED(browser); Loader* loader = furi_record_open(RECORD_LOADER); - const char* app_name = archive_get_flipper_app_name(selected->type); - - if(app_name) { - if(selected->is_app) { - char* param = strrchr(furi_string_get_cstr(selected->path), '/'); - if(param != NULL) { - param++; - } - loader_start_with_gui_error(loader, app_name, param); + if(selected->type == ArchiveFileTypeSetting) { + FuriString* app_name = furi_string_alloc_set(selected->path); + furi_string_right(app_name, furi_string_search_char(app_name, '/', 1) + 1); + size_t slash = furi_string_search_char(app_name, '/', 1); + if(slash != FURI_STRING_FAILURE) { + furi_string_left(app_name, slash); + FuriString* app_args = + furi_string_alloc_set_str(furi_string_get_cstr(app_name) + slash + 1); + loader_start_with_gui_error( + loader, furi_string_get_cstr(app_name), furi_string_get_cstr(app_args)); + furi_string_free(app_args); } else { - loader_start_with_gui_error(loader, app_name, furi_string_get_cstr(selected->path)); + loader_start_with_gui_error(loader, furi_string_get_cstr(app_name), NULL); } + furi_string_free(app_name); } else { - loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL); + const char* app_name = archive_get_flipper_app_name(selected->type); + if(app_name) { + if(selected->is_app) { + char* param = strrchr(furi_string_get_cstr(selected->path), '/'); + if(param != NULL) { + param++; + } + loader_start_with_gui_error(loader, app_name, param); + } else { + loader_start_with_gui_error( + loader, app_name, furi_string_get_cstr(selected->path)); + } + } else { + loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL); + } } furi_record_close(RECORD_LOADER); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index e1739ca5b..aa992264e 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -33,6 +33,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeBadUsb] = &I_badusb_10px, [ArchiveFileTypeU2f] = &I_u2f_10px, [ArchiveFileTypeApplication] = &I_Apps_10px, + [ArchiveFileTypeSetting] = &I_settings_10px, [ArchiveFileTypeUpdateManifest] = &I_update_10px, [ArchiveFileTypeFolder] = &I_dir_10px, [ArchiveFileTypeUnknown] = &I_unknown_10px, diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 8d3909fcc..9844e248d 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -7,7 +7,7 @@ App( icon="A_BadUsb_14", order=70, resources="resources", - fap_libs=["assets", "ble_profile"], + fap_libs=["assets"], fap_icon="icon.png", fap_category="USB", ) diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index eda702cf4..96ccb14ed 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -31,52 +31,123 @@ static void bad_usb_app_tick_event_callback(void* context) { static void bad_usb_load_settings(BadUsbApp* app) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff = flipper_format_file_alloc(storage); - bool state = false; + bool loaded = false; + BadUsbHidConfig* hid_cfg = &app->user_hid_cfg; FuriString* temp_str = furi_string_alloc(); - uint32_t version = 0; - uint32_t interface = 0; + uint32_t temp_uint = 0; if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) { do { - if(!flipper_format_read_header(fff, temp_str, &version)) break; + if(!flipper_format_read_header(fff, temp_str, &temp_uint)) break; if((strcmp(furi_string_get_cstr(temp_str), BAD_USB_SETTINGS_FILE_TYPE) != 0) || - (version != BAD_USB_SETTINGS_VERSION)) + (temp_uint != BAD_USB_SETTINGS_VERSION)) break; - if(!flipper_format_read_string(fff, "layout", temp_str)) break; - if(!flipper_format_read_uint32(fff, "interface", &interface, 1)) break; - if(interface > BadUsbHidInterfaceBle) break; + if(flipper_format_read_string(fff, "layout", temp_str)) { + furi_string_set(app->keyboard_layout, temp_str); + FileInfo layout_file_info; + FS_Error file_check_err = storage_common_stat( + storage, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); + if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) { + furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); + } + } else { + furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); + flipper_format_rewind(fff); + } - state = true; + if(!flipper_format_read_uint32(fff, "interface", &temp_uint, 1) || + temp_uint >= BadUsbHidInterfaceMAX) { + temp_uint = BadUsbHidInterfaceUsb; + flipper_format_rewind(fff); + } + app->interface = temp_uint; + + if(!flipper_format_read_bool(fff, "ble_bonding", &hid_cfg->ble.bonding, 1)) { + hid_cfg->ble.bonding = true; + flipper_format_rewind(fff); + } + + if(!flipper_format_read_uint32(fff, "ble_pairing", &temp_uint, 1) || + temp_uint >= GapPairingCount) { + temp_uint = GapPairingPinCodeVerifyYesNo; + flipper_format_rewind(fff); + } + hid_cfg->ble.pairing = temp_uint; + + if(flipper_format_read_string(fff, "ble_name", temp_str)) { + strlcpy( + hid_cfg->ble.name, furi_string_get_cstr(temp_str), sizeof(hid_cfg->ble.name)); + } else { + hid_cfg->ble.name[0] = '\0'; + flipper_format_rewind(fff); + } + + if(!flipper_format_read_hex( + fff, "ble_mac", hid_cfg->ble.mac, sizeof(hid_cfg->ble.mac))) { + memset(hid_cfg->ble.mac, 0, sizeof(hid_cfg->ble.mac)); + flipper_format_rewind(fff); + } + + if(flipper_format_read_string(fff, "usb_manuf", temp_str)) { + strlcpy( + hid_cfg->usb.manuf, + furi_string_get_cstr(temp_str), + sizeof(hid_cfg->usb.manuf)); + } else { + hid_cfg->usb.manuf[0] = '\0'; + flipper_format_rewind(fff); + } + + if(flipper_format_read_string(fff, "usb_product", temp_str)) { + strlcpy( + hid_cfg->usb.product, + furi_string_get_cstr(temp_str), + sizeof(hid_cfg->usb.product)); + } else { + hid_cfg->usb.product[0] = '\0'; + flipper_format_rewind(fff); + } + + if(!flipper_format_read_uint32(fff, "usb_vid", &hid_cfg->usb.vid, 1)) { + hid_cfg->usb.vid = 0; + flipper_format_rewind(fff); + } + + if(!flipper_format_read_uint32(fff, "usb_pid", &hid_cfg->usb.pid, 1)) { + hid_cfg->usb.pid = 0; + flipper_format_rewind(fff); + } + + loaded = true; } while(0); } - flipper_format_free(fff); - furi_record_close(RECORD_STORAGE); - - if(state) { - furi_string_set(app->keyboard_layout, temp_str); - app->interface = interface; - - Storage* fs_api = furi_record_open(RECORD_STORAGE); - FileInfo layout_file_info; - FS_Error file_check_err = storage_common_stat( - fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); - furi_record_close(RECORD_STORAGE); - if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) { - furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); - } - } else { - furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); - app->interface = BadUsbHidInterfaceUsb; - } furi_string_free(temp_str); + + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); + + if(!loaded) { + furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); + app->interface = BadUsbHidInterfaceUsb; + hid_cfg->ble.name[0] = '\0'; + memset(hid_cfg->ble.mac, 0, sizeof(hid_cfg->ble.mac)); + hid_cfg->ble.bonding = true; + hid_cfg->ble.pairing = GapPairingPinCodeVerifyYesNo; + hid_cfg->usb.vid = 0; + hid_cfg->usb.pid = 0; + hid_cfg->usb.manuf[0] = '\0'; + hid_cfg->usb.product[0] = '\0'; + } } static void bad_usb_save_settings(BadUsbApp* app) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff = flipper_format_file_alloc(storage); + BadUsbHidConfig* hid_cfg = &app->user_hid_cfg; + uint32_t temp_uint = 0; if(flipper_format_file_open_always(fff, BAD_USB_SETTINGS_PATH)) { do { @@ -84,9 +155,19 @@ static void bad_usb_save_settings(BadUsbApp* app) { fff, BAD_USB_SETTINGS_FILE_TYPE, BAD_USB_SETTINGS_VERSION)) break; if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; - uint32_t interface_id = app->interface; - if(!flipper_format_write_uint32(fff, "interface", (const uint32_t*)&interface_id, 1)) + temp_uint = app->interface; + if(!flipper_format_write_uint32(fff, "interface", &temp_uint, 1)) break; + if(!flipper_format_write_bool(fff, "ble_bonding", &hid_cfg->ble.bonding, 1)) break; + temp_uint = hid_cfg->ble.pairing; + if(!flipper_format_write_uint32(fff, "ble_pairing", &temp_uint, 1)) break; + if(!flipper_format_write_string_cstr(fff, "ble_name", hid_cfg->ble.name)) break; + if(!flipper_format_write_hex( + fff, "ble_mac", (uint8_t*)&hid_cfg->ble.mac, sizeof(hid_cfg->ble.mac))) break; + if(!flipper_format_write_string_cstr(fff, "usb_manuf", hid_cfg->usb.manuf)) break; + if(!flipper_format_write_string_cstr(fff, "usb_product", hid_cfg->usb.product)) break; + if(!flipper_format_write_uint32(fff, "usb_vid", &hid_cfg->usb.vid, 1)) break; + if(!flipper_format_write_uint32(fff, "usb_pid", &hid_cfg->usb.pid, 1)) break; } while(0); } @@ -121,7 +202,7 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { view_dispatcher_set_event_callback_context(app->view_dispatcher, app); view_dispatcher_set_tick_event_callback( - app->view_dispatcher, bad_usb_app_tick_event_callback, 500); + app->view_dispatcher, bad_usb_app_tick_event_callback, 250); view_dispatcher_set_custom_event_callback( app->view_dispatcher, bad_usb_app_custom_event_callback); view_dispatcher_set_navigation_event_callback( @@ -146,6 +227,14 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { view_dispatcher_add_view( app->view_dispatcher, BadUsbAppViewWork, bad_usb_view_get_view(app->bad_usb_view)); + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadUsbAppViewTextInput, text_input_get_view(app->text_input)); + + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadUsbAppViewByteInput, byte_input_get_view(app->byte_input)); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); if(furi_hal_usb_is_locked()) { @@ -157,6 +246,7 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { furi_check(furi_hal_usb_set_config(NULL, NULL)); if(!furi_string_empty(app->file_path)) { + scene_manager_set_scene_state(app->scene_manager, BadUsbSceneWork, true); scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); } else { furi_string_set(app->file_path, BAD_USB_APP_BASE_FOLDER); @@ -191,6 +281,14 @@ void bad_usb_app_free(BadUsbApp* app) { view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig); variable_item_list_free(app->var_item_list); + // Text Input + view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewTextInput); + text_input_free(app->text_input); + + // Byte Input + view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewByteInput); + byte_input_free(app->byte_input); + // View dispatcher view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index b34bd5de6..06a798706 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include "views/bad_usb_view.h" @@ -36,6 +38,13 @@ struct BadUsbApp { Widget* widget; Popup* popup; VariableItemList* var_item_list; + TextInput* text_input; + ByteInput* byte_input; + + char ble_name_buf[FURI_HAL_BT_ADV_NAME_LENGTH]; + uint8_t ble_mac_buf[GAP_MAC_ADDR_SIZE]; + char usb_name_buf[HID_MANUF_PRODUCT_NAME_LEN]; + uint16_t usb_vidpid_buf[2]; BadUsbAppError error; FuriString* file_path; @@ -44,6 +53,8 @@ struct BadUsbApp { BadUsbScript* bad_usb_script; BadUsbHidInterface interface; + BadUsbHidConfig user_hid_cfg; + BadUsbHidConfig script_hid_cfg; FuriHalUsbInterface* usb_if_prev; }; @@ -52,6 +63,8 @@ typedef enum { BadUsbAppViewPopup, BadUsbAppViewWork, BadUsbAppViewConfig, + BadUsbAppViewByteInput, + BadUsbAppViewTextInput, } BadUsbAppView; void bad_usb_set_interface(BadUsbApp* app, BadUsbHidInterface interface); diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.c b/applications/main/bad_usb/helpers/bad_usb_hid.c index 5d7076314..5ae4146e8 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.c +++ b/applications/main/bad_usb/helpers/bad_usb_hid.c @@ -1,5 +1,5 @@ #include "bad_usb_hid.h" -#include +#include "ble_hid_profile.h" #include #include @@ -7,8 +7,14 @@ #define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" -void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) { - furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg)); +void hid_usb_adjust_config(BadUsbHidConfig* hid_cfg) { + if(hid_cfg->usb.vid == 0) hid_cfg->usb.vid = HID_VID_DEFAULT; + if(hid_cfg->usb.pid == 0) hid_cfg->usb.pid = HID_PID_DEFAULT; +} + +void* hid_usb_init(BadUsbHidConfig* hid_cfg) { + hid_usb_adjust_config(hid_cfg); + furi_check(furi_hal_usb_set_config(&usb_hid, &hid_cfg->usb)); return NULL; } @@ -37,6 +43,31 @@ bool hid_usb_kb_release(void* inst, uint16_t button) { return furi_hal_hid_kb_release(button); } +bool hid_usb_mouse_press(void* inst, uint8_t button) { + UNUSED(inst); + return furi_hal_hid_mouse_press(button); +} + +bool hid_usb_mouse_release(void* inst, uint8_t button) { + UNUSED(inst); + return furi_hal_hid_mouse_release(button); +} + +bool hid_usb_mouse_scroll(void* inst, int8_t delta) { + UNUSED(inst); + return furi_hal_hid_mouse_scroll(delta); +} + +bool hid_usb_mouse_move(void* inst, int8_t dx, int8_t dy) { + UNUSED(inst); + return furi_hal_hid_mouse_move(dx, dy); +} + +bool hid_usb_mouse_release_all(void* inst) { + UNUSED(inst); + return furi_hal_hid_mouse_release(0); +} + bool hid_usb_consumer_press(void* inst, uint16_t button) { UNUSED(inst); return furi_hal_hid_consumer_key_press(button); @@ -51,6 +82,7 @@ bool hid_usb_release_all(void* inst) { UNUSED(inst); bool state = furi_hal_hid_kb_release_all(); state &= furi_hal_hid_consumer_key_release_all(); + state &= hid_usb_mouse_release_all(inst); return state; } @@ -60,6 +92,7 @@ uint8_t hid_usb_get_led_state(void* inst) { } static const BadUsbHidApi hid_api_usb = { + .adjust_config = hid_usb_adjust_config, .init = hid_usb_init, .deinit = hid_usb_deinit, .set_state_callback = hid_usb_set_state_callback, @@ -67,6 +100,10 @@ static const BadUsbHidApi hid_api_usb = { .kb_press = hid_usb_kb_press, .kb_release = hid_usb_kb_release, + .mouse_press = hid_usb_mouse_press, + .mouse_release = hid_usb_mouse_release, + .mouse_scroll = hid_usb_mouse_scroll, + .mouse_move = hid_usb_mouse_move, .consumer_press = hid_usb_consumer_press, .consumer_release = hid_usb_consumer_release, .release_all = hid_usb_release_all, @@ -81,11 +118,6 @@ typedef struct { bool is_connected; } BleHidInstance; -static const BleProfileHidParams ble_hid_params = { - .device_name_prefix = "BadUSB", - .mac_xor = 0x0002, -}; - static void hid_ble_connection_status_callback(BtStatus status, void* context) { furi_assert(context); BleHidInstance* ble_hid = context; @@ -95,8 +127,38 @@ static void hid_ble_connection_status_callback(BtStatus status, void* context) { } } -void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { - UNUSED(hid_cfg); +void hid_ble_adjust_config(BadUsbHidConfig* hid_cfg) { + const uint8_t* normal_mac = furi_hal_version_get_ble_mac(); + uint8_t empty_mac[GAP_MAC_ADDR_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t default_mac[GAP_MAC_ADDR_SIZE] = {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72}; // furi_hal_bt + if(memcmp(hid_cfg->ble.mac, empty_mac, sizeof(hid_cfg->ble.mac)) == 0 || + memcmp(hid_cfg->ble.mac, normal_mac, sizeof(hid_cfg->ble.mac)) == 0 || + memcmp(hid_cfg->ble.mac, default_mac, sizeof(hid_cfg->ble.mac)) == 0) { + // Derive badusb MAC from Flipper MAC + memcpy(hid_cfg->ble.mac, normal_mac, sizeof(hid_cfg->ble.mac)); + hid_cfg->ble.mac[2]++; + uint16_t badusb_mac_xor = 0x0002; + hid_cfg->ble.mac[0] ^= badusb_mac_xor; + hid_cfg->ble.mac[1] ^= badusb_mac_xor >> 8; + } + + if(hid_cfg->ble.name[0] == '\0') { + // Derive badusb name from Flipper name + const char* badusb_device_name_prefix = "BadUSB"; + snprintf( + hid_cfg->ble.name, + sizeof(hid_cfg->ble.name), + "%s %s", + badusb_device_name_prefix, + furi_hal_version_get_name_ptr()); + } + + if(hid_cfg->ble.pairing >= GapPairingCount) { + hid_cfg->ble.pairing = GapPairingPinCodeVerifyYesNo; + } +} + +void* hid_ble_init(BadUsbHidConfig* hid_cfg) { BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); ble_hid->bt = furi_record_open(RECORD_BT); bt_disconnect(ble_hid->bt); @@ -106,7 +168,8 @@ void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); + hid_ble_adjust_config(hid_cfg); + ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, &hid_cfg->ble); furi_check(ble_hid->profile); furi_hal_bt_start_advertising(); @@ -157,6 +220,27 @@ bool hid_ble_kb_release(void* inst, uint16_t button) { return ble_profile_hid_kb_release(ble_hid->profile, button); } +bool hid_ble_mouse_press(void* inst, uint8_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_mouse_press(ble_hid->profile, button); +} +bool hid_ble_mouse_release(void* inst, uint8_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_mouse_release(ble_hid->profile, button); +} +bool hid_ble_mouse_scroll(void* inst, int8_t delta) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_mouse_scroll(ble_hid->profile, delta); +} +bool hid_ble_mouse_move(void* inst, int8_t dx, int8_t dy) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_mouse_move(ble_hid->profile, dx, dy); +} + bool hid_ble_consumer_press(void* inst, uint16_t button) { BleHidInstance* ble_hid = inst; furi_assert(ble_hid); @@ -174,6 +258,7 @@ bool hid_ble_release_all(void* inst) { furi_assert(ble_hid); bool state = ble_profile_hid_kb_release_all(ble_hid->profile); state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); + state &= ble_profile_hid_mouse_release_all(ble_hid->profile); return state; } @@ -184,6 +269,7 @@ uint8_t hid_ble_get_led_state(void* inst) { } static const BadUsbHidApi hid_api_ble = { + .adjust_config = hid_ble_adjust_config, .init = hid_ble_init, .deinit = hid_ble_deinit, .set_state_callback = hid_ble_set_state_callback, @@ -191,6 +277,10 @@ static const BadUsbHidApi hid_api_ble = { .kb_press = hid_ble_kb_press, .kb_release = hid_ble_kb_release, + .mouse_press = hid_ble_mouse_press, + .mouse_release = hid_ble_mouse_release, + .mouse_scroll = hid_ble_mouse_scroll, + .mouse_move = hid_ble_mouse_move, .consumer_press = hid_ble_consumer_press, .consumer_release = hid_ble_consumer_release, .release_all = hid_ble_release_all, diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.h b/applications/main/bad_usb/helpers/bad_usb_hid.h index 71d3a58e7..8749bdc3b 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.h +++ b/applications/main/bad_usb/helpers/bad_usb_hid.h @@ -7,19 +7,32 @@ extern "C" { #include #include +#include "ble_hid_profile.h" + typedef enum { BadUsbHidInterfaceUsb, BadUsbHidInterfaceBle, + BadUsbHidInterfaceMAX, } BadUsbHidInterface; typedef struct { - void* (*init)(FuriHalUsbHidConfig* hid_cfg); + BleProfileHidParams ble; + FuriHalUsbHidConfig usb; +} BadUsbHidConfig; + +typedef struct { + void (*adjust_config)(BadUsbHidConfig* hid_cfg); + void* (*init)(BadUsbHidConfig* hid_cfg); void (*deinit)(void* inst); void (*set_state_callback)(void* inst, HidStateCallback cb, void* context); bool (*is_connected)(void* inst); bool (*kb_press)(void* inst, uint16_t button); bool (*kb_release)(void* inst, uint16_t button); + bool (*mouse_press)(void* inst, uint8_t button); + bool (*mouse_release)(void* inst, uint8_t button); + bool (*mouse_scroll)(void* inst, int8_t delta); + bool (*mouse_move)(void* inst, int8_t dx, int8_t dy); bool (*consumer_press)(void* inst, uint16_t button); bool (*consumer_release)(void* inst, uint16_t button); bool (*release_all)(void* inst); diff --git a/applications/main/bad_usb/helpers/ble_hid_profile.c b/applications/main/bad_usb/helpers/ble_hid_profile.c new file mode 100644 index 000000000..a4f32159e --- /dev/null +++ b/applications/main/bad_usb/helpers/ble_hid_profile.c @@ -0,0 +1,429 @@ +#include "ble_hid_profile.h" + +// Based on + +#include +#include +#include +#include "ble_hid_service.h" + +#include +#include +#include + +#define HID_INFO_BASE_USB_SPECIFICATION (0x0101) +#define HID_INFO_COUNTRY_CODE (0x00) +#define BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) +#define BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) + +#define BLE_PROFILE_HID_KB_MAX_KEYS (6) +#define BLE_PROFILE_CONSUMER_MAX_KEYS (1) + +// Report ids cant be 0 +enum HidReportId { + ReportIdKeyboard = 1, + ReportIdMouse = 2, + ReportIdConsumer = 3, +}; +// Report numbers corresponded to the report id with an offset of 1 +enum HidInputNumber { + ReportNumberKeyboard = 0, + ReportNumberMouse = 1, + ReportNumberConsumer = 2, +}; + +typedef struct { + uint8_t mods; + uint8_t reserved; + uint8_t key[BLE_PROFILE_HID_KB_MAX_KEYS]; +} FURI_PACKED FuriHalBtHidKbReport; + +typedef struct { + uint8_t btn; + int8_t x; + int8_t y; + int8_t wheel; +} FURI_PACKED FuriHalBtHidMouseReport; + +typedef struct { + uint16_t key[BLE_PROFILE_CONSUMER_MAX_KEYS]; +} FURI_PACKED FuriHalBtHidConsumerReport; + +// keyboard+mouse+consumer hid report +static const uint8_t ble_profile_hid_report_map_data[] = { + // Keyboard Report + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_KEYBOARD), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdKeyboard), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), + HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(8), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(1), + HID_REPORT_SIZE(8), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_LED), + HID_REPORT_COUNT(8), + HID_REPORT_SIZE(1), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(8), + HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(BLE_PROFILE_HID_KB_MAX_KEYS), + HID_REPORT_SIZE(8), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(101), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(0), + HID_USAGE_MAXIMUM(101), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, + // Mouse Report + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_MOUSE), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_USAGE(HID_DESKTOP_POINTER), + HID_COLLECTION(HID_PHYSICAL_COLLECTION), + HID_REPORT_ID(ReportIdMouse), + HID_USAGE_PAGE(HID_PAGE_BUTTON), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(3), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_COUNT(3), + HID_REPORT_SIZE(1), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(5), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_X), + HID_USAGE(HID_DESKTOP_Y), + HID_USAGE(HID_DESKTOP_WHEEL), + HID_LOGICAL_MINIMUM(-127), + HID_LOGICAL_MAXIMUM(127), + HID_REPORT_SIZE(8), + HID_REPORT_COUNT(3), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + HID_END_COLLECTION, + HID_END_COLLECTION, + // Consumer Report + HID_USAGE_PAGE(HID_PAGE_CONSUMER), + HID_USAGE(HID_CONSUMER_CONTROL), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdConsumer), + HID_LOGICAL_MINIMUM(0), + HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), + HID_USAGE_MINIMUM(0), + HID_RI_USAGE_MAXIMUM(16, 0x3FF), + HID_REPORT_COUNT(BLE_PROFILE_CONSUMER_MAX_KEYS), + HID_REPORT_SIZE(16), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, +}; + +typedef struct { + FuriHalBleProfileBase base; + + FuriHalBtHidKbReport* kb_report; + FuriHalBtHidMouseReport* mouse_report; + FuriHalBtHidConsumerReport* consumer_report; + + BleServiceBattery* battery_svc; + BleServiceDevInfo* dev_info_svc; + BleServiceHid* hid_svc; +} BleProfileHid; +_Static_assert(offsetof(BleProfileHid, base) == 0, "Wrong layout"); + +static FuriHalBleProfileBase* ble_profile_hid_start(FuriHalBleProfileParams profile_params) { + UNUSED(profile_params); + + BleProfileHid* profile = malloc(sizeof(BleProfileHid)); + + profile->base.config = ble_profile_hid; + + profile->battery_svc = ble_svc_battery_start(true); + profile->dev_info_svc = ble_svc_dev_info_start(); + profile->hid_svc = ble_svc_hid_start(); + + // Configure HID Keyboard + profile->kb_report = malloc(sizeof(FuriHalBtHidKbReport)); + profile->mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); + profile->consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); + + // Configure Report Map characteristic + ble_svc_hid_update_report_map( + profile->hid_svc, + ble_profile_hid_report_map_data, + sizeof(ble_profile_hid_report_map_data)); + // Configure HID Information characteristic + uint8_t hid_info_val[4] = { + HID_INFO_BASE_USB_SPECIFICATION & 0x00ff, + (HID_INFO_BASE_USB_SPECIFICATION & 0xff00) >> 8, + HID_INFO_COUNTRY_CODE, + BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK | + BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK, + }; + ble_svc_hid_update_info(profile->hid_svc, hid_info_val); + + return &profile->base; +} + +static void ble_profile_hid_stop(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + ble_svc_battery_stop(hid_profile->battery_svc); + ble_svc_dev_info_stop(hid_profile->dev_info_svc); + ble_svc_hid_stop(hid_profile->hid_svc); + + free(hid_profile->kb_report); + free(hid_profile->mouse_report); + free(hid_profile->consumer_report); +} + +bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; + for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { + if(kb_report->key[i] == 0) { + kb_report->key[i] = button & 0xFF; + break; + } + } + kb_report->mods |= (button >> 8); + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberKeyboard, + (uint8_t*)kb_report, + sizeof(FuriHalBtHidKbReport)); +} + +bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + + FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; + for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { + if(kb_report->key[i] == (button & 0xFF)) { + kb_report->key[i] = 0; + break; + } + } + kb_report->mods &= ~(button >> 8); + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberKeyboard, + (uint8_t*)kb_report, + sizeof(FuriHalBtHidKbReport)); +} + +bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; + for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { + kb_report->key[i] = 0; + } + kb_report->mods = 0; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberKeyboard, + (uint8_t*)kb_report, + sizeof(FuriHalBtHidKbReport)); +} + +bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; + for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 + if(consumer_report->key[i] == 0) { + consumer_report->key[i] = button; + break; + } + } + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberConsumer, + (uint8_t*)consumer_report, + sizeof(FuriHalBtHidConsumerReport)); +} + +bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; + for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 + if(consumer_report->key[i] == button) { + consumer_report->key[i] = 0; + break; + } + } + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberConsumer, + (uint8_t*)consumer_report, + sizeof(FuriHalBtHidConsumerReport)); +} + +bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; + for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 + consumer_report->key[i] = 0; + } + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberConsumer, + (uint8_t*)consumer_report, + sizeof(FuriHalBtHidConsumerReport)); +} + +bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->x = dx; + mouse_report->y = dy; + bool state = ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); + mouse_report->x = 0; + mouse_report->y = 0; + return state; +} + +bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->btn |= button; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); +} + +bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->btn &= ~button; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); +} + +bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->btn = 0; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); +} + +bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->wheel = delta; + bool state = ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); + mouse_report->wheel = 0; + return state; +} + +// AN5289: 4.7, in order to use flash controller interval must be at least 25ms + advertisement, which is 30 ms +// Since we don't use flash controller anymore interval can be lowered to 7.5ms +#define CONNECTION_INTERVAL_MIN (0x0006) +// Up to 45 ms +#define CONNECTION_INTERVAL_MAX (0x24) + +static GapConfig template_config = { + .adv_service = + { + .UUID_Type = UUID_TYPE_16, + .Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, + }, + .appearance_char = GAP_APPEARANCE_KEYBOARD, + .bonding_mode = true, + .pairing_method = GapPairingPinCodeVerifyYesNo, + .conn_param = + { + .conn_int_min = CONNECTION_INTERVAL_MIN, + .conn_int_max = CONNECTION_INTERVAL_MAX, + .slave_latency = 0, + .supervisor_timeout = 0, + }, +}; + +static void ble_profile_hid_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) { + furi_check(profile_params); + BleProfileHidParams* hid_profile_params = profile_params; + + furi_check(config); + memcpy(config, &template_config, sizeof(GapConfig)); + + // Set MAC address + memcpy(config->mac_address, hid_profile_params->mac, sizeof(config->mac_address)); + + // Set advertise name + config->adv_name[0] = furi_hal_version_get_ble_local_device_name_ptr()[0]; + strlcpy(config->adv_name + 1, hid_profile_params->name, sizeof(config->adv_name) - 1); + + // Set bonding mode + config->bonding_mode = hid_profile_params->bonding; + + // Set pairing method + config->pairing_method = hid_profile_params->pairing; +} + +static const FuriHalBleProfileTemplate profile_callbacks = { + .start = ble_profile_hid_start, + .stop = ble_profile_hid_stop, + .get_gap_config = ble_profile_hid_get_config, +}; + +const FuriHalBleProfileTemplate* ble_profile_hid = &profile_callbacks; diff --git a/applications/main/bad_usb/helpers/ble_hid_profile.h b/applications/main/bad_usb/helpers/ble_hid_profile.h new file mode 100644 index 000000000..2302aa581 --- /dev/null +++ b/applications/main/bad_usb/helpers/ble_hid_profile.h @@ -0,0 +1,109 @@ +#pragma once + +// Based on + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Optional arguments to pass along with profile template as + * FuriHalBleProfileParams for tuning profile behavior + **/ +typedef struct { + char name[FURI_HAL_BT_ADV_NAME_LENGTH]; /**< Full device name */ + uint8_t mac[GAP_MAC_ADDR_SIZE]; /**< Full device address */ + bool bonding; /**< Save paired devices */ + GapPairing pairing; /**< Pairing security method */ +} BleProfileHidParams; + +/** Hid Keyboard Profile descriptor */ +extern const FuriHalBleProfileTemplate* ble_profile_hid; + +/** Press keyboard button + * + * @param profile profile instance + * @param button button code from HID specification + * + * @return true on success + */ +bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button); + +/** Release keyboard button + * + * @param profile profile instance + * @param button button code from HID specification + * + * @return true on success + */ +bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button); + +/** Release all keyboard buttons + * + * @param profile profile instance + * @return true on success + */ +bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile); + +/** Set the following consumer key to pressed state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button); + +/** Set the following consumer key to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button); + +/** Set consumer key to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile); + +/** Set mouse movement and send HID report + * + * @param profile profile instance + * @param dx x coordinate delta + * @param dy y coordinate delta + */ +bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy); + +/** Set mouse button to pressed state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button); + +/** Set mouse button to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button); + +/** Set mouse button to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile); + +/** Set mouse wheel position and send HID report + * + * @param profile profile instance + * @param delta number of scroll steps + */ +bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/bad_usb/helpers/ble_hid_service.c b/applications/main/bad_usb/helpers/ble_hid_service.c new file mode 100644 index 000000000..b546368dd --- /dev/null +++ b/applications/main/bad_usb/helpers/ble_hid_service.c @@ -0,0 +1,325 @@ +#include "ble_hid_service.h" + +// Based on + +#include "app_common.h" // IWYU pragma: keep +#include +#include +#include + +#include +#include + +#define TAG "BleHid" + +#define BLE_SVC_HID_REPORT_MAP_MAX_LEN (255) +#define BLE_SVC_HID_REPORT_MAX_LEN (255) +#define BLE_SVC_HID_REPORT_REF_LEN (2) +#define BLE_SVC_HID_INFO_LEN (4) +#define BLE_SVC_HID_CONTROL_POINT_LEN (1) + +#define BLE_SVC_HID_INPUT_REPORT_COUNT (3) +#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0) +#define BLE_SVC_HID_FEATURE_REPORT_COUNT (0) +#define BLE_SVC_HID_REPORT_COUNT \ + (BLE_SVC_HID_INPUT_REPORT_COUNT + BLE_SVC_HID_OUTPUT_REPORT_COUNT + \ + BLE_SVC_HID_FEATURE_REPORT_COUNT) + +typedef enum { + HidSvcGattCharacteristicProtocolMode = 0, + HidSvcGattCharacteristicReportMap, + HidSvcGattCharacteristicInfo, + HidSvcGattCharacteristicCtrlPoint, + HidSvcGattCharacteristicCount, +} HidSvcGattCharacteristicId; + +typedef struct { + uint8_t report_idx; + uint8_t report_type; +} HidSvcReportId; + +static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes"); + +static const Service_UUID_t ble_svc_hid_uuid = { + .Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, +}; + +static bool ble_svc_hid_char_desc_data_callback( + const void* context, + const uint8_t** data, + uint16_t* data_len) { + const HidSvcReportId* report_id = context; + *data_len = sizeof(HidSvcReportId); + if(data) { + *data = (const uint8_t*)report_id; + } + return false; +} + +typedef struct { + const void* data_ptr; + uint16_t data_len; +} HidSvcDataWrapper; + +static bool ble_svc_hid_report_data_callback( + const void* context, + const uint8_t** data, + uint16_t* data_len) { + const HidSvcDataWrapper* report_data = context; + if(data) { + *data = report_data->data_ptr; + *data_len = report_data->data_len; + } else { + *data_len = BLE_SVC_HID_REPORT_MAP_MAX_LEN; + } + return false; +} + +static const BleGattCharacteristicParams ble_svc_hid_chars[HidSvcGattCharacteristicCount] = { + [HidSvcGattCharacteristicProtocolMode] = + {.name = "Protocol Mode", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = 1, + .uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [HidSvcGattCharacteristicReportMap] = + {.name = "Report Map", + .data_prop_type = FlipperGattCharacteristicDataCallback, + .data.callback.fn = ble_svc_hid_report_data_callback, + .data.callback.context = NULL, + .uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_VARIABLE}, + [HidSvcGattCharacteristicInfo] = + {.name = "HID Information", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = BLE_SVC_HID_INFO_LEN, + .data.fixed.ptr = NULL, + .uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [HidSvcGattCharacteristicCtrlPoint] = + {.name = "HID Control Point", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = BLE_SVC_HID_CONTROL_POINT_LEN, + .uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_WRITE_WITHOUT_RESP, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, +}; + +static const BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr_template = { + .uuid_type = UUID_TYPE_16, + .uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID, + .max_length = BLE_SVC_HID_REPORT_REF_LEN, + .data_callback.fn = ble_svc_hid_char_desc_data_callback, + .security_permissions = ATTR_PERMISSION_NONE, + .access_permissions = ATTR_ACCESS_READ_WRITE, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT, +}; + +static const BleGattCharacteristicParams ble_svc_hid_report_template = { + .name = "Report", + .data_prop_type = FlipperGattCharacteristicDataCallback, + .data.callback.fn = ble_svc_hid_report_data_callback, + .data.callback.context = NULL, + .uuid.Char_UUID_16 = REPORT_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_VARIABLE, +}; + +struct BleServiceHid { + uint16_t svc_handle; + BleGattCharacteristicInstance chars[HidSvcGattCharacteristicCount]; + BleGattCharacteristicInstance input_report_chars[BLE_SVC_HID_INPUT_REPORT_COUNT]; + BleGattCharacteristicInstance output_report_chars[BLE_SVC_HID_OUTPUT_REPORT_COUNT]; + BleGattCharacteristicInstance feature_report_chars[BLE_SVC_HID_FEATURE_REPORT_COUNT]; + GapSvcEventHandler* event_handler; +}; + +static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) { + UNUSED(context); + + BleEventAckStatus ret = BleEventNotAck; + hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data); + evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; + // aci_gatt_attribute_modified_event_rp0* attribute_modified; + + if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { + if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { + // Process modification events + ret = BleEventAckFlowEnable; + } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { + // Process notification confirmation + ret = BleEventAckFlowEnable; + } + } + return ret; +} + +BleServiceHid* ble_svc_hid_start(void) { + BleServiceHid* hid_svc = malloc(sizeof(BleServiceHid)); + + // Register event handler + hid_svc->event_handler = + ble_event_dispatcher_register_svc_handler(ble_svc_hid_event_handler, hid_svc); + /** + * Add Human Interface Device Service + */ + if(!ble_gatt_service_add( + UUID_TYPE_16, + &ble_svc_hid_uuid, + PRIMARY_SERVICE, + 2 + /* protocol mode */ + (4 * BLE_SVC_HID_INPUT_REPORT_COUNT) + (3 * BLE_SVC_HID_OUTPUT_REPORT_COUNT) + + (3 * BLE_SVC_HID_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + + 2, /* Service + Report Map + HID Information + HID Control Point */ + &hid_svc->svc_handle)) { + free(hid_svc); + return NULL; + } + + // Maintain previously defined characteristic order + ble_gatt_characteristic_init( + hid_svc->svc_handle, + &ble_svc_hid_chars[HidSvcGattCharacteristicProtocolMode], + &hid_svc->chars[HidSvcGattCharacteristicProtocolMode]); + + uint8_t protocol_mode = 1; + ble_gatt_characteristic_update( + hid_svc->svc_handle, + &hid_svc->chars[HidSvcGattCharacteristicProtocolMode], + &protocol_mode); + + // reports + BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr; + BleGattCharacteristicParams report_char; + HidSvcReportId report_id; + + memcpy( + &ble_svc_hid_char_descr, &ble_svc_hid_char_descr_template, sizeof(ble_svc_hid_char_descr)); + memcpy(&report_char, &ble_svc_hid_report_template, sizeof(report_char)); + + ble_svc_hid_char_descr.data_callback.context = &report_id; + report_char.descriptor_params = &ble_svc_hid_char_descr; + + typedef struct { + uint8_t report_type; + uint8_t report_count; + BleGattCharacteristicInstance* chars; + } HidSvcReportCharProps; + + HidSvcReportCharProps hid_report_chars[] = { + {0x01, BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, + {0x02, BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, + {0x03, BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, + }; + + for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); + report_type_idx++) { + report_id.report_type = hid_report_chars[report_type_idx].report_type; + for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; + report_idx++) { + report_id.report_idx = report_idx + 1; + ble_gatt_characteristic_init( + hid_svc->svc_handle, + &report_char, + &hid_report_chars[report_type_idx].chars[report_idx]); + } + } + + // Setup remaining characteristics + for(size_t i = HidSvcGattCharacteristicReportMap; i < HidSvcGattCharacteristicCount; i++) { + ble_gatt_characteristic_init( + hid_svc->svc_handle, &ble_svc_hid_chars[i], &hid_svc->chars[i]); + } + + return hid_svc; +} + +bool ble_svc_hid_update_report_map(BleServiceHid* hid_svc, const uint8_t* data, uint16_t len) { + furi_assert(data); + furi_assert(hid_svc); + + HidSvcDataWrapper report_data = { + .data_ptr = data, + .data_len = len, + }; + return ble_gatt_characteristic_update( + hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicReportMap], &report_data); +} + +bool ble_svc_hid_update_input_report( + BleServiceHid* hid_svc, + uint8_t input_report_num, + uint8_t* data, + uint16_t len) { + furi_assert(data); + furi_assert(hid_svc); + furi_assert(input_report_num < BLE_SVC_HID_INPUT_REPORT_COUNT); + + HidSvcDataWrapper report_data = { + .data_ptr = data, + .data_len = len, + }; + + return ble_gatt_characteristic_update( + hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data); +} + +bool ble_svc_hid_update_info(BleServiceHid* hid_svc, uint8_t* data) { + furi_assert(data); + furi_assert(hid_svc); + + return ble_gatt_characteristic_update( + hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data); +} + +void ble_svc_hid_stop(BleServiceHid* hid_svc) { + furi_assert(hid_svc); + ble_event_dispatcher_unregister_svc_handler(hid_svc->event_handler); + // Delete characteristics + for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) { + ble_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]); + } + + typedef struct { + uint8_t report_count; + BleGattCharacteristicInstance* chars; + } HidSvcReportCharProps; + + HidSvcReportCharProps hid_report_chars[] = { + {BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, + {BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, + {BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, + }; + + for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); + report_type_idx++) { + for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; + report_idx++) { + ble_gatt_characteristic_delete( + hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]); + } + } + + // Delete service + ble_gatt_service_delete(hid_svc->svc_handle); + free(hid_svc); +} diff --git a/applications/main/bad_usb/helpers/ble_hid_service.h b/applications/main/bad_usb/helpers/ble_hid_service.h new file mode 100644 index 000000000..e1ac3b0be --- /dev/null +++ b/applications/main/bad_usb/helpers/ble_hid_service.h @@ -0,0 +1,31 @@ +#pragma once + +// Based on + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BleServiceHid BleServiceHid; + +BleServiceHid* ble_svc_hid_start(void); + +void ble_svc_hid_stop(BleServiceHid* service); + +bool ble_svc_hid_update_report_map(BleServiceHid* service, const uint8_t* data, uint16_t len); + +bool ble_svc_hid_update_input_report( + BleServiceHid* service, + uint8_t input_report_num, + uint8_t* data, + uint16_t len); + +// Expects data to be of length BLE_SVC_HID_INFO_LEN (4 bytes) +bool ble_svc_hid_update_info(BleServiceHid* service, uint8_t* data); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 8408b96ae..a64629af2 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -25,6 +25,8 @@ typedef enum { } WorkerEvtFlags; static const char ducky_cmd_id[] = {"ID"}; +static const char ducky_cmd_bt_id[] = {"BT_ID"}; +static const char ducky_cmd_ble_id[] = {"BLE_ID"}; static const uint8_t numpad_keys[10] = { HID_KEYPAD_0, @@ -40,11 +42,8 @@ static const uint8_t numpad_keys[10] = { }; uint32_t ducky_get_command_len(const char* line) { - uint32_t len = strlen(line); - for(uint32_t i = 0; i < len; i++) { - if(line[i] == ' ') return i; - } - return 0; + char* first_space = strchr(line, ' '); + return first_space ? (first_space - line) : 0; } bool ducky_is_line_end(const char chr) { @@ -180,63 +179,100 @@ static bool ducky_string_next(BadUsbScript* bad_usb) { static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { uint32_t line_len = furi_string_size(line); - const char* line_tmp = furi_string_get_cstr(line); + const char* line_cstr = furi_string_get_cstr(line); if(line_len == 0) { return SCRIPT_STATE_NEXT_LINE; // Skip empty lines } - FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); + FURI_LOG_D(WORKER_TAG, "line:%s", line_cstr); // Ducky Lang Functions - int32_t cmd_result = ducky_execute_cmd(bad_usb, line_tmp); + int32_t cmd_result = ducky_execute_cmd(bad_usb, line_cstr); if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { return cmd_result; } - // Special keys + modifiers - uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_usb, "No keycode defined for %s", line_tmp); + // Mouse Keys + uint16_t key = ducky_get_mouse_keycode_by_name(line_cstr); + if(key != HID_MOUSE_INVALID) { + bad_usb->hid->mouse_press(bad_usb->hid_inst, key); + bad_usb->hid->mouse_release(bad_usb->hid_inst, key); + return 0; } - if((key & 0xFF00) != 0) { - // It's a modifier key - uint32_t offset = ducky_get_command_len(line_tmp) + 1; - // ducky_get_command_len() returns 0 without space, so check for != 1 - if(offset != 1 && line_len > offset) { - // It's also a key combination - key |= ducky_get_keycode(bad_usb, line_tmp + offset, true); - } + + // Parse chain of modifiers linked by spaces and hyphens + uint16_t modifiers = 0; + while(1) { + key = ducky_get_next_modifier_keycode_by_name(&line_cstr); + if(key == HID_KEYBOARD_NONE) break; + + modifiers |= key; + char next_char = *line_cstr; + if(next_char == ' ' || next_char == '-') line_cstr++; } + + // Main key + char next_char = *line_cstr; + uint16_t main_key = ducky_get_keycode_by_name(line_cstr); + if(!main_key && next_char) main_key = BADUSB_ASCII_TO_KEY(bad_usb, next_char); + key = modifiers | main_key; + + if(key == 0 && next_char) ducky_error(bad_usb, "No keycode defined for %s", line_cstr); + bad_usb->hid->kb_press(bad_usb->hid_inst, key); bad_usb->hid->kb_release(bad_usb->hid_inst, key); return 0; } static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) { - if(sscanf(line, "%lX:%lX", &bad_usb->hid_cfg.vid, &bad_usb->hid_cfg.pid) == 2) { - bad_usb->hid_cfg.manuf[0] = '\0'; - bad_usb->hid_cfg.product[0] = '\0'; + FuriHalUsbHidConfig* usb_hid_cfg = &bad_usb->hid_cfg->usb; + + if(sscanf(line, "%lX:%lX", &usb_hid_cfg->vid, &usb_hid_cfg->pid) == 2) { + usb_hid_cfg->manuf[0] = '\0'; + usb_hid_cfg->product[0] = '\0'; uint8_t id_len = ducky_get_command_len(line); if(!ducky_is_line_end(line[id_len + 1])) { sscanf( &line[id_len + 1], "%31[^\r\n:]:%31[^\r\n]", - bad_usb->hid_cfg.manuf, - bad_usb->hid_cfg.product); + usb_hid_cfg->manuf, + usb_hid_cfg->product); } FURI_LOG_D( WORKER_TAG, "set id: %04lX:%04lX mfr:%s product:%s", - bad_usb->hid_cfg.vid, - bad_usb->hid_cfg.pid, - bad_usb->hid_cfg.manuf, - bad_usb->hid_cfg.product); + usb_hid_cfg->vid, + usb_hid_cfg->pid, + usb_hid_cfg->manuf, + usb_hid_cfg->product); return true; } return false; } +static bool ducky_set_ble_id(BadUsbScript* bad_usb, const char* line) { + BleProfileHidParams* ble_hid_cfg = &bad_usb->hid_cfg->ble; + + size_t line_len = strlen(line); + size_t mac_len = sizeof(ble_hid_cfg->mac) * 3; // 2 hex chars + separator per byte + if(line_len < mac_len + 1) return false; // MAC + at least 1 char for name + + for(size_t i = 0; i < sizeof(ble_hid_cfg->mac); i++) { + const char* hex_byte = &line[i * 3]; + // This sscanf() doesn't work well with %02hhX, need to use a u32 + uint32_t temp_uint; + if(sscanf(hex_byte, "%02lX", &temp_uint) != 1) { + return false; + } + ble_hid_cfg->mac[sizeof(ble_hid_cfg->mac) - 1 - i] = temp_uint; + } + + strlcpy(ble_hid_cfg->name, line + mac_len, sizeof(ble_hid_cfg->name)); + FURI_LOG_D(WORKER_TAG, "set ble id: %s", line); + return true; +} + static void bad_usb_hid_state_callback(bool state, void* context) { furi_assert(context); BadUsbScript* bad_usb = context; @@ -275,17 +311,30 @@ static bool ducky_script_preload(BadUsbScript* bad_usb, File* script_file) { } } while(ret > 0); - const char* line_tmp = furi_string_get_cstr(bad_usb->line); - bool id_set = false; // Looking for ID command at first line - if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) { - id_set = ducky_set_usb_id(bad_usb, &line_tmp[strlen(ducky_cmd_id) + 1]); + if(bad_usb->load_id_cfg) { + const char* line_tmp = furi_string_get_cstr(bad_usb->line); + BadUsbHidInterface interface = *bad_usb->interface; + // Look for ID/BLE_ID/BT_ID command on first line + if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) { + if(ducky_set_usb_id(bad_usb, &line_tmp[strlen(ducky_cmd_id) + 1])) { + interface = BadUsbHidInterfaceUsb; + } + } else if( + strncmp(line_tmp, ducky_cmd_ble_id, strlen(ducky_cmd_ble_id)) == 0 || + strncmp(line_tmp, ducky_cmd_bt_id, strlen(ducky_cmd_bt_id)) == 0) { + if(ducky_set_ble_id(bad_usb, &line_tmp[ducky_get_command_len(line_tmp) + 1])) { + interface = BadUsbHidInterfaceBle; + } + } + + // Auto-switch based on ID/BLE_ID/BT_ID command, user can override manually after + if(interface != *bad_usb->interface) { + *bad_usb->interface = interface; + bad_usb->hid = bad_usb_hid_get_interface(*bad_usb->interface); + } } - if(id_set) { - bad_usb->hid_inst = bad_usb->hid->init(&bad_usb->hid_cfg); - } else { - bad_usb->hid_inst = bad_usb->hid->init(NULL); - } + bad_usb->hid_inst = bad_usb->hid->init(bad_usb->hid_cfg); bad_usb->hid->set_state_callback(bad_usb->hid_inst, bad_usb_hid_state_callback, bad_usb); storage_file_seek(script_file, 0, true); @@ -388,9 +437,12 @@ static int32_t bad_usb_worker(void* context) { bad_usb->line = furi_string_alloc(); bad_usb->line_prev = furi_string_alloc(); bad_usb->string_print = furi_string_alloc(); + bad_usb->st.elapsed = 0; while(1) { + uint32_t start = furi_get_tick(); if(worker_state == BadUsbStateInit) { // State: initialization + start = 0; if(storage_file_open( script_file, furi_string_get_cstr(bad_usb->file_path), @@ -400,7 +452,7 @@ static int32_t bad_usb_worker(void* context) { if(bad_usb->hid->is_connected(bad_usb->hid_inst)) { worker_state = BadUsbStateIdle; // Ready to run } else { - worker_state = BadUsbStateNotConnected; // USB not connected + worker_state = BadUsbStateNotConnected; // Not connected } } else { worker_state = BadUsbStateScriptError; // Script preload error @@ -411,7 +463,8 @@ static int32_t bad_usb_worker(void* context) { } bad_usb->st.state = worker_state; - } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected + } else if(worker_state == BadUsbStateNotConnected) { // State: Not connected + start = 0; uint32_t flags = bad_usb_flags_get( WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop, FuriWaitForever); @@ -421,11 +474,12 @@ static int32_t bad_usb_worker(void* context) { } else if(flags & WorkerEvtConnect) { worker_state = BadUsbStateIdle; // Ready to run } else if(flags & WorkerEvtStartStop) { - worker_state = BadUsbStateWillRun; // Will run when USB is connected + worker_state = BadUsbStateWillRun; // Will run when connected } bad_usb->st.state = worker_state; } else if(worker_state == BadUsbStateIdle) { // State: ready to start + start = 0; uint32_t flags = bad_usb_flags_get( WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever); @@ -444,12 +498,14 @@ static int32_t bad_usb_worker(void* context) { bad_usb->file_end = false; storage_file_seek(script_file, 0, true); worker_state = BadUsbStateRunning; + bad_usb->st.elapsed = 0; } else if(flags & WorkerEvtDisconnect) { - worker_state = BadUsbStateNotConnected; // USB disconnected + worker_state = BadUsbStateNotConnected; // Disconnected } bad_usb->st.state = worker_state; } else if(worker_state == BadUsbStateWillRun) { // State: start on connection + start = 0; uint32_t flags = bad_usb_flags_get( WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); @@ -474,6 +530,7 @@ static int32_t bad_usb_worker(void* context) { if(flags == (unsigned)FuriFlagErrorTimeout) { // If nothing happened - start script execution worker_state = BadUsbStateRunning; + bad_usb->st.elapsed = 0; } else if(flags & WorkerEvtStartStop) { worker_state = BadUsbStateIdle; furi_thread_flags_clear(WorkerEvtStartStop); @@ -484,7 +541,7 @@ static int32_t bad_usb_worker(void* context) { bad_usb->st.state = worker_state; } else if(worker_state == BadUsbStateRunning) { // State: running - uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint16_t delay_cur = (delay_val > 100) ? (100) : (delay_val); uint32_t flags = furi_thread_flags_wait( WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, FuriFlagWaitAny, @@ -498,19 +555,21 @@ static int32_t bad_usb_worker(void* context) { worker_state = BadUsbStateIdle; // Stop executing script bad_usb->hid->release_all(bad_usb->hid_inst); } else if(flags & WorkerEvtDisconnect) { - worker_state = BadUsbStateNotConnected; // USB disconnected + worker_state = BadUsbStateNotConnected; // Disconnected bad_usb->hid->release_all(bad_usb->hid_inst); } else if(flags & WorkerEvtPauseResume) { pause_state = BadUsbStateRunning; worker_state = BadUsbStatePaused; // Pause } bad_usb->st.state = worker_state; + bad_usb->st.elapsed += (furi_get_tick() - start); continue; } else if( (flags == (unsigned)FuriFlagErrorTimeout) || (flags == (unsigned)FuriFlagErrorResource)) { if(delay_val > 0) { bad_usb->st.delay_remain--; + bad_usb->st.elapsed += (furi_get_tick() - start); continue; } bad_usb->st.state = BadUsbStateRunning; @@ -525,6 +584,7 @@ static int32_t bad_usb_worker(void* context) { worker_state = BadUsbStateIdle; bad_usb->st.state = BadUsbStateDone; bad_usb->hid->release_all(bad_usb->hid_inst); + bad_usb->st.elapsed += (furi_get_tick() - start); continue; } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays delay_val = bad_usb->defdelay; @@ -533,14 +593,15 @@ static int32_t bad_usb_worker(void* context) { } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input worker_state = BadUsbStateWaitForBtn; bad_usb->st.state = BadUsbStateWaitForBtn; // Show long delays - } else if(delay_val > 1000) { + } else if(delay_val > 100) { bad_usb->st.state = BadUsbStateDelay; // Show long delays - bad_usb->st.delay_remain = delay_val / 1000; + bad_usb->st.delay_remain = delay_val / 100; } } else { furi_check((flags & FuriFlagError) == 0); } } else if(worker_state == BadUsbStateWaitForBtn) { // State: Wait for button Press + start = 0; uint32_t flags = bad_usb_flags_get( WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, FuriWaitForever); @@ -551,13 +612,14 @@ static int32_t bad_usb_worker(void* context) { delay_val = 0; worker_state = BadUsbStateRunning; } else if(flags & WorkerEvtDisconnect) { - worker_state = BadUsbStateNotConnected; // USB disconnected + worker_state = BadUsbStateNotConnected; // Disconnected bad_usb->hid->release_all(bad_usb->hid_inst); } bad_usb->st.state = worker_state; continue; } } else if(worker_state == BadUsbStatePaused) { // State: Paused + start = 0; uint32_t flags = bad_usb_flags_get( WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, FuriWaitForever); @@ -569,14 +631,14 @@ static int32_t bad_usb_worker(void* context) { bad_usb->st.state = worker_state; bad_usb->hid->release_all(bad_usb->hid_inst); } else if(flags & WorkerEvtDisconnect) { - worker_state = BadUsbStateNotConnected; // USB disconnected + worker_state = BadUsbStateNotConnected; // Disconnected bad_usb->st.state = worker_state; bad_usb->hid->release_all(bad_usb->hid_inst); } else if(flags & WorkerEvtPauseResume) { if(pause_state == BadUsbStateRunning) { if(delay_val > 0) { bad_usb->st.state = BadUsbStateDelay; - bad_usb->st.delay_remain = delay_val / 1000; + bad_usb->st.delay_remain = delay_val / 100; } else { bad_usb->st.state = BadUsbStateRunning; delay_val = 0; @@ -603,13 +665,14 @@ static int32_t bad_usb_worker(void* context) { worker_state = BadUsbStateIdle; // Stop executing script bad_usb->hid->release_all(bad_usb->hid_inst); } else if(flags & WorkerEvtDisconnect) { - worker_state = BadUsbStateNotConnected; // USB disconnected + worker_state = BadUsbStateNotConnected; // Disconnected bad_usb->hid->release_all(bad_usb->hid_inst); } else if(flags & WorkerEvtPauseResume) { pause_state = BadUsbStateStringDelay; worker_state = BadUsbStatePaused; // Pause } bad_usb->st.state = worker_state; + bad_usb->st.elapsed += (furi_get_tick() - start); continue; } else if( (flags == (unsigned)FuriFlagErrorTimeout) || @@ -625,6 +688,7 @@ static int32_t bad_usb_worker(void* context) { } else if( (worker_state == BadUsbStateFileError) || (worker_state == BadUsbStateScriptError)) { // State: error + start = 0; uint32_t flags = bad_usb_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command @@ -632,6 +696,9 @@ static int32_t bad_usb_worker(void* context) { break; } } + if(start) { + bad_usb->st.elapsed += (furi_get_tick() - start); + } } bad_usb->hid->set_state_callback(bad_usb->hid_inst, NULL, NULL); @@ -654,7 +721,11 @@ static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) { memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout))); } -BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) { +BadUsbScript* bad_usb_script_open( + FuriString* file_path, + BadUsbHidInterface* interface, + BadUsbHidConfig* hid_cfg, + bool load_id_cfg) { furi_assert(file_path); BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); @@ -664,7 +735,10 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface inte bad_usb->st.state = BadUsbStateInit; bad_usb->st.error[0] = '\0'; - bad_usb->hid = bad_usb_hid_get_interface(interface); + bad_usb->interface = interface; + bad_usb->hid_cfg = hid_cfg; + bad_usb->load_id_cfg = load_id_cfg; + bad_usb->hid = bad_usb_hid_get_interface(*bad_usb->interface); bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb); furi_thread_start(bad_usb->thread); diff --git a/applications/main/bad_usb/helpers/ducky_script.h b/applications/main/bad_usb/helpers/ducky_script.h index 9519623f6..9131ef43e 100644 --- a/applications/main/bad_usb/helpers/ducky_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -30,11 +30,16 @@ typedef struct { uint32_t delay_remain; size_t error_line; char error[64]; + uint32_t elapsed; } BadUsbState; typedef struct BadUsbScript BadUsbScript; -BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface); +BadUsbScript* bad_usb_script_open( + FuriString* file_path, + BadUsbHidInterface* interface, + BadUsbHidConfig* hid_cfg, + bool load_id_cfg); void bad_usb_script_close(BadUsbScript* bad_usb); diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c index 79dcdd531..6c6fe36c7 100644 --- a/applications/main/bad_usb/helpers/ducky_script_commands.c +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -1,4 +1,5 @@ #include +#include #include "ducky_script.h" #include "ducky_script_i.h" @@ -124,34 +125,58 @@ static int32_t ducky_fnc_altstring(BadUsbScript* bad_usb, const char* line, int3 static int32_t ducky_fnc_hold(BadUsbScript* bad_usb, const char* line, int32_t param) { UNUSED(param); - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_usb, "No keycode defined for %s", line); - } - bad_usb->key_hold_nb++; + if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) { - return ducky_error(bad_usb, "Too many keys are hold"); + return ducky_error(bad_usb, "Too many keys are held"); } - bad_usb->hid->kb_press(bad_usb->hid_inst, key); - return 0; + + // Handle Mouse Keys here + uint16_t key = ducky_get_mouse_keycode_by_name(line); + if(key != HID_MOUSE_NONE) { + bad_usb->key_hold_nb++; + bad_usb->hid->mouse_press(bad_usb->hid_inst, key); + return 0; + } + + // Handle Keyboard keys here + key = ducky_get_keycode(bad_usb, line, true); + if(key != HID_KEYBOARD_NONE) { + bad_usb->key_hold_nb++; + bad_usb->hid->kb_press(bad_usb->hid_inst, key); + return 0; + } + + // keyboard and mouse were none + return ducky_error(bad_usb, "Unknown keycode for %s", line); } static int32_t ducky_fnc_release(BadUsbScript* bad_usb, const char* line, int32_t param) { UNUSED(param); - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_usb, "No keycode defined for %s", line); - } + if(bad_usb->key_hold_nb == 0) { - return ducky_error(bad_usb, "No keys are hold"); + return ducky_error(bad_usb, "No keys are held"); } - bad_usb->key_hold_nb--; - bad_usb->hid->kb_release(bad_usb->hid_inst, key); - return 0; + + // Handle Mouse Keys here + uint16_t key = ducky_get_mouse_keycode_by_name(line); + if(key != HID_MOUSE_NONE) { + bad_usb->key_hold_nb--; + bad_usb->hid->mouse_release(bad_usb->hid_inst, key); + return 0; + } + + //Handle Keyboard Keys here + key = ducky_get_keycode(bad_usb, line, true); + if(key != HID_KEYBOARD_NONE) { + bad_usb->key_hold_nb--; + bad_usb->hid->kb_release(bad_usb->hid_inst, key); + return 0; + } + + // keyboard and mouse were none + return ducky_error(bad_usb, "No keycode defined for %s", line); } static int32_t ducky_fnc_media(BadUsbScript* bad_usb, const char* line, int32_t param) { @@ -191,9 +216,48 @@ static int32_t ducky_fnc_waitforbutton(BadUsbScript* bad_usb, const char* line, return SCRIPT_STATE_WAIT_FOR_BTN; } +static int32_t ducky_fnc_mouse_scroll(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[strcspn(line, " ") + 1]; + int32_t mouse_scroll_dist = 0; + + if(strint_to_int32(line, NULL, &mouse_scroll_dist, 10) != StrintParseNoError) { + return ducky_error(bad_usb, "Invalid Number %s", line); + } + + bad_usb->hid->mouse_scroll(bad_usb->hid_inst, mouse_scroll_dist); + + return 0; +} + +static int32_t ducky_fnc_mouse_move(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[strcspn(line, " ") + 1]; + int32_t mouse_move_x = 0; + int32_t mouse_move_y = 0; + + if(strint_to_int32(line, NULL, &mouse_move_x, 10) != StrintParseNoError) { + return ducky_error(bad_usb, "Invalid Number %s", line); + } + + line = &line[strcspn(line, " ") + 1]; + + if(strint_to_int32(line, NULL, &mouse_move_y, 10) != StrintParseNoError) { + return ducky_error(bad_usb, "Invalid Number %s", line); + } + + bad_usb->hid->mouse_move(bad_usb->hid_inst, mouse_move_x, mouse_move_y); + + return 0; +} + static const DuckyCmd ducky_commands[] = { {"REM", NULL, -1}, {"ID", NULL, -1}, + {"BT_ID", NULL, -1}, + {"BLE_ID", NULL, -1}, {"DELAY", ducky_fnc_delay, -1}, {"STRING", ducky_fnc_string, 0}, {"STRINGLN", ducky_fnc_string, 1}, @@ -213,6 +277,10 @@ static const DuckyCmd ducky_commands[] = { {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, {"MEDIA", ducky_fnc_media, -1}, {"GLOBE", ducky_fnc_globe, -1}, + {"MOUSEMOVE", ducky_fnc_mouse_move, -1}, + {"MOUSE_MOVE", ducky_fnc_mouse_move, -1}, + {"MOUSESCROLL", ducky_fnc_mouse_scroll, -1}, + {"MOUSE_SCROLL", ducky_fnc_mouse_scroll, -1}, }; #define TAG "BadUsb" diff --git a/applications/main/bad_usb/helpers/ducky_script_i.h b/applications/main/bad_usb/helpers/ducky_script_i.h index 464c8a72b..d735a8407 100644 --- a/applications/main/bad_usb/helpers/ducky_script_i.h +++ b/applications/main/bad_usb/helpers/ducky_script_i.h @@ -18,8 +18,13 @@ extern "C" { #define FILE_BUFFER_LEN 16 +#define HID_MOUSE_INVALID 0 +#define HID_MOUSE_NONE 0 + struct BadUsbScript { - FuriHalUsbHidConfig hid_cfg; + BadUsbHidInterface* interface; + BadUsbHidConfig* hid_cfg; + bool load_id_cfg; const BadUsbHidApi* hid; void* hid_inst; FuriThread* thread; @@ -51,10 +56,14 @@ uint32_t ducky_get_command_len(const char* line); bool ducky_is_line_end(const char chr); +uint16_t ducky_get_next_modifier_keycode_by_name(const char** param); + uint16_t ducky_get_keycode_by_name(const char* param); uint16_t ducky_get_media_keycode_by_name(const char* param); +uint8_t ducky_get_mouse_keycode_by_name(const char* param); + bool ducky_get_number(const char* param, uint32_t* val); void ducky_numlock_on(BadUsbScript* bad_usb); diff --git a/applications/main/bad_usb/helpers/ducky_script_keycodes.c b/applications/main/bad_usb/helpers/ducky_script_keycodes.c index 290618c13..ce957bb4e 100644 --- a/applications/main/bad_usb/helpers/ducky_script_keycodes.c +++ b/applications/main/bad_usb/helpers/ducky_script_keycodes.c @@ -6,21 +6,16 @@ typedef struct { uint16_t keycode; } DuckyKey; -static const DuckyKey ducky_keys[] = { - {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT}, - {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, - {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, - {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, - {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, - {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL}, - +static const DuckyKey ducky_modifier_keys[] = { {"CTRL", KEY_MOD_LEFT_CTRL}, {"CONTROL", KEY_MOD_LEFT_CTRL}, {"SHIFT", KEY_MOD_LEFT_SHIFT}, {"ALT", KEY_MOD_LEFT_ALT}, {"GUI", KEY_MOD_LEFT_GUI}, {"WINDOWS", KEY_MOD_LEFT_GUI}, +}; +static const DuckyKey ducky_keys[] = { {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, {"DOWN", HID_KEYBOARD_DOWN_ARROW}, {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, @@ -108,6 +103,34 @@ static const DuckyKey ducky_media_keys[] = { {"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT}, }; +static const DuckyKey ducky_mouse_keys[] = { + {"LEFTCLICK", HID_MOUSE_BTN_LEFT}, + {"LEFT_CLICK", HID_MOUSE_BTN_LEFT}, + {"RIGHTCLICK", HID_MOUSE_BTN_RIGHT}, + {"RIGHT_CLICK", HID_MOUSE_BTN_RIGHT}, + {"MIDDLECLICK", HID_MOUSE_BTN_WHEEL}, + {"MIDDLE_CLICK", HID_MOUSE_BTN_WHEEL}, + {"WHEELCLICK", HID_MOUSE_BTN_WHEEL}, + {"WHEEL_CLICK", HID_MOUSE_BTN_WHEEL}, +}; + +uint16_t ducky_get_next_modifier_keycode_by_name(const char** param) { + const char* input_str = *param; + + for(size_t i = 0; i < COUNT_OF(ducky_modifier_keys); i++) { + size_t key_cmd_len = strlen(ducky_modifier_keys[i].name); + if((strncmp(input_str, ducky_modifier_keys[i].name, key_cmd_len) == 0)) { + char next_char_after_key = input_str[key_cmd_len]; + if(ducky_is_line_end(next_char_after_key) || (next_char_after_key == '-')) { + *param = &input_str[key_cmd_len]; + return ducky_modifier_keys[i].keycode; + } + } + } + + return HID_KEYBOARD_NONE; +} + uint16_t ducky_get_keycode_by_name(const char* param) { for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { size_t key_cmd_len = strlen(ducky_keys[i].name); @@ -131,3 +154,15 @@ uint16_t ducky_get_media_keycode_by_name(const char* param) { return HID_CONSUMER_UNASSIGNED; } + +uint8_t ducky_get_mouse_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_mouse_keys); i++) { + size_t key_cmd_len = strlen(ducky_mouse_keys[i].name); + if((strncmp(param, ducky_mouse_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_mouse_keys[i].keycode; + } + } + + return HID_MOUSE_INVALID; +} diff --git a/applications/main/bad_usb/resources/badusb/assets/layouts/de-DE-mac.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/de-DE-mac.kl new file mode 100755 index 000000000..471b7143e Binary files /dev/null and b/applications/main/bad_usb/resources/badusb/assets/layouts/de-DE-mac.kl differ diff --git a/applications/main/bad_usb/resources/badusb/test_mouse.txt b/applications/main/bad_usb/resources/badusb/test_mouse.txt new file mode 100644 index 000000000..97391cf17 --- /dev/null +++ b/applications/main/bad_usb/resources/badusb/test_mouse.txt @@ -0,0 +1,46 @@ +ID 1234:abcd Generic:USB Keyboard +REM Declare ourselves as a generic usb keyboard +REM You can override this to use something else +REM Check the `lsusb` command to know your own devices IDs + +DEFAULT_DELAY 200 +DEFAULT_STRING_DELAY 100 + +DELAY 1000 + +REM Test all mouse functions +LEFTCLICK +RIGHTCLICK +MIDDLECLICK + +DELAY 1000 + +MOUSEMOVE -10 0 +REPEAT 20 +MOUSEMOVE 0 10 +REPEAT 20 +MOUSEMOVE 10 0 +REPEAT 20 +MOUSEMOVE 0 -10 +REPEAT 20 + +DELAY 1000 + +MOUSESCROLL -50 +MOUSESCROLL 50 + +DELAY 1000 + +REM Verify Mouse hold working +HOLD LEFTCLICK +DELAY 2000 +RELEASE LEFTCLICK + +DELAY 1000 + +REM Verify KB hold working +HOLD M +DELAY 2000 +RELEASE M + +ENTER \ No newline at end of file diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c index ae440cade..5c59fe6b9 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.c @@ -1,10 +1,68 @@ #include "../bad_usb_app_i.h" -enum SubmenuIndex { +enum ConfigIndex { ConfigIndexKeyboardLayout, - ConfigIndexBleUnpair, + ConfigIndexConnection, }; +enum ConfigIndexBle { + ConfigIndexBlePersistPairing = ConfigIndexConnection + 1, + ConfigIndexBlePairingMode, + ConfigIndexBleSetDeviceName, + ConfigIndexBleSetMacAddress, + ConfigIndexBleRandomizeMacAddress, + ConfigIndexBleRestoreDefaults, + ConfigIndexBleRemovePairing, +}; + +enum ConfigIndexUsb { + ConfigIndexUsbSetManufacturerName = ConfigIndexConnection + 1, + ConfigIndexUsbSetProductName, + ConfigIndexUsbSetVidPid, + ConfigIndexUsbRandomizeVidPid, + ConfigIndexUsbRestoreDefaults, +}; + +void bad_usb_scene_config_connection_callback(VariableItem* item) { + BadUsbApp* bad_usb = variable_item_get_context(item); + bad_usb_set_interface( + bad_usb, + bad_usb->interface == BadUsbHidInterfaceBle ? BadUsbHidInterfaceUsb : + BadUsbHidInterfaceBle); + variable_item_set_current_value_text( + item, bad_usb->interface == BadUsbHidInterfaceBle ? "BLE" : "USB"); + view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ConfigIndexConnection); +} + +void bad_usb_scene_config_ble_persist_pairing_callback(VariableItem* item) { + BadUsbApp* bad_usb = variable_item_get_context(item); + bool value = variable_item_get_current_value_index(item); + const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface); + // Apply to current script config + bad_usb->script_hid_cfg.ble.bonding = value; + hid->adjust_config(&bad_usb->script_hid_cfg); + // Set in user config to save in settings file + bad_usb->user_hid_cfg.ble.bonding = value; + variable_item_set_current_value_text(item, value ? "ON" : "OFF"); +} + +const char* const ble_pairing_mode_names[GapPairingCount] = { + "YesNo", + "PIN Type", + "PIN Y/N", +}; +void bad_usb_scene_config_ble_pairing_mode_callback(VariableItem* item) { + BadUsbApp* bad_usb = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface); + // Apply to current script config + bad_usb->script_hid_cfg.ble.pairing = index; + hid->adjust_config(&bad_usb->script_hid_cfg); + // Set in user config to save in settings file + bad_usb->user_hid_cfg.ble.pairing = index; + variable_item_set_current_value_text(item, ble_pairing_mode_names[index]); +} + void bad_usb_scene_config_select_callback(void* context, uint32_t index) { BadUsbApp* bad_usb = context; @@ -13,12 +71,59 @@ void bad_usb_scene_config_select_callback(void* context, uint32_t index) { static void draw_menu(BadUsbApp* bad_usb) { VariableItemList* var_item_list = bad_usb->var_item_list; + VariableItem* item; variable_item_list_reset(var_item_list); variable_item_list_add(var_item_list, "Keyboard Layout (global)", 0, NULL, NULL); - variable_item_list_add(var_item_list, "Remove Pairing", 0, NULL, NULL); + item = variable_item_list_add( + var_item_list, "Connection", 2, bad_usb_scene_config_connection_callback, bad_usb); + variable_item_set_current_value_index(item, bad_usb->interface == BadUsbHidInterfaceBle); + variable_item_set_current_value_text( + item, bad_usb->interface == BadUsbHidInterfaceBle ? "BLE" : "USB"); + + if(bad_usb->interface == BadUsbHidInterfaceBle) { + BleProfileHidParams* ble_hid_cfg = &bad_usb->script_hid_cfg.ble; + + item = variable_item_list_add( + var_item_list, + "Persist Pairing", + 2, + bad_usb_scene_config_ble_persist_pairing_callback, + bad_usb); + variable_item_set_current_value_index(item, ble_hid_cfg->bonding); + variable_item_set_current_value_text(item, ble_hid_cfg->bonding ? "ON" : "OFF"); + + item = variable_item_list_add( + var_item_list, + "Pairing Mode", + GapPairingCount, + bad_usb_scene_config_ble_pairing_mode_callback, + bad_usb); + variable_item_set_current_value_index(item, ble_hid_cfg->pairing); + variable_item_set_current_value_text(item, ble_pairing_mode_names[ble_hid_cfg->pairing]); + + variable_item_list_add(var_item_list, "Set Device Name", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Set MAC Address", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Randomize MAC Address", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Restore BLE Defaults", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Remove BLE Pairing", 0, NULL, NULL); + } else { + variable_item_list_add(var_item_list, "Set Manufacturer Name", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Set Product Name", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Set VID and PID", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Randomize VID and PID", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Restore USB Defaults", 0, NULL, NULL); + } } void bad_usb_scene_config_on_enter(void* context) { @@ -28,7 +133,8 @@ void bad_usb_scene_config_on_enter(void* context) { variable_item_list_set_enter_callback( var_item_list, bad_usb_scene_config_select_callback, bad_usb); draw_menu(bad_usb); - variable_item_list_set_selected_item(var_item_list, 0); + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfig)); view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig); } @@ -38,13 +144,110 @@ bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(bad_usb->scene_manager, BadUsbSceneConfig, event.event); consumed = true; - if(event.event == ConfigIndexKeyboardLayout) { + const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface); + + switch(event.event) { + case ConfigIndexKeyboardLayout: scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout); - } else if(event.event == ConfigIndexBleUnpair) { - scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfirmUnpair); + break; + case ConfigIndexConnection: + // Refresh default values for new interface + hid->adjust_config(&bad_usb->script_hid_cfg); + // Redraw menu with new interface options + draw_menu(bad_usb); + break; + default: + break; + } + if(bad_usb->interface == BadUsbHidInterfaceBle) { + switch(event.event) { + case ConfigIndexBleSetDeviceName: + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigBleName); + break; + case ConfigIndexBleSetMacAddress: + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigBleMac); + break; + case ConfigIndexBleRandomizeMacAddress: + // Apply to current script config + furi_hal_random_fill_buf( + bad_usb->script_hid_cfg.ble.mac, sizeof(bad_usb->script_hid_cfg.ble.mac)); + bad_usb->script_hid_cfg.ble.mac[sizeof(bad_usb->script_hid_cfg.ble.mac) - 1] |= + 0b11 << 6; // Set 2 MSB for Random Static Address + hid->adjust_config(&bad_usb->script_hid_cfg); + // Set in user config to save in settings file + memcpy( + bad_usb->user_hid_cfg.ble.mac, + bad_usb->script_hid_cfg.ble.mac, + sizeof(bad_usb->user_hid_cfg.ble.mac)); + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneDone); + break; + case ConfigIndexBleRestoreDefaults: + // Apply to current script config + bad_usb->script_hid_cfg.ble.name[0] = '\0'; + memset( + bad_usb->script_hid_cfg.ble.mac, 0, sizeof(bad_usb->script_hid_cfg.ble.mac)); + bad_usb->script_hid_cfg.ble.bonding = true; + bad_usb->script_hid_cfg.ble.pairing = GapPairingPinCodeVerifyYesNo; + hid->adjust_config(&bad_usb->script_hid_cfg); + // Set in user config to save in settings file + memcpy( + &bad_usb->user_hid_cfg.ble, + &bad_usb->script_hid_cfg.ble, + sizeof(bad_usb->user_hid_cfg.ble)); + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneDone); + break; + case ConfigIndexBleRemovePairing: + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfirmUnpair); + break; + default: + break; + } } else { - furi_crash("Unknown key type"); + switch(event.event) { + case ConfigIndexUsbSetManufacturerName: + scene_manager_set_scene_state( + bad_usb->scene_manager, BadUsbSceneConfigUsbName, true); + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigUsbName); + break; + case ConfigIndexUsbSetProductName: + scene_manager_set_scene_state( + bad_usb->scene_manager, BadUsbSceneConfigUsbName, false); + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigUsbName); + break; + case ConfigIndexUsbSetVidPid: + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigUsbVidPid); + break; + case ConfigIndexUsbRandomizeVidPid: + furi_hal_random_fill_buf( + (void*)bad_usb->usb_vidpid_buf, sizeof(bad_usb->usb_vidpid_buf)); + // Apply to current script config + bad_usb->script_hid_cfg.usb.vid = bad_usb->usb_vidpid_buf[0]; + bad_usb->script_hid_cfg.usb.pid = bad_usb->usb_vidpid_buf[1]; + hid->adjust_config(&bad_usb->script_hid_cfg); + // Set in user config to save in settings file + bad_usb->user_hid_cfg.usb.vid = bad_usb->script_hid_cfg.usb.vid; + bad_usb->user_hid_cfg.usb.pid = bad_usb->script_hid_cfg.usb.pid; + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneDone); + break; + case ConfigIndexUsbRestoreDefaults: + // Apply to current script config + bad_usb->script_hid_cfg.usb.vid = 0; + bad_usb->script_hid_cfg.usb.pid = 0; + bad_usb->script_hid_cfg.usb.manuf[0] = '\0'; + bad_usb->script_hid_cfg.usb.product[0] = '\0'; + hid->adjust_config(&bad_usb->script_hid_cfg); + // Set in user config to save in settings file + memcpy( + &bad_usb->user_hid_cfg.usb, + &bad_usb->script_hid_cfg.usb, + sizeof(bad_usb->user_hid_cfg.usb)); + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneDone); + break; + default: + break; + } } } diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.h b/applications/main/bad_usb/scenes/bad_usb_scene_config.h index 3d1b8b1a7..2ea25e134 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.h +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.h @@ -3,5 +3,9 @@ ADD_SCENE(bad_usb, work, Work) ADD_SCENE(bad_usb, error, Error) ADD_SCENE(bad_usb, config, Config) ADD_SCENE(bad_usb, config_layout, ConfigLayout) +ADD_SCENE(bad_usb, config_ble_name, ConfigBleName) +ADD_SCENE(bad_usb, config_ble_mac, ConfigBleMac) +ADD_SCENE(bad_usb, config_usb_name, ConfigUsbName) +ADD_SCENE(bad_usb, config_usb_vidpid, ConfigUsbVidPid) ADD_SCENE(bad_usb, confirm_unpair, ConfirmUnpair) -ADD_SCENE(bad_usb, unpair_done, UnpairDone) +ADD_SCENE(bad_usb, done, Done) diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config_ble_mac.c b/applications/main/bad_usb/scenes/bad_usb_scene_config_ble_mac.c new file mode 100644 index 000000000..7ad4e3ed4 --- /dev/null +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config_ble_mac.c @@ -0,0 +1,73 @@ +#include "../bad_usb_app_i.h" + +enum ByteInputResult { + ByteInputResultOk, +}; + +static void reverse_mac_addr(uint8_t mac_addr[GAP_MAC_ADDR_SIZE]) { + uint8_t tmp; + for(size_t i = 0; i < GAP_MAC_ADDR_SIZE / 2; i++) { + tmp = mac_addr[i]; + mac_addr[i] = mac_addr[GAP_MAC_ADDR_SIZE - 1 - i]; + mac_addr[GAP_MAC_ADDR_SIZE - 1 - i] = tmp; + } +} + +void bad_usb_scene_config_ble_mac_byte_input_callback(void* context) { + BadUsbApp* bad_usb = context; + + view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ByteInputResultOk); +} + +void bad_usb_scene_config_ble_mac_on_enter(void* context) { + BadUsbApp* bad_usb = context; + ByteInput* byte_input = bad_usb->byte_input; + + memcpy(bad_usb->ble_mac_buf, bad_usb->script_hid_cfg.ble.mac, sizeof(bad_usb->ble_mac_buf)); + reverse_mac_addr(bad_usb->ble_mac_buf); + byte_input_set_header_text(byte_input, "Set BLE MAC address"); + + byte_input_set_result_callback( + byte_input, + bad_usb_scene_config_ble_mac_byte_input_callback, + NULL, + bad_usb, + bad_usb->ble_mac_buf, + sizeof(bad_usb->ble_mac_buf)); + + view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewByteInput); +} + +bool bad_usb_scene_config_ble_mac_on_event(void* context, SceneManagerEvent event) { + BadUsbApp* bad_usb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == ByteInputResultOk) { + const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface); + reverse_mac_addr(bad_usb->ble_mac_buf); + // Apply to current script config + memcpy( + bad_usb->script_hid_cfg.ble.mac, + bad_usb->ble_mac_buf, + sizeof(bad_usb->script_hid_cfg.ble.mac)); + hid->adjust_config(&bad_usb->script_hid_cfg); + // Set in user config to save in settings file + memcpy( + bad_usb->user_hid_cfg.ble.mac, + bad_usb->ble_mac_buf, + sizeof(bad_usb->user_hid_cfg.ble.mac)); + } + scene_manager_previous_scene(bad_usb->scene_manager); + } + return consumed; +} + +void bad_usb_scene_config_ble_mac_on_exit(void* context) { + BadUsbApp* bad_usb = context; + ByteInput* byte_input = bad_usb->byte_input; + + byte_input_set_result_callback(byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(byte_input, ""); +} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config_ble_name.c b/applications/main/bad_usb/scenes/bad_usb_scene_config_ble_name.c new file mode 100644 index 000000000..af7913e7d --- /dev/null +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config_ble_name.c @@ -0,0 +1,62 @@ +#include "../bad_usb_app_i.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void bad_usb_scene_config_ble_name_text_input_callback(void* context) { + BadUsbApp* bad_usb = context; + + view_dispatcher_send_custom_event(bad_usb->view_dispatcher, TextInputResultOk); +} + +void bad_usb_scene_config_ble_name_on_enter(void* context) { + BadUsbApp* bad_usb = context; + TextInput* text_input = bad_usb->text_input; + + strlcpy( + bad_usb->ble_name_buf, bad_usb->script_hid_cfg.ble.name, sizeof(bad_usb->ble_name_buf)); + text_input_set_header_text(text_input, "Set BLE device name"); + + text_input_set_result_callback( + text_input, + bad_usb_scene_config_ble_name_text_input_callback, + bad_usb, + bad_usb->ble_name_buf, + sizeof(bad_usb->ble_name_buf), + true); + + view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewTextInput); +} + +bool bad_usb_scene_config_ble_name_on_event(void* context, SceneManagerEvent event) { + BadUsbApp* bad_usb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == TextInputResultOk) { + const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface); + // Apply to current script config + strlcpy( + bad_usb->script_hid_cfg.ble.name, + bad_usb->ble_name_buf, + sizeof(bad_usb->script_hid_cfg.ble.name)); + hid->adjust_config(&bad_usb->script_hid_cfg); + // Set in user config to save in settings file + strlcpy( + bad_usb->user_hid_cfg.ble.name, + bad_usb->ble_name_buf, + sizeof(bad_usb->user_hid_cfg.ble.name)); + } + scene_manager_previous_scene(bad_usb->scene_manager); + } + return consumed; +} + +void bad_usb_scene_config_ble_name_on_exit(void* context) { + BadUsbApp* bad_usb = context; + TextInput* text_input = bad_usb->text_input; + + text_input_reset(text_input); +} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c b/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c index 3f01d7090..80ab44ab3 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c @@ -29,21 +29,17 @@ static bool bad_usb_layout_select(BadUsbApp* bad_usb) { void bad_usb_scene_config_layout_on_enter(void* context) { BadUsbApp* bad_usb = context; - if(bad_usb_layout_select(bad_usb)) { - scene_manager_search_and_switch_to_previous_scene(bad_usb->scene_manager, BadUsbSceneWork); - } else { - scene_manager_previous_scene(bad_usb->scene_manager); - } + bad_usb_layout_select(bad_usb); + + scene_manager_previous_scene(bad_usb->scene_manager); } bool bad_usb_scene_config_layout_on_event(void* context, SceneManagerEvent event) { UNUSED(context); UNUSED(event); - // BadUsbApp* bad_usb = context; return false; } void bad_usb_scene_config_layout_on_exit(void* context) { UNUSED(context); - // BadUsbApp* bad_usb = context; } diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config_usb_name.c b/applications/main/bad_usb/scenes/bad_usb_scene_config_usb_name.c new file mode 100644 index 000000000..d0e136634 --- /dev/null +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config_usb_name.c @@ -0,0 +1,86 @@ +#include "../bad_usb_app_i.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void bad_usb_scene_config_usb_name_text_input_callback(void* context) { + BadUsbApp* bad_usb = context; + + view_dispatcher_send_custom_event(bad_usb->view_dispatcher, TextInputResultOk); +} + +void bad_usb_scene_config_usb_name_on_enter(void* context) { + BadUsbApp* bad_usb = context; + TextInput* text_input = bad_usb->text_input; + + if(scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfigUsbName)) { + strlcpy( + bad_usb->usb_name_buf, + bad_usb->script_hid_cfg.usb.manuf, + sizeof(bad_usb->usb_name_buf)); + text_input_set_header_text(text_input, "Set USB manufacturer name"); + } else { + strlcpy( + bad_usb->usb_name_buf, + bad_usb->script_hid_cfg.usb.product, + sizeof(bad_usb->usb_name_buf)); + text_input_set_header_text(text_input, "Set USB product name"); + } + + text_input_set_result_callback( + text_input, + bad_usb_scene_config_usb_name_text_input_callback, + bad_usb, + bad_usb->usb_name_buf, + sizeof(bad_usb->usb_name_buf), + true); + + view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewTextInput); +} + +bool bad_usb_scene_config_usb_name_on_event(void* context, SceneManagerEvent event) { + BadUsbApp* bad_usb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == TextInputResultOk) { + const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface); + if(scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfigUsbName)) { + // Apply to current script config + strlcpy( + bad_usb->script_hid_cfg.usb.manuf, + bad_usb->usb_name_buf, + sizeof(bad_usb->script_hid_cfg.usb.manuf)); + hid->adjust_config(&bad_usb->script_hid_cfg); + // Set in user config to save in settings file + strlcpy( + bad_usb->user_hid_cfg.usb.manuf, + bad_usb->usb_name_buf, + sizeof(bad_usb->user_hid_cfg.usb.manuf)); + } else { + // Apply to current script config + strlcpy( + bad_usb->script_hid_cfg.usb.product, + bad_usb->usb_name_buf, + sizeof(bad_usb->script_hid_cfg.usb.product)); + hid->adjust_config(&bad_usb->script_hid_cfg); + // Set in user config to save in settings file + strlcpy( + bad_usb->user_hid_cfg.usb.product, + bad_usb->usb_name_buf, + sizeof(bad_usb->user_hid_cfg.usb.product)); + } + } + scene_manager_previous_scene(bad_usb->scene_manager); + } + return consumed; +} + +void bad_usb_scene_config_usb_name_on_exit(void* context) { + BadUsbApp* bad_usb = context; + TextInput* text_input = bad_usb->text_input; + + text_input_reset(text_input); +} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config_usb_vidpid.c b/applications/main/bad_usb/scenes/bad_usb_scene_config_usb_vidpid.c new file mode 100644 index 000000000..ce0c51a47 --- /dev/null +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config_usb_vidpid.c @@ -0,0 +1,59 @@ +#include "../bad_usb_app_i.h" + +enum ByteInputResult { + ByteInputResultOk, +}; + +void bad_usb_scene_config_usb_vidpid_byte_input_callback(void* context) { + BadUsbApp* bad_usb = context; + + view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ByteInputResultOk); +} + +void bad_usb_scene_config_usb_vidpid_on_enter(void* context) { + BadUsbApp* bad_usb = context; + ByteInput* byte_input = bad_usb->byte_input; + + bad_usb->usb_vidpid_buf[0] = __builtin_bswap16(bad_usb->script_hid_cfg.usb.vid); + bad_usb->usb_vidpid_buf[1] = __builtin_bswap16(bad_usb->script_hid_cfg.usb.pid); + byte_input_set_header_text(byte_input, "Set USB VID:PID"); + + byte_input_set_result_callback( + byte_input, + bad_usb_scene_config_usb_vidpid_byte_input_callback, + NULL, + bad_usb, + (void*)bad_usb->usb_vidpid_buf, + sizeof(bad_usb->usb_vidpid_buf)); + + view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewByteInput); +} + +bool bad_usb_scene_config_usb_vidpid_on_event(void* context, SceneManagerEvent event) { + BadUsbApp* bad_usb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == ByteInputResultOk) { + const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface); + // Apply to current script config + bad_usb->script_hid_cfg.usb.vid = __builtin_bswap16(bad_usb->usb_vidpid_buf[0]); + bad_usb->script_hid_cfg.usb.pid = __builtin_bswap16(bad_usb->usb_vidpid_buf[1]); + hid->adjust_config(&bad_usb->script_hid_cfg); + // Set in user config to save in settings file + bad_usb->user_hid_cfg.usb.vid = bad_usb->script_hid_cfg.usb.vid; + bad_usb->user_hid_cfg.usb.pid = bad_usb->script_hid_cfg.usb.pid; + } + scene_manager_previous_scene(bad_usb->scene_manager); + } + return consumed; +} + +void bad_usb_scene_config_usb_vidpid_on_exit(void* context) { + BadUsbApp* bad_usb = context; + ByteInput* byte_input = bad_usb->byte_input; + + byte_input_set_result_callback(byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(byte_input, ""); +} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c b/applications/main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c index b8fd993e2..cd600386c 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c @@ -36,7 +36,8 @@ bool bad_usb_scene_confirm_unpair_on_event(void* context, SceneManagerEvent even if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(scene_manager, BadUsbSceneUnpairDone); + bad_usb_hid_ble_remove_pairing(); + scene_manager_next_scene(scene_manager, BadUsbSceneDone); } else if(event.event == GuiButtonTypeLeft) { scene_manager_previous_scene(scene_manager); } diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_unpair_done.c b/applications/main/bad_usb/scenes/bad_usb_scene_done.c similarity index 67% rename from applications/main/bad_usb/scenes/bad_usb_scene_unpair_done.c rename to applications/main/bad_usb/scenes/bad_usb_scene_done.c index 9583f9bfb..9a878d889 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_unpair_done.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_done.c @@ -1,19 +1,17 @@ #include "../bad_usb_app_i.h" -static void bad_usb_scene_unpair_done_popup_callback(void* context) { +static void bad_usb_scene_done_popup_callback(void* context) { BadUsbApp* bad_usb = context; scene_manager_search_and_switch_to_previous_scene(bad_usb->scene_manager, BadUsbSceneConfig); } -void bad_usb_scene_unpair_done_on_enter(void* context) { +void bad_usb_scene_done_on_enter(void* context) { BadUsbApp* bad_usb = context; Popup* popup = bad_usb->popup; - bad_usb_hid_ble_remove_pairing(); - popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58); popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom); - popup_set_callback(popup, bad_usb_scene_unpair_done_popup_callback); + popup_set_callback(popup, bad_usb_scene_done_popup_callback); popup_set_context(popup, bad_usb); popup_set_timeout(popup, 1500); popup_enable_timeout(popup); @@ -21,7 +19,7 @@ void bad_usb_scene_unpair_done_on_enter(void* context) { view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewPopup); } -bool bad_usb_scene_unpair_done_on_event(void* context, SceneManagerEvent event) { +bool bad_usb_scene_done_on_event(void* context, SceneManagerEvent event) { BadUsbApp* bad_usb = context; UNUSED(bad_usb); UNUSED(event); @@ -30,7 +28,7 @@ bool bad_usb_scene_unpair_done_on_event(void* context, SceneManagerEvent event) return consumed; } -void bad_usb_scene_unpair_done_on_exit(void* context) { +void bad_usb_scene_done_on_exit(void* context) { BadUsbApp* bad_usb = context; Popup* popup = bad_usb->popup; UNUSED(popup); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c index 5e2c3f14b..9aa6f3eea 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c @@ -1,5 +1,4 @@ #include "../bad_usb_app_i.h" -#include #include static bool bad_usb_file_select(BadUsbApp* bad_usb) { @@ -27,6 +26,7 @@ void bad_usb_scene_file_select_on_enter(void* context) { } if(bad_usb_file_select(bad_usb)) { + scene_manager_set_scene_state(bad_usb->scene_manager, BadUsbSceneWork, true); scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork); } else { view_dispatcher_stop(bad_usb->view_dispatcher); @@ -36,11 +36,9 @@ void bad_usb_scene_file_select_on_enter(void* context) { bool bad_usb_scene_file_select_on_event(void* context, SceneManagerEvent event) { UNUSED(context); UNUSED(event); - // BadUsbApp* bad_usb = context; return false; } void bad_usb_scene_file_select_on_exit(void* context) { UNUSED(context); - // BadUsbApp* bad_usb = context; } diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 0382b0f8e..d57f7eb33 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -20,11 +20,8 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { bad_usb_script_close(app->bad_usb_script); app->bad_usb_script = NULL; - if(app->interface == BadUsbHidInterfaceBle) { - scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); - } else { - scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout); - } + scene_manager_set_scene_state(app->scene_manager, BadUsbSceneConfig, 0); + scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); } consumed = true; } else if(event.event == InputKeyOk) { @@ -37,7 +34,9 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { app->interface == BadUsbHidInterfaceBle ? BadUsbHidInterfaceUsb : BadUsbHidInterfaceBle); bad_usb_script_close(app->bad_usb_script); - app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface); + app->bad_usb_script = bad_usb_script_open( + app->file_path, &app->interface, &app->script_hid_cfg, false); + bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); } else { bad_usb_script_pause_resume(app->bad_usb_script); } @@ -45,6 +44,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { } } else if(event.type == SceneManagerEventTypeTick) { bad_usb_view_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script)); + bad_usb_view_set_interface(app->bad_usb_view, app->interface); } return consumed; } @@ -54,7 +54,18 @@ void bad_usb_scene_work_on_enter(void* context) { bad_usb_view_set_interface(app->bad_usb_view, app->interface); - app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface); + // Opening script the first time: + // - copy user settings as base config + // - load ID/BLE_ID/BT_ID config if present + // Then disable this until next script selected so user can customize options + bool first_script_load = scene_manager_get_scene_state(app->scene_manager, BadUsbSceneWork); + if(first_script_load) { + memcpy(&app->script_hid_cfg, &app->user_hid_cfg, sizeof(app->script_hid_cfg)); + scene_manager_set_scene_state(app->scene_manager, BadUsbSceneWork, false); + } + // Interface and config are passed as pointers as ID/BLE_ID/BT_ID config can modify them + app->bad_usb_script = bad_usb_script_open( + app->file_path, &app->interface, &app->script_hid_cfg, first_script_load); bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); FuriString* file_name; diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 1a6f77958..4032ea974 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -24,8 +24,7 @@ typedef struct { static void bad_usb_draw_callback(Canvas* canvas, void* _model) { BadUsbModel* model = _model; - FuriString* disp_str; - disp_str = furi_string_alloc_set(model->file_name); + FuriString* disp_str = furi_string_alloc_set(model->file_name); elements_string_fit_width(canvas, disp_str, 128 - 2); canvas_set_font(canvas, FontSecondary); canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); @@ -35,6 +34,8 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { } else { furi_string_printf(disp_str, "(%s)", model->layout); } + uint32_t e = model->state.elapsed; + furi_string_cat_printf(disp_str, " %02lu:%02lu.%ld", e / 60 / 1000, e / 1000, e % 1000); elements_string_fit_width(canvas, disp_str, 128 - 2); canvas_draw_str( canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str)); @@ -52,13 +53,8 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { if((state == BadUsbStateIdle) || (state == BadUsbStateDone) || (state == BadUsbStateNotConnected)) { elements_button_center(canvas, "Run"); - if(model->interface == BadUsbHidInterfaceBle) { - elements_button_right(canvas, "USB"); - elements_button_left(canvas, "Config"); - } else { - elements_button_right(canvas, "BLE"); - elements_button_left(canvas, "Layout"); - } + elements_button_left(canvas, "Config"); + elements_button_right(canvas, model->interface == BadUsbHidInterfaceBle ? "USB" : "BLE"); } else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); if(!model->pause_wait) { @@ -90,77 +86,85 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); } else if(state == BadUsbStateScriptError) { canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); - canvas_set_font(canvas, FontSecondary); furi_string_printf(disp_str, "line %zu", model->state.error_line); canvas_draw_str_aligned( canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - furi_string_set_str(disp_str, model->state.error); elements_string_fit_width(canvas, disp_str, canvas_width(canvas)); canvas_draw_str_aligned( canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); } else if(state == BadUsbStateIdle) { canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); + furi_string_printf(disp_str, "0/%zu", model->state.line_nb); + canvas_draw_str_aligned( + canvas, 124, 47, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); canvas_set_font(canvas, FontBigNumbers); - canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas_draw_str_aligned(canvas, 112, 37, AlignRight, AlignBottom, "0"); + canvas_draw_icon(canvas, 115, 23, &I_Percent_10x14); } else if(state == BadUsbStateRunning) { if(model->anim_frame == 0) { canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); } else { canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21); } + furi_string_printf(disp_str, "%zu/%zu", model->state.line_cur, model->state.line_nb); + canvas_draw_str_aligned( + canvas, 124, 47, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); canvas_set_font(canvas, FontBigNumbers); furi_string_printf( disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); canvas_draw_str_aligned( - canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas, 112, 37, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + canvas_draw_icon(canvas, 115, 23, &I_Percent_10x14); } else if(state == BadUsbStateDone) { canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); + furi_string_printf(disp_str, "%zu/%zu", model->state.line_nb, model->state.line_nb); + canvas_draw_str_aligned( + canvas, 124, 47, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); canvas_set_font(canvas, FontBigNumbers); - canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas_draw_str_aligned(canvas, 112, 37, AlignRight, AlignBottom, "100"); + canvas_draw_icon(canvas, 115, 23, &I_Percent_10x14); } else if(state == BadUsbStateDelay) { if(model->anim_frame == 0) { canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); } else { canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); } + uint32_t delay = model->state.delay_remain / 10; + if(delay) { + furi_string_printf(disp_str, "Delay %lus", delay); + canvas_draw_str_aligned( + canvas, 4, 61, AlignLeft, AlignBottom, furi_string_get_cstr(disp_str)); + } + furi_string_printf(disp_str, "%zu/%zu", model->state.line_cur, model->state.line_nb); + canvas_draw_str_aligned( + canvas, 124, 47, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); canvas_set_font(canvas, FontBigNumbers); furi_string_printf( disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); canvas_draw_str_aligned( - canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - canvas_set_font(canvas, FontSecondary); - furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); - canvas_draw_str_aligned( - canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); + canvas, 112, 37, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + canvas_draw_icon(canvas, 115, 23, &I_Percent_10x14); } else if((state == BadUsbStatePaused) || (state == BadUsbStateWaitForBtn)) { if(model->anim_frame == 0) { canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); } else { canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); } + if(state != BadUsbStateWaitForBtn) { + canvas_draw_str_aligned(canvas, 4, 61, AlignLeft, AlignBottom, "Paused"); + } + furi_string_printf(disp_str, "%zu/%zu", model->state.line_cur, model->state.line_nb); + canvas_draw_str_aligned( + canvas, 124, 47, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); canvas_set_font(canvas, FontBigNumbers); furi_string_printf( disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); canvas_draw_str_aligned( - canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused"); - furi_string_reset(disp_str); + canvas, 112, 37, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + canvas_draw_icon(canvas, 115, 23, &I_Percent_10x14); } else { canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); } diff --git a/applications/main/gpio/gpio_app.c b/applications/main/gpio/gpio_app.c index 234cc793a..a21813955 100644 --- a/applications/main/gpio/gpio_app.c +++ b/applications/main/gpio/gpio_app.c @@ -30,6 +30,8 @@ GpioApp* gpio_app_alloc(void) { app->gui = furi_record_open(RECORD_GUI); app->gpio_items = gpio_items_alloc(); + app->power = furi_record_open(RECORD_POWER); + app->view_dispatcher = view_dispatcher_alloc(); app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app); view_dispatcher_set_event_callback_context(app->view_dispatcher, app); @@ -100,6 +102,7 @@ void gpio_app_free(GpioApp* app) { // Close records furi_record_close(RECORD_GUI); furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_POWER); expansion_enable(app->expansion); furi_record_close(RECORD_EXPANSION); diff --git a/applications/main/gpio/gpio_app_i.h b/applications/main/gpio/gpio_app_i.h index ce4cb6f55..4fbe25ad8 100644 --- a/applications/main/gpio/gpio_app_i.h +++ b/applications/main/gpio/gpio_app_i.h @@ -5,6 +5,7 @@ #include "scenes/gpio_scene.h" #include "gpio_custom_event.h" #include "usb_uart_bridge.h" +#include #include #include @@ -27,6 +28,7 @@ struct GpioApp { SceneManager* scene_manager; Widget* widget; DialogEx* dialog; + Power* power; VariableItemList* var_item_list; VariableItem* var_item_flow; diff --git a/applications/main/gpio/scenes/gpio_scene_start.c b/applications/main/gpio/scenes/gpio_scene_start.c index fdd48e560..0f37d77d8 100644 --- a/applications/main/gpio/scenes/gpio_scene_start.c +++ b/applications/main/gpio/scenes/gpio_scene_start.c @@ -60,7 +60,7 @@ void gpio_scene_start_on_enter(void* context) { GpioOtgSettingsNum, gpio_scene_start_var_list_change_callback, app); - if(furi_hal_power_is_otg_enabled()) { + if(power_is_otg_enabled(app->power)) { variable_item_set_current_value_index(item, GpioOtgOn); variable_item_set_current_value_text(item, gpio_otg_text[GpioOtgOn]); } else { @@ -80,9 +80,9 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == GpioStartEventOtgOn) { - if(!furi_hal_power_is_otg_enabled()) furi_hal_power_enable_otg(); + power_enable_otg(app->power, true); } else if(event.event == GpioStartEventOtgOff) { - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); + power_enable_otg(app->power, false); } else if(event.event == GpioStartEventManualControl) { scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemTest); scene_manager_next_scene(app->scene_manager, GpioSceneTest); diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index 0d2c63b3c..3e1cefb93 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -1,7 +1,6 @@ #include "usb_uart_bridge.h" #include "usb_cdc.h" #include -#include #include #include #include @@ -35,12 +34,12 @@ typedef enum { WorkerEvtLineCfgSet = (1 << 6), WorkerEvtCtrlLineSet = (1 << 7), - WorkerEvtSendBreak = (1 << 8), + } WorkerEvtFlags; #define WORKER_ALL_RX_EVENTS \ (WorkerEvtStop | WorkerEvtRxDone | WorkerEvtCfgChange | WorkerEvtLineCfgSet | \ - WorkerEvtCtrlLineSet | WorkerEvtCdcTxComplete | WorkerEvtSendBreak) + WorkerEvtCtrlLineSet | WorkerEvtCdcTxComplete) #define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtCdcRx) struct UsbUartBridge { @@ -61,6 +60,8 @@ struct UsbUartBridge { FuriApiLock cfg_lock; + CliVcp* cli_vcp; + uint8_t rx_buf[USB_CDC_PKT_LEN]; }; @@ -69,7 +70,6 @@ static void vcp_on_cdc_rx(void* context); static void vcp_state_callback(void* context, uint8_t state); static void vcp_on_cdc_control_line(void* context, uint8_t state); static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config); -static void vcp_on_cdc_break(void* context, uint16_t duration); static const CdcCallbacks cdc_cb = { vcp_on_cdc_tx_complete, @@ -77,7 +77,6 @@ static const CdcCallbacks cdc_cb = { vcp_state_callback, vcp_on_cdc_control_line, vcp_on_line_config, - vcp_on_cdc_break, }; /* USB UART worker */ @@ -108,15 +107,11 @@ static void usb_uart_on_irq_rx_dma_cb( static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) { furi_hal_usb_unlock(); if(vcp_ch == 0) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_record_close(RECORD_CLI); + cli_vcp_disable(usb_uart->cli_vcp); furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); } else { furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true); - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); + cli_vcp_enable(usb_uart->cli_vcp); } furi_hal_cdc_set_callbacks(vcp_ch, (CdcCallbacks*)&cdc_cb, usb_uart); } @@ -125,9 +120,7 @@ static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) { UNUSED(usb_uart); furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL); if(vcp_ch != 0) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_record_close(RECORD_CLI); + cli_vcp_disable(usb_uart->cli_vcp); } } @@ -179,13 +172,15 @@ static int32_t usb_uart_worker(void* context) { memcpy(&usb_uart->cfg, &usb_uart->cfg_new, sizeof(UsbUartConfig)); + usb_uart->cli_vcp = furi_record_open(RECORD_CLI_VCP); + usb_uart->rx_stream = furi_stream_buffer_alloc(USB_UART_RX_BUF_SIZE, 1); usb_uart->tx_sem = furi_semaphore_alloc(1, 1); usb_uart->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal); usb_uart->tx_thread = - furi_thread_alloc_ex("UsbUartTxWorker", 512, usb_uart_tx_thread, usb_uart); + furi_thread_alloc_ex("UsbUartTxWorker", 768, usb_uart_tx_thread, usb_uart); usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch); usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch); @@ -289,12 +284,7 @@ static int32_t usb_uart_worker(void* context) { if(events & WorkerEvtCtrlLineSet) { usb_uart_update_ctrl_lines(usb_uart); } - if(events & WorkerEvtSendBreak) { - furi_hal_serial_send_break(usb_uart->serial_handle); - } } - usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch); - usb_uart_serial_deinit(usb_uart); furi_hal_gpio_init(USB_USART_DE_RE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); @@ -307,15 +297,18 @@ static int32_t usb_uart_worker(void* context) { furi_thread_join(usb_uart->tx_thread); furi_thread_free(usb_uart->tx_thread); + usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch); + usb_uart_serial_deinit(usb_uart); + furi_stream_buffer_free(usb_uart->rx_stream); furi_mutex_free(usb_uart->usb_mutex); furi_semaphore_free(usb_uart->tx_sem); furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); + cli_vcp_enable(usb_uart->cli_vcp); + + furi_record_close(RECORD_CLI_VCP); return 0; } @@ -382,12 +375,6 @@ static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtLineCfgSet); } -static void vcp_on_cdc_break(void* context, uint16_t duration) { - UNUSED(duration); - UsbUartBridge* usb_uart = (UsbUartBridge*)context; - furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtSendBreak); -} - UsbUartBridge* usb_uart_enable(UsbUartConfig* cfg) { UsbUartBridge* usb_uart = malloc(sizeof(UsbUartBridge)); diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index 06455aeb9..84afe0f02 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -13,19 +13,10 @@ App( ) App( - appid="ibutton_cli", + appid="cli_ikey", targets=["f7"], apptype=FlipperAppType.PLUGIN, - entry_point="ibutton_cli_plugin_ep", + entry_point="cli_ikey_ep", requires=["cli"], sources=["ibutton_cli.c"], ) - -App( - appid="ibutton_start", - apptype=FlipperAppType.STARTUP, - targets=["f7"], - entry_point="ibutton_on_system_start", - sources=["ibutton_start.c"], - order=60, -) diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c index dcac8f963..0b9a59586 100644 --- a/applications/main/ibutton/ibutton_cli.c +++ b/applications/main/ibutton/ibutton_cli.c @@ -1,8 +1,9 @@ #include #include -#include +#include #include +#include #include #include @@ -79,7 +80,7 @@ static void ibutton_cli_worker_read_cb(void* context) { furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE); } -static void ibutton_cli_read(Cli* cli) { +static void ibutton_cli_read(PipeSide* pipe) { iButtonProtocols* protocols = ibutton_protocols_alloc(); iButtonWorker* worker = ibutton_worker_alloc(protocols); iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols)); @@ -100,7 +101,7 @@ static void ibutton_cli_read(Cli* cli) { break; } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } ibutton_worker_stop(worker); @@ -125,7 +126,7 @@ static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult furi_event_flag_set(write_context->event, EVENT_FLAG_IBUTTON_COMPLETE); } -void ibutton_cli_write(Cli* cli, FuriString* args) { +void ibutton_cli_write(PipeSide* pipe, FuriString* args) { iButtonProtocols* protocols = ibutton_protocols_alloc(); iButtonWorker* worker = ibutton_worker_alloc(protocols); iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols)); @@ -168,7 +169,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } } while(false); @@ -182,7 +183,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) { furi_event_flag_free(write_context.event); } -void ibutton_cli_emulate(Cli* cli, FuriString* args) { +void ibutton_cli_emulate(PipeSide* pipe, FuriString* args) { iButtonProtocols* protocols = ibutton_protocols_alloc(); iButtonWorker* worker = ibutton_worker_alloc(protocols); iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols)); @@ -201,9 +202,9 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) { ibutton_worker_emulate_start(worker, key); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(100); - } + }; } while(false); @@ -215,8 +216,7 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) { ibutton_protocols_free(protocols); } -void ibutton_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -228,11 +228,11 @@ void ibutton_cli(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "read") == 0) { - ibutton_cli_read(cli); + ibutton_cli_read(pipe); } else if(furi_string_cmp_str(cmd, "write") == 0) { - ibutton_cli_write(cli, args); + ibutton_cli_write(pipe, args); } else if(furi_string_cmp_str(cmd, "emulate") == 0) { - ibutton_cli_emulate(cli, args); + ibutton_cli_emulate(pipe, args); } else { ibutton_cli_print_usage(); } @@ -240,15 +240,4 @@ void ibutton_cli(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -#include -#include - -static const FlipperAppPluginDescriptor plugin_descriptor = { - .appid = CLI_PLUGIN_APP_ID, - .ep_api_version = CLI_PLUGIN_API_VERSION, - .entry_point = &ibutton_cli, -}; - -const FlipperAppPluginDescriptor* ibutton_cli_plugin_ep(void) { - return &plugin_descriptor; -} +CLI_COMMAND_INTERFACE(ikey, execute, CliCommandFlagDefault, 1024, CLI_APPID); diff --git a/applications/main/ibutton/ibutton_start.c b/applications/main/ibutton/ibutton_start.c deleted file mode 100644 index d252bed7f..000000000 --- a/applications/main/ibutton/ibutton_start.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -static void ibutton_cli_wrapper(Cli* cli, FuriString* args, void* context) { - cli_plugin_wrapper("ibutton", cli, args, context); -} - -void ibutton_on_system_start(void) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli_wrapper, cli); - furi_record_close(RECORD_CLI); -} diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index 586adf110..79b3fdbfa 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -15,10 +15,10 @@ App( ) App( - appid="infrared_cli", + appid="cli_ir", targets=["f7"], apptype=FlipperAppType.PLUGIN, - entry_point="infrared_cli_plugin_ep", + entry_point="cli_ir_ep", requires=["cli"], sources=[ "infrared_cli.c", @@ -26,12 +26,3 @@ App( "infrared_signal.c", ], ) - -App( - appid="infrared_start", - apptype=FlipperAppType.STARTUP, - targets=["f7"], - entry_point="infrared_on_system_start", - sources=["infrared_start.c"], - order=20, -) diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 446a4800d..587e6f0e7 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -1,6 +1,6 @@ #include "infrared_app_i.h" -#include +#include #include #include @@ -492,12 +492,12 @@ void infrared_set_tx_pin(InfraredApp* infrared, FuriHalInfraredTxPin tx_pin) { } void infrared_enable_otg(InfraredApp* infrared, bool enable) { - if(enable) { - if(!furi_hal_power_is_otg_enabled()) furi_hal_power_enable_otg(); - } else { - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); - } + Power* power = furi_record_open(RECORD_POWER); + + power_enable_otg(power, enable); infrared->app_state.is_otg_enabled = enable; + + furi_record_close(RECORD_POWER); } static void infrared_load_settings(InfraredApp* infrared) { diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 8c7422d5e..1ec4645e9 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -2,26 +2,61 @@ #include #include +#include #include #include "infrared_signal.h" +ARRAY_DEF(SignalPositionArray, size_t, M_DEFAULT_OPLIST); + typedef struct { - uint32_t index; - uint32_t count; + size_t index; + SignalPositionArray_t signals; } InfraredBruteForceRecord; +static inline void ir_bf_record_init(InfraredBruteForceRecord* record) { + record->index = 0; + SignalPositionArray_init(record->signals); +} +#define IR_BF_RECORD_INIT(r) (ir_bf_record_init(&(r))) + +static inline void + ir_bf_record_init_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) { + dest->index = src->index; + SignalPositionArray_init_set(dest->signals, src->signals); +} +#define IR_BF_RECORD_INIT_SET(d, s) (ir_bf_record_init_set(&(d), &(s))) + +static inline void + ir_bf_record_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) { + dest->index = src->index; + SignalPositionArray_set(dest->signals, src->signals); +} +#define IR_BF_RECORD_SET(d, s) (ir_bf_record_set(&(d), &(s))) + +static inline void ir_bf_record_clear(InfraredBruteForceRecord* record) { + SignalPositionArray_clear(record->signals); +} +#define IR_BF_RECORD_CLEAR(r) (ir_bf_record_clear(&(r))) + +#define IR_BF_RECORD_OPLIST \ + (INIT(IR_BF_RECORD_INIT), \ + INIT_SET(IR_BF_RECORD_INIT_SET), \ + SET(IR_BF_RECORD_SET), \ + CLEAR(IR_BF_RECORD_CLEAR)) + DICT_DEF2( InfraredBruteForceRecordDict, FuriString*, FURI_STRING_OPLIST, InfraredBruteForceRecord, - M_POD_OPLIST); + IR_BF_RECORD_OPLIST); struct InfraredBruteForce { FlipperFormat* ff; const char* db_filename; FuriString* current_record_name; + InfraredBruteForceRecord current_record; InfraredSignal* current_signal; InfraredBruteForceRecordDict_t records; bool is_started; @@ -39,6 +74,7 @@ InfraredBruteForce* infrared_brute_force_alloc(void) { } void infrared_brute_force_free(InfraredBruteForce* brute_force) { + furi_check(brute_force); furi_assert(!brute_force->is_started); InfraredBruteForceRecordDict_clear(brute_force->records); furi_string_free(brute_force->current_record_name); @@ -46,11 +82,13 @@ void infrared_brute_force_free(InfraredBruteForce* brute_force) { } void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) { + furi_check(brute_force); furi_assert(!brute_force->is_started); brute_force->db_filename = db_filename; } InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { + furi_check(brute_force); furi_assert(!brute_force->is_started); furi_assert(brute_force->db_filename); InfraredErrorCode error = InfraredErrorCodeNone; @@ -66,19 +104,19 @@ InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* br break; } - bool signals_valid = false; + bool signal_valid = false; while(infrared_signal_read_name(ff, signal_name) == InfraredErrorCodeNone) { + size_t signal_start = flipper_format_tell(ff); error = infrared_signal_read_body(signal, ff); - signals_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal); - if(!signals_valid) break; + signal_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal); + if(!signal_valid) break; InfraredBruteForceRecord* record = InfraredBruteForceRecordDict_get(brute_force->records, signal_name); - if(record) { //-V547 - ++(record->count); - } + furi_assert(record); + SignalPositionArray_push_back(record->signals, signal_start); } - if(!signals_valid) break; + if(!signal_valid) break; } while(false); infrared_signal_free(signal); @@ -93,6 +131,7 @@ bool infrared_brute_force_start( InfraredBruteForce* brute_force, uint32_t index, uint32_t* record_count) { + furi_check(brute_force); furi_assert(!brute_force->is_started); bool success = false; *record_count = 0; @@ -103,9 +142,10 @@ bool infrared_brute_force_start( InfraredBruteForceRecordDict_next(it)) { const InfraredBruteForceRecordDict_itref_t* record = InfraredBruteForceRecordDict_cref(it); if(record->value.index == index) { - *record_count = record->value.count; + *record_count = SignalPositionArray_size(record->value.signals); if(*record_count) { furi_string_set(brute_force->current_record_name, record->key); + brute_force->current_record = record->value; } break; } @@ -124,10 +164,12 @@ bool infrared_brute_force_start( } bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force) { + furi_check(brute_force); return brute_force->is_started; } void infrared_brute_force_stop(InfraredBruteForce* brute_force) { + furi_check(brute_force); furi_assert(brute_force->is_started); furi_string_reset(brute_force->current_record_name); infrared_signal_free(brute_force->current_signal); @@ -138,25 +180,32 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) { furi_record_close(RECORD_STORAGE); } -bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) { +bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index) { + furi_check(brute_force); furi_assert(brute_force->is_started); - const bool success = infrared_signal_search_by_name_and_read( - brute_force->current_signal, - brute_force->ff, - furi_string_get_cstr(brute_force->current_record_name)) == - InfraredErrorCodeNone; - if(success) { - infrared_signal_transmit(brute_force->current_signal); - } - return success; + if(signal_index >= SignalPositionArray_size(brute_force->current_record.signals)) return false; + + size_t signal_start = + *SignalPositionArray_cget(brute_force->current_record.signals, signal_index); + if(!flipper_format_seek(brute_force->ff, signal_start, FlipperFormatOffsetFromStart)) + return false; + + if(INFRARED_ERROR_PRESENT( + infrared_signal_read_body(brute_force->current_signal, brute_force->ff))) + return false; + + infrared_signal_transmit(brute_force->current_signal); + return true; } void infrared_brute_force_add_record( InfraredBruteForce* brute_force, uint32_t index, const char* name) { - InfraredBruteForceRecord value = {.index = index, .count = 0}; + InfraredBruteForceRecord value; + ir_bf_record_init(&value); + value.index = index; FuriString* key; key = furi_string_alloc_set(name); InfraredBruteForceRecordDict_set_at(brute_force->records, key, value); diff --git a/applications/main/infrared/infrared_brute_force.h b/applications/main/infrared/infrared_brute_force.h index 879642257..2c75d37f2 100644 --- a/applications/main/infrared/infrared_brute_force.h +++ b/applications/main/infrared/infrared_brute_force.h @@ -78,18 +78,16 @@ bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force); void infrared_brute_force_stop(InfraredBruteForce* brute_force); /** - * @brief Send the next signal from the chosen category. - * - * This function is called repeatedly until no more signals are left - * in the chosen signal category. - * - * @warning Transmission must be started first by calling infrared_brute_force_start() - * before calling this function. - * - * @param[in,out] brute_force pointer to the instance to be used. - * @returns true if the next signal existed and could be transmitted, false otherwise. + * @brief Send an arbitrary signal from the chosen category. + * + * @param[in] brute_force pointer to the instance + * @param signal_index the index of the signal within the category, must be + * between 0 and `record_count` as told by + * `infrared_brute_force_start` + * + * @returns true on success, false otherwise */ -bool infrared_brute_force_send_next(InfraredBruteForce* brute_force); +bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index); /** * @brief Add a signal category to an InfraredBruteForce instance's dictionary. diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index cdd5b9a11..eb13bcd79 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -1,11 +1,11 @@ -#include -#include +#include #include #include #include #include #include #include +#include #include #include "infrared_signal.h" @@ -19,14 +19,14 @@ DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST) -static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args); -static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args); -static void infrared_cli_process_decode(Cli* cli, FuriString* args); -static void infrared_cli_process_universal(Cli* cli, FuriString* args); +static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args); +static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args); +static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args); +static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args); static const struct { const char* cmd; - void (*process_function)(Cli* cli, FuriString* args); + void (*process_function)(PipeSide* pipe, FuriString* args); } infrared_cli_commands[] = { {.cmd = "rx", .process_function = infrared_cli_start_ir_rx}, {.cmd = "tx", .process_function = infrared_cli_start_ir_tx}, @@ -38,7 +38,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv furi_assert(received_signal); char buf[100]; size_t buf_cnt; - Cli* cli = (Cli*)context; + PipeSide* pipe = (PipeSide*)context; if(infrared_worker_signal_is_decoded(received_signal)) { const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal); @@ -52,20 +52,20 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4), message->command, message->repeat ? " R" : ""); - cli_write(cli, (uint8_t*)buf, buf_cnt); + pipe_send(pipe, buf, buf_cnt); } else { const uint32_t* timings; size_t timings_cnt; infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt); buf_cnt = snprintf(buf, sizeof(buf), "RAW, %zu samples:\r\n", timings_cnt); - cli_write(cli, (uint8_t*)buf, buf_cnt); + pipe_send(pipe, buf, buf_cnt); for(size_t i = 0; i < timings_cnt; ++i) { buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[i]); - cli_write(cli, (uint8_t*)buf, buf_cnt); + pipe_send(pipe, buf, buf_cnt); } buf_cnt = snprintf(buf, sizeof(buf), "\r\n"); - cli_write(cli, (uint8_t*)buf, buf_cnt); + pipe_send(pipe, buf, buf_cnt); } } @@ -124,9 +124,7 @@ static void infrared_cli_print_usage(void) { infrared_cli_print_universal_remotes(); } -static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { - UNUSED(cli); - +static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args) { bool enable_decoding = true; if(!furi_string_empty(args)) { @@ -142,10 +140,10 @@ static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { InfraredWorker* worker = infrared_worker_alloc(); infrared_worker_rx_enable_signal_decoding(worker, enable_decoding); infrared_worker_rx_start(worker); - infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli); + infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, pipe); printf("Receiving %s INFRARED...\r\nPress Ctrl+C to abort\r\n", enable_decoding ? "" : "RAW"); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(50); } @@ -214,8 +212,8 @@ static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) { return infrared_signal_is_valid(signal); } -static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args) { - UNUSED(cli); +static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); const char* str = furi_string_get_cstr(args); InfraredSignal* signal = infrared_signal_alloc(); @@ -335,8 +333,8 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o return ret; } -static void infrared_cli_process_decode(Cli* cli, FuriString* args) { - UNUSED(cli); +static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* input_file = flipper_format_buffered_file_alloc(storage); FlipperFormat* output_file = NULL; @@ -455,8 +453,10 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) { furi_record_close(RECORD_STORAGE); } -static void - infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) { +static void infrared_cli_brute_force_signals( + PipeSide* pipe, + FuriString* remote_name, + FuriString* signal_name) { InfraredBruteForce* brute_force = infrared_brute_force_alloc(); FuriString* remote_path = furi_string_alloc_printf( "%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name)); @@ -475,25 +475,24 @@ static void break; } - uint32_t record_count; + uint32_t signal_count, current_signal = 0; bool running = infrared_brute_force_start( - brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &record_count); + brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &signal_count); - if(record_count <= 0) { + if(signal_count <= 0) { printf("Invalid signal name.\r\n"); break; } - printf("Sending %lu signal(s)...\r\n", record_count); + printf("Sending %lu signal(s)...\r\n", signal_count); printf("Press Ctrl-C to stop.\r\n"); - int records_sent = 0; while(running) { - running = infrared_brute_force_send_next(brute_force); + running = infrared_brute_force_send(brute_force, current_signal); - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; - printf("\r%d%% complete.", (int)((float)records_sent++ / (float)record_count * 100)); + printf("\r%d%% complete.", (int)((float)current_signal++ / (float)signal_count * 100)); fflush(stdout); } @@ -505,7 +504,7 @@ static void infrared_brute_force_free(brute_force); } -static void infrared_cli_process_universal(Cli* cli, FuriString* args) { +static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args) { FuriString* arg1 = furi_string_alloc(); FuriString* arg2 = furi_string_alloc(); @@ -520,14 +519,14 @@ static void infrared_cli_process_universal(Cli* cli, FuriString* args) { } else if(furi_string_equal_str(arg1, "list")) { infrared_cli_list_remote_signals(arg2); } else { - infrared_cli_brute_force_signals(cli, arg1, arg2); + infrared_cli_brute_force_signals(pipe, arg1, arg2); } furi_string_free(arg1); furi_string_free(arg2); } -static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); if(furi_hal_infrared_is_busy()) { printf("INFRARED is busy. Exiting."); @@ -547,7 +546,7 @@ static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) { } if(i < COUNT_OF(infrared_cli_commands)) { - infrared_cli_commands[i].process_function(cli, args); + infrared_cli_commands[i].process_function(pipe, args); } else { infrared_cli_print_usage(); } @@ -555,15 +554,4 @@ static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) { furi_string_free(command); } -#include -#include - -static const FlipperAppPluginDescriptor plugin_descriptor = { - .appid = CLI_PLUGIN_APP_ID, - .ep_api_version = CLI_PLUGIN_API_VERSION, - .entry_point = &infrared_cli_start_ir, -}; - -const FlipperAppPluginDescriptor* infrared_cli_plugin_ep(void) { - return &plugin_descriptor; -} +CLI_COMMAND_INTERFACE(ir, execute, CliCommandFlagDefault, 2048, CLI_APPID); diff --git a/applications/main/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h index 2efc99f4b..7109a48b7 100644 --- a/applications/main/infrared/infrared_custom_event.h +++ b/applications/main/infrared/infrared_custom_event.h @@ -3,7 +3,7 @@ #include #include -enum InfraredCustomEventType { +typedef enum { // Reserve first 100 events for button types and indexes, starting from 0 InfraredCustomEventTypeReserved = 100, InfraredCustomEventTypeMenuSelected, @@ -13,7 +13,7 @@ enum InfraredCustomEventType { InfraredCustomEventTypeTextEditDone, InfraredCustomEventTypePopupClosed, InfraredCustomEventTypeButtonSelected, - InfraredCustomEventTypeBackPressed, + InfraredCustomEventTypePopupInput, InfraredCustomEventTypeTaskFinished, InfraredCustomEventTypeRpcLoadFile, @@ -27,7 +27,7 @@ enum InfraredCustomEventType { InfraredCustomEventTypeGpioTxPinChanged, InfraredCustomEventTypeGpioOtgChanged, -}; +} InfraredCustomEventType; #pragma pack(push, 1) typedef union { diff --git a/applications/main/infrared/infrared_start.c b/applications/main/infrared/infrared_start.c deleted file mode 100644 index 6de11b677..000000000 --- a/applications/main/infrared/infrared_start.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -static void infrared_cli_start_ir_wrapper(Cli* cli, FuriString* args, void* context) { - cli_plugin_wrapper("infrared", cli, args, context); -} - -void infrared_on_system_start(void) { - Cli* cli = (Cli*)furi_record_open(RECORD_CLI); - cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir_wrapper, NULL); - furi_record_close(RECORD_CLI); -} diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index a42dfd9c1..5585a5f8f 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -3771,7 +3771,7 @@ protocol: NECext address: D9 14 00 00 command: 4F B0 00 00 # -name: Vol_down +name: Vol_dn type: parsed protocol: NECext address: D9 14 00 00 diff --git a/applications/main/infrared/resources/infrared/assets/projectors.ir b/applications/main/infrared/resources/infrared/assets/projectors.ir index 0279f73e9..00fb5aa9f 100644 --- a/applications/main/infrared/resources/infrared/assets/projectors.ir +++ b/applications/main/infrared/resources/infrared/assets/projectors.ir @@ -1812,7 +1812,7 @@ protocol: NECext address: 86 6B 00 00 command: 09 F6 00 00 # -name: Vol_down +name: Vol_dn type: parsed protocol: NECext address: 4F 50 00 00 diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index a52f141c4..4e5a965a7 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -2,15 +2,30 @@ #include -void infrared_scene_universal_common_item_callback(void* context, uint32_t index) { +#pragma pack(push, 1) +typedef union { + uint32_t packed_value; + struct { + bool is_paused; + uint8_t padding; + uint16_t signal_index; + }; +} InfraredSceneState; +#pragma pack(pop) + +void infrared_scene_universal_common_item_callback(void* context, uint32_t index, InputType type) { InfraredApp* infrared = context; - uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index); - view_dispatcher_send_custom_event(infrared->view_dispatcher, event); + if(type == InputTypeShort) { + uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index); + view_dispatcher_send_custom_event(infrared->view_dispatcher, event); + } } -static void infrared_scene_universal_common_progress_back_callback(void* context) { +static void infrared_scene_universal_common_progress_input_callback( + void* context, + InfraredProgressViewInput input) { InfraredApp* infrared = context; - uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1); + uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypePopupInput, input); view_dispatcher_send_custom_event(infrared->view_dispatcher, event); } @@ -19,8 +34,8 @@ static void ViewStack* view_stack = infrared->view_stack; InfraredProgressView* progress = infrared->progress; infrared_progress_view_set_progress_total(progress, record_count); - infrared_progress_view_set_back_callback( - progress, infrared_scene_universal_common_progress_back_callback, infrared); + infrared_progress_view_set_input_callback( + progress, infrared_scene_universal_common_progress_input_callback, infrared); view_stack_add_view(view_stack, infrared_progress_view_get_view(progress)); infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend); } @@ -51,29 +66,111 @@ void infrared_scene_universal_common_on_enter(void* context) { infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback); } +static void infrared_scene_universal_common_handle_popup_input( + InfraredApp* infrared, + InfraredProgressViewInput input) { + InfraredBruteForce* brute_force = infrared->brute_force; + SceneManager* scene_manager = infrared->scene_manager; + uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager); + switch(input) { + case InfraredProgressViewInputStop: { + infrared_brute_force_stop(brute_force); + infrared_scene_universal_common_hide_popup(infrared); + break; + } + + case InfraredProgressViewInputPause: { + infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop); + infrared_progress_view_set_paused(infrared->progress, true); + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + scene_state.is_paused = true; + if(scene_state.signal_index) + scene_state.signal_index--; // when running, the state stores the next index + scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value); + break; + } + + case InfraredProgressViewInputResume: { + infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend); + infrared_progress_view_set_paused(infrared->progress, false); + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + scene_state.is_paused = false; + scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value); + break; + } + + case InfraredProgressViewInputNextSignal: { + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + scene_state.signal_index++; + if(infrared_progress_view_set_progress(infrared->progress, scene_state.signal_index + 1)) + scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value); + break; + } + + case InfraredProgressViewInputPreviousSignal: { + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + if(scene_state.signal_index) { + scene_state.signal_index--; + if(infrared_progress_view_set_progress( + infrared->progress, scene_state.signal_index + 1)) + scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value); + } + break; + } + + case InfraredProgressViewInputSendSingle: { + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend); + infrared_brute_force_send(infrared->brute_force, scene_state.signal_index); + infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop); + break; + } + + default: + furi_crash(); + } +} + bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) { InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; InfraredBruteForce* brute_force = infrared->brute_force; + uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager); bool consumed = false; if(infrared_brute_force_is_started(brute_force)) { if(event.type == SceneManagerEventTypeTick) { - bool success = infrared_brute_force_send_next(brute_force); - if(success) { - success = infrared_progress_view_increase_progress(infrared->progress); + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + + if(!scene_state.is_paused) { + bool success = infrared_brute_force_send(brute_force, scene_state.signal_index); + if(success) { + success = infrared_progress_view_set_progress( + infrared->progress, scene_state.signal_index + 1); + scene_state.signal_index++; + scene_manager_set_scene_state( + scene_manager, scene_id, scene_state.packed_value); + } + if(!success) { + infrared_brute_force_stop(brute_force); + infrared_scene_universal_common_hide_popup(infrared); + } + consumed = true; } - if(!success) { - infrared_brute_force_stop(brute_force); - infrared_scene_universal_common_hide_popup(infrared); - } - consumed = true; } else if(event.type == SceneManagerEventTypeCustom) { - if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) { - infrared_brute_force_stop(brute_force); - infrared_scene_universal_common_hide_popup(infrared); + uint16_t event_type; + int16_t event_value; + infrared_custom_event_unpack(event.event, &event_type, &event_value); + if(event_type == InfraredCustomEventTypePopupInput) { + infrared_scene_universal_common_handle_popup_input(infrared, event_value); + consumed = true; } - consumed = true; } } else { if(event.type == SceneManagerEventTypeBack) { @@ -87,6 +184,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e if(event_type == InfraredCustomEventTypeButtonSelected) { uint32_t record_count; if(infrared_brute_force_start(brute_force, event_value, &record_count)) { + scene_manager_set_scene_state(infrared->scene_manager, scene_id, 0); dolphin_deed(DolphinDeedIrSend); infrared_scene_universal_common_show_popup(infrared, record_count); } else { diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.h b/applications/main/infrared/scenes/common/infrared_scene_universal_common.h index a6c697d77..277598e42 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.h +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.h @@ -5,4 +5,4 @@ void infrared_scene_universal_common_on_enter(void* context); bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event); void infrared_scene_universal_common_on_exit(void* context); -void infrared_scene_universal_common_item_callback(void* context, uint32_t index); +void infrared_scene_universal_common_item_callback(void* context, uint32_t index, InputType type); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c index 9288a4a4d..15c4b8db2 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_ac.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -118,7 +118,7 @@ void infrared_scene_universal_ac_on_enter(void* context) { button_panel_add_icon(button_panel, 0, 60, &I_cool_30x51); button_panel_add_icon(button_panel, 34, 60, &I_heat_30x51); - button_panel_add_label(button_panel, 4, 10, FontPrimary, "AC remote"); + button_panel_add_label(button_panel, 24, 10, FontPrimary, "AC"); infrared_scene_universal_common_on_enter(context); } diff --git a/applications/main/infrared/scenes/infrared_scene_universal_audio.c b/applications/main/infrared/scenes/infrared_scene_universal_audio.c index a15b2ce99..223251f10 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_audio.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_audio.c @@ -114,7 +114,7 @@ void infrared_scene_universal_audio_on_enter(void* context) { context); infrared_brute_force_add_record(brute_force, i++, "Vol_up"); - button_panel_add_label(button_panel, 1, 10, FontPrimary, "Mus. remote"); + button_panel_add_label(button_panel, 1, 10, FontPrimary, "Audio player"); button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30); infrared_scene_universal_common_on_enter(context); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_projector.c b/applications/main/infrared/scenes/infrared_scene_universal_projector.c index 19bcfd9be..ac3f0c3fe 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_projector.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_projector.c @@ -89,7 +89,7 @@ void infrared_scene_universal_projector_on_enter(void* context) { infrared_brute_force_add_record(brute_force, i++, "Pause"); button_panel_add_icon(button_panel, 4, 109, &I_pause_text_23x5); - button_panel_add_label(button_panel, 3, 11, FontPrimary, "Proj. remote"); + button_panel_add_label(button_panel, 10, 11, FontPrimary, "Projector"); button_panel_add_icon(button_panel, 34, 68, &I_vol_ac_text_30x30); infrared_scene_universal_common_on_enter(context); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_tv.c b/applications/main/infrared/scenes/infrared_scene_universal_tv.c index 16633e29c..6130861ee 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_tv.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_tv.c @@ -91,7 +91,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { context); infrared_brute_force_add_record(brute_force, i++, "Ch_prev"); - button_panel_add_label(button_panel, 5, 10, FontPrimary, "TV remote"); + button_panel_add_label(button_panel, 25, 10, FontPrimary, "TV"); infrared_scene_universal_common_on_enter(context); } diff --git a/applications/main/infrared/views/infrared_progress_view.c b/applications/main/infrared/views/infrared_progress_view.c index 1f491e4ab..76454b5bd 100644 --- a/applications/main/infrared/views/infrared_progress_view.c +++ b/applications/main/infrared/views/infrared_progress_view.c @@ -14,54 +14,80 @@ struct InfraredProgressView { View* view; - InfraredProgressViewBackCallback back_callback; + InfraredProgressViewInputCallback input_callback; void* context; }; typedef struct { size_t progress; size_t progress_total; + bool is_paused; } InfraredProgressViewModel; -bool infrared_progress_view_increase_progress(InfraredProgressView* progress) { - furi_assert(progress); - bool result = false; - - InfraredProgressViewModel* model = view_get_model(progress->view); - if(model->progress < model->progress_total) { - ++model->progress; - result = model->progress < model->progress_total; - } - view_commit_model(progress->view, true); - - return result; -} - static void infrared_progress_view_draw_callback(Canvas* canvas, void* _model) { InfraredProgressViewModel* model = (InfraredProgressViewModel*)_model; uint8_t x = 0; - uint8_t y = 36; + uint8_t y = 25; uint8_t width = 63; - uint8_t height = 59; + uint8_t height = 81; elements_bold_rounded_frame(canvas, x, y, width, height); canvas_set_font(canvas, FontSecondary); elements_multiline_text_aligned( - canvas, x + 34, y + 9, AlignCenter, AlignCenter, "Sending ..."); + canvas, + x + 32, + y + 9, + AlignCenter, + AlignCenter, + model->is_paused ? "Paused" : "Sending..."); float progress_value = (float)model->progress / model->progress_total; elements_progress_bar(canvas, x + 4, y + 19, width - 7, progress_value); - char number_string[10] = {0}; - snprintf( - number_string, sizeof(number_string), "%d/%d", model->progress, model->progress_total); + char progress_string[16] = {0}; + if(model->is_paused) { + snprintf( + progress_string, + sizeof(progress_string), + "%zu/%zu", + model->progress, + model->progress_total); + } else { + uint8_t percent_value = 100 * model->progress / model->progress_total; + snprintf(progress_string, sizeof(progress_string), "%d%%", percent_value); + } elements_multiline_text_aligned( - canvas, x + 33, y + 37, AlignCenter, AlignCenter, number_string); + canvas, x + 33, y + 37, AlignCenter, AlignCenter, progress_string); - canvas_draw_icon(canvas, x + 14, y + height - 14, &I_Pin_back_arrow_10x8); - canvas_draw_str(canvas, x + 30, y + height - 6, "= stop"); + uint8_t buttons_x = x + (model->is_paused ? 10 : 14); + uint8_t buttons_y = y + (model->is_paused ? 46 : 50); + + canvas_draw_icon(canvas, buttons_x + 0, buttons_y + 0, &I_Pin_back_arrow_10x8); + canvas_draw_str(canvas, buttons_x + 14, buttons_y + 8, model->is_paused ? "resume" : "stop"); + + canvas_draw_icon(canvas, buttons_x + 1, buttons_y + 10, &I_Ok_btn_9x9); + canvas_draw_str(canvas, buttons_x + 14, buttons_y + 17, model->is_paused ? "send 1" : "pause"); + + if(model->is_paused) { + canvas_draw_icon(canvas, buttons_x + 2, buttons_y + 21, &I_ButtonLeftSmall_3x5); + canvas_draw_icon(canvas, buttons_x + 7, buttons_y + 21, &I_ButtonRightSmall_3x5); + canvas_draw_str(canvas, buttons_x + 14, buttons_y + 26, "select"); + } +} + +bool infrared_progress_view_set_progress(InfraredProgressView* instance, uint16_t progress) { + bool result; + with_view_model( + instance->view, + InfraredProgressViewModel * model, + { + result = progress <= model->progress_total; + if(result) model->progress = progress; + }, + true); + return result; } void infrared_progress_view_set_progress_total( @@ -74,15 +100,45 @@ void infrared_progress_view_set_progress_total( view_commit_model(progress->view, false); } +void infrared_progress_view_set_paused(InfraredProgressView* instance, bool is_paused) { + with_view_model( + instance->view, InfraredProgressViewModel * model, { model->is_paused = is_paused; }, true); +} + bool infrared_progress_view_input_callback(InputEvent* event, void* context) { InfraredProgressView* instance = context; - if((event->type == InputTypeShort) && (event->key == InputKeyBack)) { - if(instance->back_callback) { - instance->back_callback(instance->context); - } + if(event->type == InputTypePress || event->type == InputTypeRelease) { + return false; } + if(!instance->input_callback) return false; + + with_view_model( + instance->view, + InfraredProgressViewModel * model, + { + if(model->is_paused) { + if(event->key == InputKeyLeft) + instance->input_callback( + instance->context, InfraredProgressViewInputPreviousSignal); + else if(event->key == InputKeyRight) + instance->input_callback( + instance->context, InfraredProgressViewInputNextSignal); + else if(event->key == InputKeyOk) + instance->input_callback( + instance->context, InfraredProgressViewInputSendSingle); + else if(event->key == InputKeyBack) + instance->input_callback(instance->context, InfraredProgressViewInputResume); + } else { + if(event->key == InputKeyOk) + instance->input_callback(instance->context, InfraredProgressViewInputPause); + else if(event->key == InputKeyBack) + instance->input_callback(instance->context, InfraredProgressViewInputStop); + } + }, + false); + return true; } @@ -106,12 +162,12 @@ void infrared_progress_view_free(InfraredProgressView* progress) { free(progress); } -void infrared_progress_view_set_back_callback( +void infrared_progress_view_set_input_callback( InfraredProgressView* instance, - InfraredProgressViewBackCallback callback, + InfraredProgressViewInputCallback callback, void* context) { furi_assert(instance); - instance->back_callback = callback; + instance->input_callback = callback; instance->context = context; } diff --git a/applications/main/infrared/views/infrared_progress_view.h b/applications/main/infrared/views/infrared_progress_view.h index 9d1f70e91..c33f1e553 100644 --- a/applications/main/infrared/views/infrared_progress_view.h +++ b/applications/main/infrared/views/infrared_progress_view.h @@ -13,8 +13,17 @@ extern "C" { /** Anonymous instance */ typedef struct InfraredProgressView InfraredProgressView; -/** Callback for back button handling */ -typedef void (*InfraredProgressViewBackCallback)(void*); +typedef enum { + InfraredProgressViewInputStop, + InfraredProgressViewInputPause, + InfraredProgressViewInputResume, + InfraredProgressViewInputPreviousSignal, + InfraredProgressViewInputNextSignal, + InfraredProgressViewInputSendSingle, +} InfraredProgressViewInput; + +/** Callback for input handling */ +typedef void (*InfraredProgressViewInputCallback)(void* context, InfraredProgressViewInput event); /** Allocate and initialize Infrared view * @@ -35,13 +44,12 @@ void infrared_progress_view_free(InfraredProgressView* instance); */ View* infrared_progress_view_get_view(InfraredProgressView* instance); -/** Increase progress on progress view module +/** Set progress of progress view module * * @param instance view module - * @retval true - value is incremented and maximum is reached, - * false - value is incremented and maximum is not reached + * @param progress progress value */ -bool infrared_progress_view_increase_progress(InfraredProgressView* instance); +bool infrared_progress_view_set_progress(InfraredProgressView* instance, uint16_t progress); /** Set maximum progress value * @@ -52,15 +60,22 @@ void infrared_progress_view_set_progress_total( InfraredProgressView* instance, uint16_t progress_max); -/** Set back button callback +/** Selects the variant of the View + * + * @param instance view instance + * @param is_paused the "paused" variant is displayed if true; the "sending" one if false + */ +void infrared_progress_view_set_paused(InfraredProgressView* instance, bool is_paused); + +/** Set input callback * * @param instance - view module - * @param callback - callback to call for back button + * @param callback - callback to call for input * @param context - context to pass to callback */ -void infrared_progress_view_set_back_callback( +void infrared_progress_view_set_input_callback( InfraredProgressView* instance, - InfraredProgressViewBackCallback callback, + InfraredProgressViewInputCallback callback, void* context); #ifdef __cplusplus diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index efded8001..d6fca74f4 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -13,19 +13,10 @@ App( ) App( - appid="lfrfid_cli", + appid="cli_rfid", targets=["f7"], apptype=FlipperAppType.PLUGIN, - entry_point="lfrfid_cli_plugin_ep", + entry_point="cli_rfid_ep", requires=["cli"], sources=["lfrfid_cli.c"], ) - -App( - appid="lfrfid_start", - targets=["f7"], - apptype=FlipperAppType.STARTUP, - entry_point="lfrfid_on_system_start", - sources=["lfrfid_start.c"], - order=50, -) diff --git a/applications/main/lfrfid/lfrfid_cli.c b/applications/main/lfrfid/lfrfid_cli.c index eaafcda92..63ca046b9 100644 --- a/applications/main/lfrfid/lfrfid_cli.c +++ b/applications/main/lfrfid/lfrfid_cli.c @@ -1,11 +1,12 @@ #include #include #include -#include +#include #include #include #include #include +#include #include @@ -40,7 +41,7 @@ static void lfrfid_cli_read_callback(LFRFIDWorkerReadResult result, ProtocolId p furi_event_flag_set(context->event, 1 << result); } -static void lfrfid_cli_read(Cli* cli, FuriString* args) { +static void lfrfid_cli_read(PipeSide* pipe, FuriString* args) { FuriString* type_string; type_string = furi_string_alloc(); LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto; @@ -87,7 +88,7 @@ static void lfrfid_cli_read(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } lfrfid_worker_stop(worker); @@ -183,7 +184,7 @@ static void lfrfid_cli_write_callback(LFRFIDWorkerWriteResult result, void* ctx) furi_event_flag_set(events, 1 << result); } -static void lfrfid_cli_write(Cli* cli, FuriString* args) { +static void lfrfid_cli_write(PipeSide* pipe, FuriString* args) { ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol; @@ -203,7 +204,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) { (1 << LFRFIDWorkerWriteProtocolCannotBeWritten) | (1 << LFRFIDWorkerWriteFobCannotBeWritten); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); if(flags != (unsigned)FuriFlagErrorTimeout) { if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) { @@ -230,7 +231,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) { furi_event_flag_free(event); } -static void lfrfid_cli_emulate(Cli* cli, FuriString* args) { +static void lfrfid_cli_emulate(PipeSide* pipe, FuriString* args) { ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol; @@ -245,7 +246,7 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) { lfrfid_worker_emulate_start(worker, protocol); printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n"); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(100); } printf("Emulation stopped\r\n"); @@ -256,8 +257,8 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) { protocol_dict_free(dict); } -static void lfrfid_cli_raw_analyze(Cli* cli, FuriString* args) { - UNUSED(cli); +static void lfrfid_cli_raw_analyze(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); FuriString *filepath, *info_string; filepath = furi_string_alloc(); info_string = furi_string_alloc(); @@ -383,9 +384,7 @@ static void lfrfid_cli_raw_read_callback(LFRFIDWorkerReadRawResult result, void* furi_event_flag_set(event, 1 << result); } -static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) { - UNUSED(cli); - +static void lfrfid_cli_raw_read(PipeSide* pipe, FuriString* args) { FuriString *filepath, *type_string; filepath = furi_string_alloc(); type_string = furi_string_alloc(); @@ -443,7 +442,7 @@ static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } if(overrun) { @@ -470,9 +469,7 @@ static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result, furi_event_flag_set(event, 1 << result); } -static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) { - UNUSED(cli); - +static void lfrfid_cli_raw_emulate(PipeSide* pipe, FuriString* args) { FuriString* filepath; filepath = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); @@ -518,7 +515,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } if(overrun) { @@ -539,7 +536,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) { furi_string_free(filepath); } -static void lfrfid_cli(Cli* cli, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -551,17 +548,17 @@ static void lfrfid_cli(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "read") == 0) { - lfrfid_cli_read(cli, args); + lfrfid_cli_read(pipe, args); } else if(furi_string_cmp_str(cmd, "write") == 0) { - lfrfid_cli_write(cli, args); + lfrfid_cli_write(pipe, args); } else if(furi_string_cmp_str(cmd, "emulate") == 0) { - lfrfid_cli_emulate(cli, args); + lfrfid_cli_emulate(pipe, args); } else if(furi_string_cmp_str(cmd, "raw_read") == 0) { - lfrfid_cli_raw_read(cli, args); + lfrfid_cli_raw_read(pipe, args); } else if(furi_string_cmp_str(cmd, "raw_emulate") == 0) { - lfrfid_cli_raw_emulate(cli, args); + lfrfid_cli_raw_emulate(pipe, args); } else if(furi_string_cmp_str(cmd, "raw_analyze") == 0) { - lfrfid_cli_raw_analyze(cli, args); + lfrfid_cli_raw_analyze(pipe, args); } else { lfrfid_cli_print_usage(); } @@ -569,15 +566,4 @@ static void lfrfid_cli(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -#include -#include - -static const FlipperAppPluginDescriptor plugin_descriptor = { - .appid = CLI_PLUGIN_APP_ID, - .ep_api_version = CLI_PLUGIN_API_VERSION, - .entry_point = &lfrfid_cli, -}; - -const FlipperAppPluginDescriptor* lfrfid_cli_plugin_ep(void) { - return &plugin_descriptor; -} +CLI_COMMAND_INTERFACE(rfid, execute, CliCommandFlagDefault, 2048, CLI_APPID); diff --git a/applications/main/lfrfid/lfrfid_i.h b/applications/main/lfrfid/lfrfid_i.h index 913b7358b..c34b201bb 100644 --- a/applications/main/lfrfid/lfrfid_i.h +++ b/applications/main/lfrfid/lfrfid_i.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #include diff --git a/applications/main/lfrfid/lfrfid_start.c b/applications/main/lfrfid/lfrfid_start.c deleted file mode 100644 index faf275355..000000000 --- a/applications/main/lfrfid/lfrfid_start.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -static void lfrfid_cli_wrapper(Cli* cli, FuriString* args, void* context) { - cli_plugin_wrapper("lfrfid", cli, args, context); -} - -void lfrfid_on_system_start(void) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli_wrapper, NULL); - furi_record_close(RECORD_CLI); -} diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 3eb24096b..96ac4c3b7 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -311,19 +311,20 @@ App( ) App( - appid="nfc_cli", - targets=["f7"], + appid="disney_infinity_parser", apptype=FlipperAppType.PLUGIN, - entry_point="nfc_cli_plugin_ep", - requires=["cli"], - sources=["nfc_cli.c"], + entry_point="disney_infinity_plugin_ep", + targets=["f7"], + requires=["nfc"], + fap_libs=["mbedtls"], + sources=["plugins/supported_cards/disney_infinity.c"], ) App( - appid="nfc_start", + appid="cli_nfc", targets=["f7"], - apptype=FlipperAppType.STARTUP, - entry_point="nfc_on_system_start", - sources=["nfc_start.c"], - order=30, + apptype=FlipperAppType.PLUGIN, + entry_point="cli_nfc_ep", + requires=["cli"], + sources=["nfc_cli.c"], ) diff --git a/applications/main/nfc/helpers/mf_ultralight_auth.c b/applications/main/nfc/helpers/mf_ultralight_auth.c index e97649cb3..fccf50cdd 100644 --- a/applications/main/nfc/helpers/mf_ultralight_auth.c +++ b/applications/main/nfc/helpers/mf_ultralight_auth.c @@ -18,9 +18,11 @@ void mf_ultralight_auth_free(MfUltralightAuth* instance) { void mf_ultralight_auth_reset(MfUltralightAuth* instance) { furi_assert(instance); + uint32_t default_password = MF_ULTRALIGHT_DEFAULT_PASSWORD; + instance->type = MfUltralightAuthTypeNone; - memset(&instance->password, 0, sizeof(MfUltralightAuthPassword)); - memset(&instance->tdes_key, 0, sizeof(MfUltralightC3DesAuthKey)); + memcpy(&instance->password, &default_password, sizeof(MfUltralightAuthPassword)); + memcpy(&instance->tdes_key, MF_ULTRALIGHT_C_DEFAULT_KEY, sizeof(MfUltralightC3DesAuthKey)); memset(&instance->pack, 0, sizeof(MfUltralightAuthPack)); } diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c index ba8f10b93..fbf331d3b 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c @@ -37,11 +37,13 @@ void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) { } void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str) { - if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { + const uint16_t block_count = iso15693_3_get_block_count(data); + const uint8_t block_size = iso15693_3_get_block_size(data); + + if((data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) && + (block_count > 0 && block_size > 0)) { furi_string_cat(str, "\e#Memory data\n\e*--------------------\n"); - const uint16_t block_count = iso15693_3_get_block_count(data); - const uint8_t block_size = iso15693_3_get_block_size(data); const uint16_t display_block_count = MIN(NFC_RENDER_ISO15693_3_MAX_BYTES / block_size, block_count); diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 27e9a60f5..4f8540d6e 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -14,7 +14,8 @@ enum { SubmenuIndexDetectReader = SubmenuIndexCommonMax, SubmenuIndexWrite, SubmenuIndexUpdate, - SubmenuIndexDictAttack + SubmenuIndexDictAttack, + SubmenuIndexCrackNonces, }; static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { @@ -128,6 +129,13 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { SubmenuIndexDictAttack, nfc_protocol_support_common_submenu_callback, instance); + + submenu_add_item( + submenu, + "Crack nonces in MFKey32", + SubmenuIndexCrackNonces, + nfc_protocol_support_common_submenu_callback, + instance); } } @@ -193,6 +201,11 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexDetectReader) { + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneSaveConfirm, + NfcSceneSaveConfirmStateDetectReader); + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm); dolphin_deed(DolphinDeedNfcDetectReader); consumed = true; @@ -205,6 +218,11 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag } else if(event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); consumed = true; + } else if(event.event == SubmenuIndexCrackNonces) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneSaveConfirm, NfcSceneSaveConfirmStateCrackNonces); + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm); + consumed = true; } } diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index 783cbb871..96e4a30f9 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -180,6 +180,9 @@ void nfc_render_mf_desfire_file_settings_data( case MfDesfireFileTypeCyclicRecord: type = "cyclic"; break; + case MfDesfireFileTypeTransactionMac: + type = "txn-mac"; + break; default: type = "unknown"; } @@ -237,6 +240,15 @@ void nfc_render_mf_desfire_file_settings_data( furi_string_cat_printf(str, "size %lu\n", record_size); furi_string_cat_printf(str, "num %lu max %lu\n", record_count, settings->record.max); break; + case MfDesfireFileTypeTransactionMac: + record_count = 0; + furi_string_cat_printf( + str, + "key opt %02X ver %02X\n", + settings->transaction_mac.key_option, + settings->transaction_mac.key_version); + furi_string_cat_printf(str, "cnt limit %lu\n", settings->transaction_mac.counter_limit); + break; } bool is_auth_required = true; diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 04b472bec..c8973fb0d 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -476,6 +476,26 @@ static void nfc_show_initial_scene_for_device(NfcApp* nfc) { scene_manager_next_scene(nfc->scene_manager, scene); } +void nfc_app_run_external(NfcApp* nfc, const char* app_path) { + furi_assert(nfc); + furi_assert(app_path); + + Loader* loader = furi_record_open(RECORD_LOADER); + + loader_enqueue_launch(loader, app_path, NULL, LoaderDeferredLaunchFlagGui); + + FuriString* self_path = furi_string_alloc(); + furi_check(loader_get_application_launch_path(loader, self_path)); + + loader_enqueue_launch( + loader, furi_string_get_cstr(self_path), NULL, LoaderDeferredLaunchFlagGui); + furi_string_free(self_path); + + furi_record_close(RECORD_LOADER); + + view_dispatcher_stop(nfc->view_dispatcher); +} + int32_t nfc_app(void* p) { if(!nfc_is_hal_ready()) return 0; diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 970da89ad..78f6e8e98 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -37,6 +36,7 @@ #include "helpers/felica_auth.h" #include "helpers/slix_unlock.h" +#include #include #include #include @@ -82,6 +82,8 @@ #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH \ (NFC_APP_FOLDER "/assets/mf_classic_dict_nested.nfc") +#define NFC_MFKEY32_APP_PATH (EXT_PATH("apps/NFC/mfkey.fap")) + typedef enum { NfcRpcStateIdle, NfcRpcStateEmulating, @@ -169,6 +171,11 @@ typedef enum { NfcViewDetectReader, } NfcView; +typedef enum { + NfcSceneSaveConfirmStateDetectReader, + NfcSceneSaveConfirmStateCrackNonces, +} NfcSceneSaveConfirmState; + int32_t nfc_task(void* p); void nfc_text_store_set(NfcApp* nfc, const char* text, ...); @@ -204,3 +211,5 @@ bool nfc_save_file(NfcApp* instance, FuriString* path); void nfc_make_app_folder(NfcApp* instance); void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string); + +void nfc_app_run_external(NfcApp* nfc, const char* app_path); diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index fae8ca933..5b54d38db 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -1,23 +1,45 @@ #include #include -#include +#include #include #include +#include +#include +#include +#include +#include +#include #include #define FLAG_EVENT (1 << 10) +#define NFC_MAX_BUFFER_SIZE (256) +#define NFC_BASE_PROTOCOL_MAX (3) +#define POLLER_DONE (1 << 0) +#define POLLER_ERR (1 << 1) +static NfcProtocol BASE_PROTOCOL[NFC_BASE_PROTOCOL_MAX] = { + NfcProtocolIso14443_4a, + NfcProtocolIso14443_4b, + NfcProtocolIso15693_3}; +typedef struct ApduContext { + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + bool ready; + FuriThreadId thread_id; +} ApduContext; + static void nfc_cli_print_usage(void) { printf("Usage:\r\n"); printf("nfc \r\n"); printf("Cmd list:\r\n"); + printf("\tapdu\t - Send APDU and print response \r\n"); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { printf("\tfield\t - turn field on\r\n"); } } -static void nfc_cli_field(Cli* cli, FuriString* args) { +static void nfc_cli_field(PipeSide* pipe, FuriString* args) { UNUSED(args); // Check if nfc worker is not busy if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { @@ -32,7 +54,7 @@ static void nfc_cli_field(Cli* cli, FuriString* args) { printf("Field is on. Don't leave device in this mode for too long.\r\n"); printf("Press Ctrl+C to abort\r\n"); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(50); } @@ -40,7 +62,145 @@ static void nfc_cli_field(Cli* cli, FuriString* args) { furi_hal_nfc_release(); } -static void nfc_cli(Cli* cli, FuriString* args, void* context) { +static NfcCommand trx_callback(NfcGenericEvent event, void* context) { + furi_check(context); + ApduContext* apdu_context = (ApduContext*)context; + + if(apdu_context->ready) { + apdu_context->ready = false; + if(NfcProtocolIso14443_4a == event.protocol) { + Iso14443_4aError err = iso14443_4a_poller_send_block( + event.instance, apdu_context->tx_buffer, apdu_context->rx_buffer); + if(Iso14443_4aErrorNone == err) { + furi_thread_flags_set(apdu_context->thread_id, POLLER_DONE); + return NfcCommandContinue; + } else { + furi_thread_flags_set(apdu_context->thread_id, POLLER_ERR); + return NfcCommandStop; + } + } else if(NfcProtocolIso14443_4b == event.protocol) { + Iso14443_4bError err = iso14443_4b_poller_send_block( + event.instance, apdu_context->tx_buffer, apdu_context->rx_buffer); + if(Iso14443_4bErrorNone == err) { + furi_thread_flags_set(apdu_context->thread_id, POLLER_DONE); + return NfcCommandContinue; + } else { + furi_thread_flags_set(apdu_context->thread_id, POLLER_ERR); + return NfcCommandStop; + } + } else if(NfcProtocolIso15693_3 == event.protocol) { + Iso15693_3Error err = iso15693_3_poller_send_frame( + event.instance, + apdu_context->tx_buffer, + apdu_context->rx_buffer, + ISO15693_3_FDT_POLL_FC); + if(Iso15693_3ErrorNone == err) { + furi_thread_flags_set(apdu_context->thread_id, POLLER_DONE); + return NfcCommandContinue; + } else { + furi_thread_flags_set(apdu_context->thread_id, POLLER_ERR); + return NfcCommandStop; + } + } else { + // should never reach here + furi_crash("Unknown protocol"); + } + } else { + furi_delay_ms(100); + } + + return NfcCommandContinue; +} + +static void nfc_cli_apdu(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); + Nfc* nfc = NULL; + NfcPoller* poller = NULL; + FuriString* data = furi_string_alloc(); + uint8_t* req_buffer = NULL; + uint8_t* resp_buffer = NULL; + size_t apdu_size = 0; + size_t resp_size = 0; + NfcProtocol current_protocol = NfcProtocolInvalid; + + do { + if(0 == args_get_first_word_length(args)) { + printf( + "Use like `nfc apdu 00a404000e325041592e5359532e444446303100 00a4040008a0000003010102` \r\n"); + break; + } + nfc = nfc_alloc(); + + printf("detecting tag\r\n"); + for(int i = 0; i < NFC_BASE_PROTOCOL_MAX; i++) { + poller = nfc_poller_alloc(nfc, BASE_PROTOCOL[i]); + bool is_detected = nfc_poller_detect(poller); + nfc_poller_free(poller); + if(is_detected) { + current_protocol = BASE_PROTOCOL[i]; + printf("detected tag:%d\r\n", BASE_PROTOCOL[i]); + break; + } + } + if(NfcProtocolInvalid == current_protocol) { + nfc_free(nfc); + printf("Can not find any tag\r\n"); + break; + } + poller = nfc_poller_alloc(nfc, current_protocol); + ApduContext* apdu_context = malloc(sizeof(ApduContext)); + apdu_context->tx_buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); + apdu_context->rx_buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); + apdu_context->ready = false; + apdu_context->thread_id = furi_thread_get_current_id(); + + nfc_poller_start(poller, trx_callback, apdu_context); + while(args_read_string_and_trim(args, data)) { + bit_buffer_reset(apdu_context->tx_buffer); + bit_buffer_reset(apdu_context->rx_buffer); + apdu_size = furi_string_size(data) / 2; + req_buffer = malloc(apdu_size); + + hex_chars_to_uint8(furi_string_get_cstr(data), req_buffer); + printf("Sending APDU:%s to Tag\r\n", furi_string_get_cstr(data)); + bit_buffer_copy_bytes(apdu_context->tx_buffer, req_buffer, apdu_size); + apdu_context->ready = true; + uint32_t flags = furi_thread_flags_wait(POLLER_DONE, FuriFlagWaitAny, 3000); + if(0 == (flags & POLLER_DONE)) { + printf("Error or Timeout"); + free(req_buffer); + break; + } + furi_assert(apdu_context->ready == false); + resp_size = bit_buffer_get_size_bytes(apdu_context->rx_buffer) * 2; + if(!resp_size) { + printf("No response\r\n"); + free(req_buffer); + continue; + } + resp_buffer = malloc(resp_size); + uint8_to_hex_chars( + bit_buffer_get_data(apdu_context->rx_buffer), resp_buffer, resp_size); + resp_buffer[resp_size] = 0; + printf("Response: %s\r\n", resp_buffer); + + free(req_buffer); + free(resp_buffer); + } + + nfc_poller_stop(poller); + nfc_poller_free(poller); + nfc_free(nfc); + + bit_buffer_free(apdu_context->tx_buffer); + bit_buffer_free(apdu_context->rx_buffer); + free(apdu_context); + } while(false); + + furi_string_free(data); +} + +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -50,9 +210,13 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) { nfc_cli_print_usage(); break; } + if(furi_string_cmp_str(cmd, "apdu") == 0) { + nfc_cli_apdu(pipe, args); + break; + } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "field") == 0) { - nfc_cli_field(cli, args); + nfc_cli_field(pipe, args); break; } } @@ -63,15 +227,4 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -#include -#include - -static const FlipperAppPluginDescriptor plugin_descriptor = { - .appid = CLI_PLUGIN_APP_ID, - .ep_api_version = CLI_PLUGIN_API_VERSION, - .entry_point = &nfc_cli, -}; - -const FlipperAppPluginDescriptor* nfc_cli_plugin_ep(void) { - return &plugin_descriptor; -} +CLI_COMMAND_INTERFACE(nfc, execute, CliCommandFlagDefault, 1024, CLI_APPID); diff --git a/applications/main/nfc/nfc_start.c b/applications/main/nfc/nfc_start.c deleted file mode 100644 index d38a956c3..000000000 --- a/applications/main/nfc/nfc_start.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -static void nfc_cli_wrapper(Cli* cli, FuriString* args, void* context) { - cli_plugin_wrapper("nfc", cli, args, context); -} - -void nfc_on_system_start(void) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "nfc", CliCommandFlagDefault, nfc_cli_wrapper, NULL); - furi_record_close(RECORD_CLI); -} diff --git a/applications/main/nfc/plugins/supported_cards/disney_infinity.c b/applications/main/nfc/plugins/supported_cards/disney_infinity.c new file mode 100644 index 000000000..a98d39ec2 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/disney_infinity.c @@ -0,0 +1,121 @@ +#include +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include +#include + +#define TAG "DisneyInfinity" +#define UID_LEN 7 + +// Derived from https://nfc.toys/#new-interoperability-for-infinity +static uint8_t seed[38] = {0x0A, 0x14, 0xFD, 0x05, 0x07, 0xFF, 0x4B, 0xCD, 0x02, 0x6B, + 0xA8, 0x3F, 0x0A, 0x3B, 0x89, 0xA9, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x28, 0x63, 0x29, 0x20, 0x44, 0x69, 0x73, + 0x6E, 0x65, 0x79, 0x20, 0x32, 0x30, 0x31, 0x33}; + +void di_key(const uint8_t* uid, MfClassicKey* key) { + uint8_t hash[20]; + memcpy(seed + 16, uid, UID_LEN); + mbedtls_sha1(seed, sizeof(seed), hash); + key->data[0] = hash[3]; + key->data[1] = hash[2]; + key->data[2] = hash[1]; + key->data[3] = hash[0]; + key->data[4] = hash[7]; + key->data[5] = hash[6]; +} + +static bool disney_infinity_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + size_t* uid_len = 0; + bool is_read = false; + MfClassicData* data = mf_classic_alloc(); + + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len); + MfClassicDeviceKeys keys = {}; + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + di_key(uid_bytes, &keys.key_a[i]); + di_key(uid_bytes, &keys.key_b[i]); + FURI_BIT_SET(keys.key_a_mask, i); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data: %d", error); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool disney_infinity_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + size_t* uid_len = 0; + bool parsed = false; + FuriString* name = furi_string_alloc(); + const uint8_t verify_sector = 0; + MfClassicKey key = {}; + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len); + + do { + // verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + + di_key(uid_bytes, &key); + if(memcmp(key.data, sec_tr->key_a.data, 6) != 0) break; + + // At some point I'd like to add name lookup like Skylanders + furi_string_printf(parsed_data, "\e#Disney Infinity\n"); + + parsed = true; + + } while(false); + + furi_string_free(name); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin disney_infinity_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = NULL, // Need UID to verify key(s) + .read = disney_infinity_read, + .parse = disney_infinity_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor disney_infinity_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &disney_infinity_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* disney_infinity_plugin_ep(void) { + return &disney_infinity_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/ndef.c b/applications/main/nfc/plugins/supported_cards/ndef.c index fb2c4da48..06982e111 100644 --- a/applications/main/nfc/plugins/supported_cards/ndef.c +++ b/applications/main/nfc/plugins/supported_cards/ndef.c @@ -22,6 +22,7 @@ #include #include +#include #define TAG "NDEF" @@ -181,30 +182,34 @@ static bool ndef_get(Ndef* ndef, size_t pos, size_t len, void* buf) { // So the first 93 (31*3) data blocks correspond to 128 real blocks. // Last 128 blocks are 8 sectors: 15 data blocks, 1 sector trailer. // So the last 120 (8*15) data blocks correspond to 128 real blocks. - div_t small_sector_data_blocks = div(pos, MF_CLASSIC_BLOCK_SIZE); + const size_t real_block_data_offset = pos % MF_CLASSIC_BLOCK_SIZE; + size_t small_sector_data_blocks = pos / MF_CLASSIC_BLOCK_SIZE; size_t large_sector_data_blocks = 0; - if(small_sector_data_blocks.quot > 93) { - large_sector_data_blocks = small_sector_data_blocks.quot - 93; - small_sector_data_blocks.quot = 93; + if(small_sector_data_blocks > 93) { + large_sector_data_blocks = small_sector_data_blocks - 93; + small_sector_data_blocks = 93; } - div_t small_sectors = div(small_sector_data_blocks.quot, 3); - size_t real_block = small_sectors.quot * 4 + small_sectors.rem; - if(small_sectors.quot >= 16) { + const size_t small_sector_block_offset = small_sector_data_blocks % 3; + const size_t small_sectors = small_sector_data_blocks / 3; + size_t real_block = small_sectors * 4 + small_sector_block_offset; + if(small_sectors >= 16) { real_block += 4; // Skip MAD2 } if(large_sector_data_blocks) { - div_t large_sectors = div(large_sector_data_blocks, 15); - real_block += large_sectors.quot * 16 + large_sectors.rem; + const size_t large_sector_block_offset = large_sector_data_blocks % 15; + const size_t large_sectors = large_sector_data_blocks / 15; + real_block += large_sectors * 16 + large_sector_block_offset; } - const uint8_t* cur = &ndef->mfc.blocks[real_block].data[small_sector_data_blocks.rem]; + const uint8_t* cur = &ndef->mfc.blocks[real_block].data[real_block_data_offset]; while(len) { size_t sector_trailer = mf_classic_get_sector_trailer_num_by_block(real_block); const uint8_t* end = &ndef->mfc.blocks[sector_trailer].data[0]; - size_t chunk_len = MIN((size_t)(end - cur), len); + const size_t chunk_len = MIN((size_t)(end - cur), len); memcpy(buf, cur, chunk_len); + buf += chunk_len; len -= chunk_len; if(len) { @@ -244,7 +249,9 @@ static inline bool is_printable(char c) { static bool is_text(const uint8_t* buf, size_t len) { for(size_t i = 0; i < len; i++) { - if(!is_printable(buf[i])) return false; + if(!is_printable(buf[i]) && !(buf[i] == '\0' && i == len - 1)) { + return false; + } } return true; } @@ -260,7 +267,7 @@ static bool ndef_dump(Ndef* ndef, const char* prefix, size_t pos, size_t len, bo for(size_t i = 0; i < len; i++) { char c; if(!ndef_get(ndef, pos + i, 1, &c)) return false; - if(!is_printable(c)) { + if(!is_printable(c) && !(c == '\0' && i == len - 1)) { furi_string_left(ndef->output, string_prev); force_hex = true; break; @@ -268,14 +275,18 @@ static bool ndef_dump(Ndef* ndef, const char* prefix, size_t pos, size_t len, bo furi_string_push_back(ndef->output, c); } } - if(force_hex) { - for(size_t i = 0; i < len; i++) { - uint8_t b; - if(!ndef_get(ndef, pos + i, 1, &b)) return false; - furi_string_cat_printf(ndef->output, "%02X ", b); + if(!force_hex) { + furi_string_cat(ndef->output, "\n"); + } else { + uint8_t buf[4]; + for(size_t i = 0; i < len; i += sizeof(buf)) { + uint8_t buf_len = MIN(sizeof(buf), len - i); + if(!ndef_get(ndef, pos + i, buf_len, &buf)) return false; + pretty_format_bytes_hex_canonical( + ndef->output, 4, PRETTY_FORMAT_FONT_MONOSPACE, buf, buf_len); + furi_string_cat(ndef->output, "\n"); } } - furi_string_cat(ndef->output, "\n"); return true; } @@ -285,9 +296,7 @@ static void if(!force_hex && is_text(buf, len)) { furi_string_cat_printf(ndef->output, "%.*s", len, (const char*)buf); } else { - for(size_t i = 0; i < len; i++) { - furi_string_cat_printf(ndef->output, "%02X ", ((const uint8_t*)buf)[i]); - } + pretty_format_bytes_hex_canonical(ndef->output, 4, PRETTY_FORMAT_FONT_MONOSPACE, buf, len); } furi_string_cat(ndef->output, "\n"); } @@ -582,7 +591,7 @@ bool ndef_parse_record( NdefTnf tnf, const char* type, uint8_t type_len) { - FURI_LOG_D(TAG, "payload type: %.*s len: %hu", type_len, type, len); + FURI_LOG_D(TAG, "payload type: %.*s len: %hu pos: %zu", type_len, type, len, pos); if(!len) { furi_string_cat(ndef->output, "Empty\n"); return true; @@ -887,13 +896,13 @@ static bool ndef_mfc_parse(const NfcDevice* device, FuriString* parsed_data) { for(uint8_t mad = 0; mad < COUNT_OF(mads); mad++) { const size_t block = mads[mad].block; const size_t sector = mf_classic_get_sector_by_block(block); - if(sector_count <= sector) break; // Skip this MAD if not present + if(sector_count <= sector) continue; // Skip this MAD if not present // Check MAD key const MfClassicSectorTrailer* sector_trailer = mf_classic_get_sector_trailer_by_sector(data, sector); const uint64_t sector_key_a = bit_lib_bytes_to_num_be( sector_trailer->key_a.data, COUNT_OF(sector_trailer->key_a.data)); - if(sector_key_a != mad_key) return false; + if(sector_key_a != mad_key) continue; // Find NDEF AIDs for(uint8_t aid_index = 0; aid_index < mads[mad].aid_count; aid_index++) { const uint8_t* aid = &data->block[block].data[2 + aid_index * AID_SIZE]; @@ -917,7 +926,7 @@ static bool ndef_mfc_parse(const NfcDevice* device, FuriString* parsed_data) { data_size = 93 + (sector_count - 32) * 15; } else { data_size = sector_count * 3; - if(sector_count >= 16) { + if(sector_count > 16) { data_size -= 3; // Skip MAD2 } } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c index 660674ceb..3311ef0e0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c @@ -1,4 +1,10 @@ #include "../nfc_app_i.h" +#include "loader/loader.h" + +typedef enum { + NfcSceneMfClassicMfKeyCompleteStateAppMissing, + NfcSceneMfClassicMfKeyCompleteStateAppPresent, +} NfcSceneMfClassicMfKeyCompleteState; void nfc_scene_mf_classic_mfkey_complete_callback( GuiButtonType result, @@ -15,22 +21,47 @@ void nfc_scene_mf_classic_mfkey_complete_on_enter(void* context) { widget_add_string_element( instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Completed!"); - widget_add_string_multiline_element( - instance->widget, - 64, - 13, - AlignCenter, - AlignTop, - FontSecondary, - "Now use Mfkey32 to extract \nkeys: r.flipper.net/nfc-tools"); - widget_add_icon_element(instance->widget, 50, 39, &I_MFKey_qr_25x25); - widget_add_button_element( - instance->widget, - GuiButtonTypeRight, - "Finish", - nfc_scene_mf_classic_mfkey_complete_callback, - instance); + NfcSceneMfClassicMfKeyCompleteState scene_state = + storage_common_exists(instance->storage, NFC_MFKEY32_APP_PATH) ? + NfcSceneMfClassicMfKeyCompleteStateAppPresent : + NfcSceneMfClassicMfKeyCompleteStateAppMissing; + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfClassicMfkeyComplete, scene_state); + + if(scene_state == NfcSceneMfClassicMfKeyCompleteStateAppMissing) { + widget_add_string_multiline_element( + instance->widget, + 64, + 13, + AlignCenter, + AlignTop, + FontSecondary, + "Now use Mfkey32 to extract \nkeys: r.flipper.net/nfc-tools"); + widget_add_icon_element(instance->widget, 50, 39, &I_MFKey_qr_25x25); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Finish", + nfc_scene_mf_classic_mfkey_complete_callback, + instance); + } else { + widget_add_string_multiline_element( + instance->widget, + 60, + 16, + AlignLeft, + AlignTop, + FontSecondary, + "Now run Mfkey32\n to extract \nkeys"); + widget_add_icon_element(instance->widget, 5, 18, &I_WarningDolphin_45x42); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Run", + nfc_scene_mf_classic_mfkey_complete_callback, + instance); + } view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } @@ -40,8 +71,14 @@ bool nfc_scene_mf_classic_mfkey_complete_on_event(void* context, SceneManagerEve if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeRight) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcSceneStart); + NfcSceneMfClassicMfKeyCompleteState scene_state = scene_manager_get_scene_state( + instance->scene_manager, NfcSceneMfClassicMfkeyComplete); + if(scene_state == NfcSceneMfClassicMfKeyCompleteStateAppMissing) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneStart); + } else { + nfc_app_run_external(instance, NFC_MFKEY32_APP_PATH); + } } } else if(event.type == SceneManagerEventTypeBack) { const uint32_t prev_scenes[] = {NfcSceneSavedMenu, NfcSceneStart}; diff --git a/applications/main/nfc/scenes/nfc_scene_save_confirm.c b/applications/main/nfc/scenes/nfc_scene_save_confirm.c index 9d0a206d3..d8fb20642 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_save_confirm.c @@ -29,7 +29,14 @@ bool nfc_scene_save_confirm_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); consumed = true; } else if(event.event == DialogExResultLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); + NfcSceneSaveConfirmState scene_state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm); + + NfcScene scene = scene_state == NfcSceneSaveConfirmStateCrackNonces ? + NfcSceneMfClassicMfkeyComplete : + NfcSceneMfClassicDetectReader; + + scene_manager_next_scene(nfc->scene_manager, scene); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index 1c76b3f87..5f812ba9c 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -29,7 +29,13 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); + NfcSceneSaveConfirmState scene_state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm); + + NfcScene scene = scene_state == NfcSceneSaveConfirmStateCrackNonces ? + NfcSceneMfClassicMfkeyComplete : + NfcSceneMfClassicDetectReader; + scene_manager_next_scene(nfc->scene_manager, scene); consumed = true; } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { consumed = scene_manager_search_and_switch_to_another_scene( diff --git a/applications/main/onewire/application.fam b/applications/main/onewire/application.fam index 9fac2ff21..e38bcdfef 100644 --- a/applications/main/onewire/application.fam +++ b/applications/main/onewire/application.fam @@ -1,16 +1,8 @@ App( - appid="onewire_cli", + appid="cli_onewire", targets=["f7"], apptype=FlipperAppType.PLUGIN, - entry_point="onewire_cli_plugin_ep", + entry_point="cli_onewire_ep", requires=["cli"], sources=["onewire_cli.c"], ) - -App( - appid="onewire_start", - apptype=FlipperAppType.STARTUP, - entry_point="onewire_on_system_start", - sources=["onewire_start.c"], - order=60, -) diff --git a/applications/main/onewire/onewire_cli.c b/applications/main/onewire/onewire_cli.c index 4ee81a462..193de76e4 100644 --- a/applications/main/onewire/onewire_cli.c +++ b/applications/main/onewire/onewire_cli.c @@ -1,7 +1,9 @@ #include #include -#include +#include +#include +#include #include #include @@ -11,16 +13,17 @@ static void onewire_cli_print_usage(void) { printf("onewire search\r\n"); } -static void onewire_cli_search(Cli* cli) { - UNUSED(cli); +static void onewire_cli_search(PipeSide* pipe) { + UNUSED(pipe); OneWireHost* onewire = onewire_host_alloc(&gpio_ibutton); + Power* power = furi_record_open(RECORD_POWER); uint8_t address[8]; bool done = false; printf("Search started\r\n"); onewire_host_start(onewire); - if(!furi_hal_power_is_otg_enabled()) furi_hal_power_enable_otg(); + power_enable_otg(power, true); while(!done) { if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) { @@ -37,11 +40,13 @@ static void onewire_cli_search(Cli* cli) { furi_delay_ms(100); } - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); + power_enable_otg(power, false); + onewire_host_free(onewire); + furi_record_close(RECORD_POWER); } -void onewire_cli(Cli* cli, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -53,21 +58,10 @@ void onewire_cli(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "search") == 0) { - onewire_cli_search(cli); + onewire_cli_search(pipe); } furi_string_free(cmd); } -#include -#include - -static const FlipperAppPluginDescriptor plugin_descriptor = { - .appid = CLI_PLUGIN_APP_ID, - .ep_api_version = CLI_PLUGIN_API_VERSION, - .entry_point = &onewire_cli, -}; - -const FlipperAppPluginDescriptor* onewire_cli_plugin_ep(void) { - return &plugin_descriptor; -} +CLI_COMMAND_INTERFACE(onewire, execute, CliCommandFlagDefault, 1024, CLI_APPID); diff --git a/applications/main/onewire/onewire_start.c b/applications/main/onewire/onewire_start.c deleted file mode 100644 index 219335411..000000000 --- a/applications/main/onewire/onewire_start.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -static void onewire_cli_wrapper(Cli* cli, FuriString* args, void* context) { - cli_plugin_wrapper("onewire", cli, args, context); -} - -void onewire_on_system_start(void) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli_wrapper, cli); - furi_record_close(RECORD_CLI); -} diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 6ec301247..19885bd12 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -11,7 +11,6 @@ App( "dialogs", ], provides=[ - "subghz_start", "subghz_load_dangerous_settings", ], icon="A_Sub1ghz_14", @@ -24,12 +23,12 @@ App( ) App( - appid="subghz_start", + appid="cli_subghz", targets=["f7"], - apptype=FlipperAppType.STARTUP, - entry_point="subghz_on_system_start", - requires=["subghz"], - order=40, + apptype=FlipperAppType.PLUGIN, + entry_point="cli_subghz_ep", + requires=["cli"], + sources=["subghz_cli.c", "helpers/subghz_chat.c"], ) App( diff --git a/applications/main/subghz/helpers/subghz_chat.c b/applications/main/subghz/helpers/subghz_chat.c index b8124026f..8ce9a72d0 100644 --- a/applications/main/subghz/helpers/subghz_chat.c +++ b/applications/main/subghz/helpers/subghz_chat.c @@ -1,5 +1,6 @@ #include "subghz_chat.h" #include +#include #define TAG "SubGhzChat" @@ -14,7 +15,7 @@ struct SubGhzChatWorker { FuriMessageQueue* event_queue; uint32_t last_time_rx_data; - Cli* cli; + PipeSide* pipe; }; /** Worker thread @@ -30,7 +31,7 @@ static int32_t subghz_chat_worker_thread(void* context) { event.event = SubGhzChatEventUserEntrance; furi_message_queue_put(instance->event_queue, &event, 0); while(instance->worker_running) { - if(cli_read_timeout(instance->cli, (uint8_t*)&c, 1, 1000) == 1) { + if(pipe_receive(instance->pipe, (uint8_t*)&c, 1) == 1) { event.event = SubGhzChatEventInputData; event.c = c; furi_message_queue_put(instance->event_queue, &event, FuriWaitForever); @@ -55,10 +56,10 @@ static void subghz_chat_worker_update_rx_event_chat(void* context) { furi_message_queue_put(instance->event_queue, &event, FuriWaitForever); } -SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli) { +SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe) { SubGhzChatWorker* instance = malloc(sizeof(SubGhzChatWorker)); - instance->cli = cli; + instance->pipe = pipe; instance->thread = furi_thread_alloc_ex("SubGhzChat", 2048, subghz_chat_worker_thread, instance); diff --git a/applications/main/subghz/helpers/subghz_chat.h b/applications/main/subghz/helpers/subghz_chat.h index 2c454b75d..25fce0ecf 100644 --- a/applications/main/subghz/helpers/subghz_chat.h +++ b/applications/main/subghz/helpers/subghz_chat.h @@ -1,7 +1,7 @@ #pragma once #include "../subghz_i.h" #include -#include +#include typedef struct SubGhzChatWorker SubGhzChatWorker; @@ -19,7 +19,7 @@ typedef struct { char c; } SubGhzChatEvent; -SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli); +SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe); void subghz_chat_worker_free(SubGhzChatWorker* instance); bool subghz_chat_worker_start( SubGhzChatWorker* instance, diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index e84c4788d..bd8dee161 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -122,6 +122,7 @@ typedef enum { SetTypeBETT_433, SetTypeGangQi_433, SetTypeHollarm_433, + SetTypeReversRB2_433, SetTypeMarantec24_868, SetTypeLinear_300_00, // SetTypeNeroSketch, //Deleted in OFW diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index a79f5dbea..63b892401 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -384,33 +384,14 @@ bool subghz_txrx_gen_secplus_v1_protocol( } void subghz_txrx_gen_serial_gangqi(uint64_t* result_key) { - uint64_t randkey; - uint64_t only_required_bytes; - uint16_t sum_of_3bytes; - uint8_t xorbytes; + uint64_t randkey = (uint64_t)rand(); + uint16_t serial = (uint16_t)((randkey) & 0xFFFF); + uint8_t const_and_button = (uint8_t)(0xD0 | 0xD); + uint8_t serial_high = (uint8_t)(serial >> 8); + uint8_t serial_low = (uint8_t)(serial & 0xFF); + uint8_t bytesum = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button); - do { - randkey = (uint64_t)rand(); - only_required_bytes = (randkey & 0x0FFFF0000) | 0x200000000; - sum_of_3bytes = ((only_required_bytes >> 32) & 0xFF) + - ((only_required_bytes >> 24) & 0xFF) + - ((only_required_bytes >> 16) & 0xFF); - xorbytes = ((only_required_bytes >> 32) & 0xFF) ^ ((only_required_bytes >> 24) & 0xFF) ^ - ((only_required_bytes >> 16) & 0xFF); - } while( - !((((!(sum_of_3bytes & 0x3)) && ((0xB < sum_of_3bytes) && (sum_of_3bytes < 0x141))) && - ((((only_required_bytes >> 32) & 0xFF) == 0x2) || - (((only_required_bytes >> 32) & 0xFF) == 0x3))) && - ((((xorbytes == 0xBA) || (xorbytes == 0xE2)) || - ((xorbytes == 0x3A) || (xorbytes == 0xF2))) || - (xorbytes == 0xB2)))); - - // Serial 01 button 01 - uint64_t new_key = only_required_bytes | (0b01 << 14) | (0xD << 10) | (0b01 << 8); - - uint8_t crc = -0xD7 - ((new_key >> 32) & 0xFF) - ((new_key >> 24) & 0xFF) - - ((new_key >> 16) & 0xFF) - ((new_key >> 8) & 0xFF); - - // Add crc sum to the end - *result_key = (new_key | crc); + // Add bytesum to the end + // serial | const_and_button + *result_key = (serial << 18) | (const_and_button << 10) | (bytesum << 2); } diff --git a/applications/main/subghz/resources/subghz/assets/setting_user.example b/applications/main/subghz/resources/subghz/assets/setting_user.example index 5034659be..9f74ee379 100644 --- a/applications/main/subghz/resources/subghz/assets/setting_user.example +++ b/applications/main/subghz/resources/subghz/assets/setting_user.example @@ -20,6 +20,19 @@ Version: 1 # Custom preset # format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register +#Custom_preset_name: FM95 +#Custom_preset_module: CC1101 +#Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00 + +#2-FSK 200khz BW / 135kHz Filter/ 15.86Khz Deviation + Ramping +#Custom_preset_name: FM15k +#Custom_preset_module: CC1101 +#Custom_preset_data: 02 0D 03 47 08 32 0B 06 15 32 14 00 13 00 12 00 11 32 10 A7 18 18 19 1D 1D 92 1C 00 1B 04 20 FB 22 17 21 B6 00 00 00 12 0E 34 60 C5 C1 C0 + +#Custom_preset_name: Pagers +#Custom_preset_module: CC1101 +#Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 64 11 93 12 0C 13 02 14 00 15 15 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00 + #Custom_preset_name: AM_1 #Custom_preset_module: CC1101 #Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 974a2f564..8e808618b 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -26,7 +26,7 @@ const char* const debug_pin_text[DEBUG_P_COUNT] = { "17(1W)", }; -#define DEBUG_COUNTER_COUNT 13 +#define DEBUG_COUNTER_COUNT 16 const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = { "+1", "+2", @@ -34,21 +34,26 @@ const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = { "+4", "+5", "+10", - "0", + "+50", + "OVFL", + "No", "-1", "-2", "-3", "-4", "-5", "-10", + "-50", }; -const uint32_t debug_counter_val[DEBUG_COUNTER_COUNT] = { +const int32_t debug_counter_val[DEBUG_COUNTER_COUNT] = { 1, 2, 3, 4, 5, 10, + 50, + 65535, 0, -1, -2, @@ -56,6 +61,7 @@ const uint32_t debug_counter_val[DEBUG_COUNTER_COUNT] = { -4, -5, -10, + -50, }; static void subghz_scene_radio_settings_set_device(VariableItem* item) { @@ -118,7 +124,7 @@ void subghz_scene_radio_settings_on_enter(void* context) { SubGhz* subghz = context; VariableItemList* variable_item_list = subghz->variable_item_list; - uint8_t value_index; + int32_t value_index; VariableItem* item; uint8_t value_count_device = RADIO_DEVICE_COUNT; @@ -152,7 +158,7 @@ void subghz_scene_radio_settings_on_enter(void* context) { furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) ? DEBUG_COUNTER_COUNT : 3, subghz_scene_receiver_config_set_debug_counter, subghz); - value_index = value_index_uint32( + value_index = value_index_int32( furi_hal_subghz_get_rolling_counter_mult(), debug_counter_val, furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) ? DEBUG_COUNTER_COUNT : 3); diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 6c3e44894..3155f9f1e 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -67,6 +67,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypePricenton433] = "Princeton 433MHz", [SetTypeGangQi_433] = "GangQi 433MHz", [SetTypeHollarm_433] = "Hollarm 433MHz", + [SetTypeReversRB2_433] = "Revers RB2 433MHz", [SetTypeMarantec24_868] = "Marantec24 868MHz", [SetTypeBETT_433] = "BETT 433MHz", [SetTypeLinear_300_00] = "Linear 300MHz", @@ -337,6 +338,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .data.bits = 42, .data.te = 0}; break; + case SetTypeReversRB2_433: + gen_info = (GenInfo){ + .type = GenData, + .mod = "AM650", + .freq = 433920000, + .data.name = SUBGHZ_PROTOCOL_REVERSRB2_NAME, // 64bits no buttons + .data.key = (key & 0x00000FFFFFFFF000) | 0xFFFFF00000000000 | 0x0000000000000A00, + .data.bits = 64, + .data.te = 0}; + break; case SetTypeMarantec24_868: gen_info = (GenInfo){ .type = GenData, diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index 07c7b6041..ebd69059f 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -79,7 +79,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { subghz_txrx_stop(subghz->txrx); if(subghz_custom_btn_get() != SUBGHZ_CUSTOM_BTN_OK) { subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK); - int8_t tmp_counter = furi_hal_subghz_get_rolling_counter_mult(); + int32_t tmp_counter = furi_hal_subghz_get_rolling_counter_mult(); furi_hal_subghz_set_rolling_counter_mult(0); // Calling restore! subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index cb4fc5504..83e68229b 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -56,11 +56,11 @@ static void subghz_rpc_command_callback(const RpcAppSystemEvent* event, void* co rpc_system_app_confirm(subghz->rpc_ctx, false); } } - +/* static void subghz_load_custom_presets(SubGhzSetting* setting) { furi_assert(setting); - const char* presets[][2] = { + const char* presets[3][2] = { {"FM95", "02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"}, @@ -88,6 +88,7 @@ static void subghz_load_custom_presets(SubGhzSetting* setting) { subghz_setting_customs_presets_to_log(setting); #endif } +*/ SubGhz* subghz_alloc(bool alloc_for_tx_only) { SubGhz* subghz = malloc(sizeof(SubGhz)); @@ -192,14 +193,14 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { //init TxRx & Protocol & History & KeyBoard subghz_unlock(subghz); - SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); + //SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); - subghz_load_custom_presets(setting); + //subghz_load_custom_presets(setting); // Load last used values for Read, Read RAW, etc. or default subghz->last_settings = subghz_last_settings_alloc(); - size_t preset_count = subghz_setting_get_preset_count(setting); - subghz_last_settings_load(subghz->last_settings, preset_count); + //size_t preset_count = subghz_setting_get_preset_count(setting); + subghz_last_settings_load(subghz->last_settings, 0); // Set LED and Amp GPIO control state furi_hal_subghz_set_ext_leds_and_amp(subghz->last_settings->leds_and_amp); diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index e23b9c4db..947920964 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include @@ -16,6 +18,7 @@ #include #include +#include #include "helpers/subghz_chat.h" @@ -73,7 +76,7 @@ static SubGhzEnvironment* subghz_cli_environment_init(void) { return environment; } -void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_tx_carrier(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -103,7 +106,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { if(furi_hal_subghz_tx()) { printf("Transmitting at frequency %lu Hz\r\n", frequency); printf("Press CTRL+C to stop\r\n"); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); } } else { @@ -116,7 +119,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_power_suppress_charge_exit(); } -void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_rx_carrier(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -144,7 +147,7 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_rx(); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); printf("RSSI: %03.1fdbm\r", (double)furi_hal_subghz_get_rssi()); fflush(stdout); @@ -177,7 +180,7 @@ static const SubGhzDevice* subghz_cli_command_get_device(uint32_t* device_ind) { return device; } -void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_tx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; uint32_t key = 0x0074BADE; @@ -247,7 +250,9 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { furi_hal_power_suppress_charge_enter(); if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { - while(!(subghz_devices_is_async_complete_tx(device) || cli_cmd_interrupt_received(cli))) { + while( + !(subghz_devices_is_async_complete_tx(device) || + cli_is_pipe_broken_or_is_etx_next_char(pipe))) { printf("."); fflush(stdout); furi_delay_ms(333); @@ -303,7 +308,7 @@ static void subghz_cli_command_rx_callback( furi_string_free(text); } -void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_rx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT @@ -359,7 +364,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { frequency, device_ind); LevelDuration level_duration; - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { int ret = furi_stream_buffer_receive( instance->stream, &level_duration, sizeof(LevelDuration), 10); if(ret == sizeof(LevelDuration)) { @@ -392,7 +397,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { free(instance); } -void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_rx_raw(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -430,7 +435,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency); LevelDuration level_duration; size_t counter = 0; - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { int ret = furi_stream_buffer_receive( instance->stream, &level_duration, sizeof(LevelDuration), 10); if(ret == 0) { @@ -466,7 +471,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { free(instance); } -void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_decode_raw(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* file_name = furi_string_alloc(); furi_string_set(file_name, EXT_PATH("subghz/test.sub")); @@ -534,7 +539,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { furi_string_get_cstr(file_name)); LevelDuration level_duration; - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_us(500); //you need to have time to read from the file from the SD card level_duration = subghz_file_encoder_worker_get_level_duration(file_worker_encoder); if(!level_duration_is_reset(level_duration)) { @@ -579,7 +584,7 @@ static FuriHalSubGhzPreset subghz_cli_get_preset_name(const char* preset_name) { return preset; } -void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) { // -V524 +void subghz_cli_command_tx_from_file(PipeSide* pipe, FuriString* args, void* context) { // -V524 UNUSED(context); FuriString* file_name; file_name = furi_string_alloc(); @@ -617,7 +622,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) if(furi_string_size(args)) { char* args_cstr = (char*)furi_string_get_cstr(args); StrintParseError parse_err = StrintParseNoError; - parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &repeat, 10); parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); if(parse_err) { cli_print_usage( @@ -783,7 +788,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { while( !(subghz_devices_is_async_complete_tx(device) || - cli_cmd_interrupt_received(cli))) { + cli_is_pipe_broken_or_is_etx_next_char(pipe))) { printf("."); fflush(stdout); furi_delay_ms(333); @@ -797,11 +802,11 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { subghz_transmitter_stop(transmitter); repeat--; - if(!cli_cmd_interrupt_received(cli) && repeat) + if(!cli_is_pipe_broken_or_is_etx_next_char(pipe) && repeat) subghz_transmitter_deserialize(transmitter, fff_data_raw); } - } while(!cli_cmd_interrupt_received(cli) && + } while(!cli_is_pipe_broken_or_is_etx_next_char(pipe) && (repeat && !strcmp(furi_string_get_cstr(temp_str), "RAW"))); subghz_devices_sleep(device); @@ -818,7 +823,6 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) subghz_devices_deinit(); // Reset custom settings subghz_environment_reset_keeloq(environment); - faac_slh_reset_prog_mode(); subghz_custom_btns_reset(); // Free environment subghz_environment_free(environment); @@ -851,8 +855,8 @@ static void subghz_cli_command_print_usage(void) { } } -static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) { - UNUSED(cli); +static void subghz_cli_command_encrypt_keeloq(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); uint8_t iv[16]; FuriString* source = furi_string_alloc(); @@ -892,8 +896,8 @@ static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) { furi_string_free(source); } -static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { - UNUSED(cli); +static void subghz_cli_command_encrypt_raw(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); uint8_t iv[16]; FuriString* source = furi_string_alloc(); @@ -927,7 +931,7 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { furi_string_free(source); } -static void subghz_cli_command_chat(Cli* cli, FuriString* args) { +static void subghz_cli_command_chat(PipeSide* pipe, FuriString* args) { uint32_t frequency = 433920000; uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT @@ -963,7 +967,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { return; } - SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(cli); + SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(pipe); if(!subghz_chat_worker_start(subghz_chat, device, frequency)) { printf("Startup error SubGhzChatWorker\r\n"); @@ -1000,13 +1004,12 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { chat_event = subghz_chat_worker_get_event_chat(subghz_chat); switch(chat_event.event) { case SubGhzChatEventInputData: - if(chat_event.c == CliSymbolAsciiETX) { + if(chat_event.c == CliKeyETX) { printf("\r\n"); chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); break; - } else if( - (chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) { + } else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) { size_t len = furi_string_utf8_length(input); if(len > furi_string_utf8_length(name)) { printf("%s", "\e[D\e[1P"); @@ -1028,7 +1031,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { } furi_string_set(input, sysmsg); } - } else if(chat_event.c == CliSymbolAsciiCR) { + } else if(chat_event.c == CliKeyCR) { printf("\r\n"); furi_string_push_back(input, '\r'); furi_string_push_back(input, '\n'); @@ -1042,7 +1045,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { furi_string_printf(input, "%s", furi_string_get_cstr(name)); printf("%s", furi_string_get_cstr(input)); fflush(stdout); - } else if(chat_event.c == CliSymbolAsciiLF) { + } else if(chat_event.c == CliKeyLF) { //cut out the symbol \n } else { putc(chat_event.c, stdout); @@ -1096,7 +1099,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { break; } } - if(!cli_is_connected(cli)) { + if(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { printf("\r\n"); chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); @@ -1121,8 +1124,9 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { printf("\r\nExit chat\r\n"); } -static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { - FuriString* cmd = furi_string_alloc(); +static void execute(PipeSide* pipe, FuriString* args, void* context) { + FuriString* cmd; + cmd = furi_string_alloc(); do { if(!args_read_string_and_trim(args, cmd)) { @@ -1131,53 +1135,53 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "chat") == 0) { - subghz_cli_command_chat(cli, args); + subghz_cli_command_chat(pipe, args); break; } if(furi_string_cmp_str(cmd, "tx") == 0) { - subghz_cli_command_tx(cli, args, context); + subghz_cli_command_tx(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "rx") == 0) { - subghz_cli_command_rx(cli, args, context); + subghz_cli_command_rx(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "rx_raw") == 0) { - subghz_cli_command_rx_raw(cli, args, context); + subghz_cli_command_rx_raw(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "decode_raw") == 0) { - subghz_cli_command_decode_raw(cli, args, context); + subghz_cli_command_decode_raw(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "tx_from_file") == 0) { - subghz_cli_command_tx_from_file(cli, args, context); + subghz_cli_command_tx_from_file(pipe, args, context); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "encrypt_keeloq") == 0) { - subghz_cli_command_encrypt_keeloq(cli, args); + subghz_cli_command_encrypt_keeloq(pipe, args); break; } if(furi_string_cmp_str(cmd, "encrypt_raw") == 0) { - subghz_cli_command_encrypt_raw(cli, args); + subghz_cli_command_encrypt_raw(pipe, args); break; } if(furi_string_cmp_str(cmd, "tx_carrier") == 0) { - subghz_cli_command_tx_carrier(cli, args, context); + subghz_cli_command_tx_carrier(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "rx_carrier") == 0) { - subghz_cli_command_rx_carrier(cli, args, context); + subghz_cli_command_rx_carrier(pipe, args, context); break; } } @@ -1188,14 +1192,4 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -void subghz_on_system_start(void) { -#ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - - cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command, NULL); - - furi_record_close(RECORD_CLI); -#else - UNUSED(subghz_cli_command); -#endif -} +CLI_COMMAND_INTERFACE(subghz, execute, CliCommandFlagDefault, 2048, CLI_APPID); diff --git a/applications/main/subghz/subghz_cli.h b/applications/main/subghz/subghz_cli.h index f6388218f..275135581 100644 --- a/applications/main/subghz/subghz_cli.h +++ b/applications/main/subghz/subghz_cli.h @@ -1,5 +1,3 @@ #pragma once -#include - void subghz_on_system_start(void); diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index 174a15a5b..6ffdc858e 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -31,6 +31,7 @@ void subghz_last_settings_free(SubGhzLastSettings* instance) { } void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count) { + UNUSED(preset_count); furi_assert(instance); // Default values (all others set to 0, if read from file fails these are used) @@ -148,7 +149,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count instance->frequency = SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY; } - if(instance->preset_index > (uint32_t)preset_count - 1) { + if(instance->preset_index > 3) { instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; } } diff --git a/applications/services/application.fam b/applications/services/application.fam index 90631408a..1b69d2388 100644 --- a/applications/services/application.fam +++ b/applications/services/application.fam @@ -3,6 +3,7 @@ App( name="Basic services", apptype=FlipperAppType.METAPACKAGE, provides=[ + "cli_vcp", "crypto_start", "rpc_start", "expansion_start", diff --git a/applications/services/bt/application.fam b/applications/services/bt/application.fam index 2d2840e3a..0e4cc918f 100644 --- a/applications/services/bt/application.fam +++ b/applications/services/bt/application.fam @@ -21,5 +21,5 @@ App( appid="bt_start", apptype=FlipperAppType.STARTUP, entry_point="bt_on_system_start", - order=70, + order=40, ) diff --git a/applications/services/bt/bt_cli.c b/applications/services/bt/bt_cli.c index 7505c424d..d81bc0505 100644 --- a/applications/services/bt/bt_cli.c +++ b/applications/services/bt/bt_cli.c @@ -1,15 +1,17 @@ #include #include -#include #include +#include +#include +#include #include #include "bt_settings.h" #include "bt_service/bt.h" #include -static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void bt_cli_command_hci_info(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); FuriString* buffer; @@ -19,7 +21,7 @@ static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) { furi_string_free(buffer); } -static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_carrier_tx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int power = 0; @@ -41,7 +43,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) printf("Press CTRL+C to stop\r\n"); furi_hal_bt_start_tone_tx(channel, 0x19 + power); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); } furi_hal_bt_stop_tone_tx(); @@ -51,7 +53,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) } while(false); } -static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_carrier_rx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; @@ -69,7 +71,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) furi_hal_bt_start_packet_rx(channel, 1); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); printf("RSSI: %6.1f dB\r", (double)furi_hal_bt_get_rssi()); fflush(stdout); @@ -82,7 +84,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) } while(false); } -static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_packet_tx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int pattern = 0; @@ -119,7 +121,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) printf("Press CTRL+C to stop\r\n"); furi_hal_bt_start_packet_tx(channel, pattern, datarate); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); } furi_hal_bt_stop_packet_test(); @@ -130,7 +132,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) } while(false); } -static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_packet_rx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int datarate = 1; @@ -152,7 +154,7 @@ static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context) printf("Press CTRL+C to stop\r\n"); furi_hal_bt_start_packet_rx(channel, datarate); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); printf("RSSI: %03.1f dB\r", (double)furi_hal_bt_get_rssi()); fflush(stdout); @@ -179,7 +181,7 @@ static void bt_cli_print_usage(void) { } } -static void bt_cli(Cli* cli, FuriString* args, void* context) { +static void bt_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); furi_record_open(RECORD_BT); @@ -194,24 +196,24 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) { break; } if(furi_string_cmp_str(cmd, "hci_info") == 0) { - bt_cli_command_hci_info(cli, args, NULL); + bt_cli_command_hci_info(pipe, args, NULL); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) { if(furi_string_cmp_str(cmd, "tx_carrier") == 0) { - bt_cli_command_carrier_tx(cli, args, NULL); + bt_cli_command_carrier_tx(pipe, args, NULL); break; } if(furi_string_cmp_str(cmd, "rx_carrier") == 0) { - bt_cli_command_carrier_rx(cli, args, NULL); + bt_cli_command_carrier_rx(pipe, args, NULL); break; } if(furi_string_cmp_str(cmd, "tx_packet") == 0) { - bt_cli_command_packet_tx(cli, args, NULL); + bt_cli_command_packet_tx(pipe, args, NULL); break; } if(furi_string_cmp_str(cmd, "rx_packet") == 0) { - bt_cli_command_packet_rx(cli, args, NULL); + bt_cli_command_packet_rx(pipe, args, NULL); break; } } @@ -229,8 +231,8 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) { void bt_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, RECORD_BT, CliCommandFlagDefault, bt_cli, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command(registry, "bt", CliCommandFlagDefault, bt_cli, NULL); furi_record_close(RECORD_CLI); #else UNUSED(bt_cli); diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index d04490502..d2e5ce2a0 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -527,19 +527,6 @@ static void bt_init_keys_settings(Bt* bt) { bt_handle_reload_keys_settings(bt); } -bool bt_remote_rssi(Bt* bt, uint8_t* rssi) { - furi_assert(bt); - - uint8_t rssi_val; - uint32_t since = furi_hal_bt_get_conn_rssi(&rssi_val); - - if(since == 0) return false; - - *rssi = rssi_val; - - return true; -} - int32_t bt_srv(void* p) { UNUSED(p); Bt* bt = bt_alloc(); diff --git a/applications/services/bt/bt_service/bt.h b/applications/services/bt/bt_service/bt.h index d49b0b3ba..403f4eb88 100644 --- a/applications/services/bt/bt_service/bt.h +++ b/applications/services/bt/bt_service/bt.h @@ -84,21 +84,6 @@ void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path); */ void bt_keys_storage_set_default_path(Bt* bt); -bool bt_remote_rssi(Bt* bt, uint8_t* rssi); - -/** - * - * (Probably bad) way of opening the RPC connection, everywhereTM -*/ - -void bt_open_rpc_connection(Bt* bt); - -/** - * - * Closing the RPC connection, everywhereTM -*/ -void bt_close_rpc_connection(Bt* bt); - #ifdef __cplusplus } #endif diff --git a/applications/services/bt/bt_service/bt_i.h b/applications/services/bt/bt_service/bt_i.h index 58a60e275..fa2a0740d 100644 --- a/applications/services/bt/bt_service/bt_i.h +++ b/applications/services/bt/bt_service/bt_i.h @@ -91,3 +91,15 @@ struct Bt { uint32_t pin; bool suppress_pin_screen; }; + +/** Open a new RPC connection + * + * @param bt Bt instance + */ +void bt_open_rpc_connection(Bt* bt); + +/** Close the active RPC connection + * + * @param bt Bt instance + */ +void bt_close_rpc_connection(Bt* bt); diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index 7a57bb607..b305fb6b0 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -1,10 +1,51 @@ App( appid="cli", - name="CliSrv", - apptype=FlipperAppType.SERVICE, - entry_point="cli_srv", + apptype=FlipperAppType.STARTUP, + entry_point="cli_on_system_start", cdefines=["SRV_CLI"], - stack_size=4 * 1024, - order=30, - sdk_headers=["cli.h", "cli_vcp.h"], + sources=[ + "cli_command_gpio.c", + "cli_main_commands.c", + "cli_main_shell.c", + ], + # This STARTUP has to be processed before those that depend on the "cli" record. + # "cli" used to be a SERVICE, but it's been converted into a STARTUP in order to + # reduce RAM usage. The "block until record has been created" mechanism + # unfortunately leads to a deadlock if the STARTUPs are processed sequentially. + order=0, +) + +App( + appid="cli_vcp", + name="CliVcpSrv", + apptype=FlipperAppType.SERVICE, + entry_point="cli_vcp_srv", + stack_size=1024, + order=10, + sdk_headers=["cli_vcp.h", "cli.h"], + sources=["cli_vcp.c"], +) + +App( + appid="cli_hello_world", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_hello_world_ep", + requires=["cli"], + sources=["commands/hello_world.c"], +) + +App( + appid="cli_neofetch", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_neofetch_ep", + requires=["cli"], + sources=["commands/neofetch.c"], +) + +App( + appid="cli_subshell_demo", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_subshell_demo_ep", + requires=["cli"], + sources=["commands/subshell_demo.c"], ) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c deleted file mode 100644 index f9b556085..000000000 --- a/applications/services/cli/cli.c +++ /dev/null @@ -1,504 +0,0 @@ -#include "cli_i.h" -#include "cli_commands.h" -#include "cli_vcp.h" -#include -#include - -#include -#include -#include - -#define TAG "CliSrv" - -#define CLI_INPUT_LEN_LIMIT 256 - -Cli* cli_alloc(void) { - Cli* cli = malloc(sizeof(Cli)); - - CliCommandTree_init(cli->commands); - - cli->last_line = furi_string_alloc(); - cli->line = furi_string_alloc(); - - cli->session = NULL; - - cli->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - - cli->idle_sem = furi_semaphore_alloc(1, 0); - - return cli; -} - -void cli_putc(Cli* cli, char c) { - furi_check(cli); - if(cli->session != NULL) { - cli->session->tx((uint8_t*)&c, 1); - } -} - -char cli_getc(Cli* cli) { - furi_check(cli); - char c = 0; - if(cli->session != NULL) { - if(cli->session->rx((uint8_t*)&c, 1, FuriWaitForever) == 0) { - cli_reset(cli); - furi_delay_tick(10); - } - } else { - cli_reset(cli); - furi_delay_tick(10); - } - return c; -} - -void cli_write(Cli* cli, const uint8_t* buffer, size_t size) { - furi_check(cli); - if(cli->session != NULL) { - cli->session->tx(buffer, size); - } -} - -size_t cli_read(Cli* cli, uint8_t* buffer, size_t size) { - furi_check(cli); - if(cli->session != NULL) { - return cli->session->rx(buffer, size, FuriWaitForever); - } else { - return 0; - } -} - -size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout) { - furi_check(cli); - if(cli->session != NULL) { - return cli->session->rx(buffer, size, timeout); - } else { - return 0; - } -} - -bool cli_is_connected(Cli* cli) { - furi_check(cli); - if(cli->session != NULL) { - return cli->session->is_connected(); - } - return false; -} - -bool cli_cmd_interrupt_received(Cli* cli) { - furi_check(cli); - char c = '\0'; - if(cli_is_connected(cli)) { - if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) { - return c == CliSymbolAsciiETX; - } - } else { - return true; - } - return false; -} - -void cli_print_usage(const char* cmd, const char* usage, const char* arg) { - furi_check(cmd); - furi_check(arg); - furi_check(usage); - - printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage); -} - -void cli_motd(void) { - printf("\r\n" - " _.-------.._ -,\r\n" - " .-\"```\"--..,,_/ /`-, -, \\ \r\n" - " .:\" /:/ /'\\ \\ ,_..., `. | |\r\n" - " / ,----/:/ /`\\ _\\~`_-\"` _;\r\n" - " ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n" - " | | | 0 | | .-' ,/` /\r\n" - " | ,..\\ \\ ,.-\"` ,/` /\r\n" - " ; : `/`\"\"\\` ,/--==,/-----,\r\n" - " | `-...| -.___-Z:_______J...---;\r\n" - " : ` _-'\r\n" - " _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n" - "| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n" - "| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n" - "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" - "\r\n" - "Welcome to Flipper Zero Command Line Interface!\r\n" - "Read the manual: https://docs.flipper.net/development/cli\r\n" - "Run `help` or `?` to list available commands\r\n" - "\r\n"); - - const Version* firmware_version = furi_hal_version_get_firmware_version(); - if(firmware_version) { - printf( - "Firmware version: %s %s (%s%s built on %s)\r\n", - version_get_gitbranch(firmware_version), - version_get_version(firmware_version), - version_get_githash(firmware_version), - version_get_dirty_flag(firmware_version) ? "-dirty" : "", - version_get_builddate(firmware_version)); - } -} - -void cli_nl(Cli* cli) { - UNUSED(cli); - printf("\r\n"); -} - -void cli_prompt(Cli* cli) { - UNUSED(cli); - printf("\r\n>: %s", furi_string_get_cstr(cli->line)); - fflush(stdout); -} - -void cli_reset(Cli* cli) { - // cli->last_line is cleared and cli->line's buffer moved to cli->last_line - furi_string_move(cli->last_line, cli->line); - // Reiniting cli->line - cli->line = furi_string_alloc(); - cli->cursor_position = 0; -} - -static void cli_handle_backspace(Cli* cli) { - if(cli->cursor_position > 0) { - furi_assert(furi_string_size(cli->line) > 0); - // Other side - printf("\e[D\e[1P"); - fflush(stdout); - // Our side - furi_string_replace_at(cli->line, cli->cursor_position - 1, 1, ""); - - cli->cursor_position--; - } else { - cli_putc(cli, CliSymbolAsciiBell); - } -} - -static void cli_normalize_line(Cli* cli) { - furi_string_trim(cli->line); - cli->cursor_position = furi_string_size(cli->line); -} - -static void cli_execute_command(Cli* cli, CliCommand* command, FuriString* args) { - if(!(command->flags & CliCommandFlagInsomniaSafe)) { - furi_hal_power_insomnia_enter(); - } - - // Ensure that we running alone - if(!(command->flags & CliCommandFlagParallelSafe)) { - Loader* loader = furi_record_open(RECORD_LOADER); - bool safety_lock = loader_lock(loader); - if(safety_lock) { - // Execute command - command->callback(cli, args, command->context); - loader_unlock(loader); - } else { - printf("Other application is running, close it first"); - } - furi_record_close(RECORD_LOADER); - } else { - // Execute command - command->callback(cli, args, command->context); - } - - if(!(command->flags & CliCommandFlagInsomniaSafe)) { - furi_hal_power_insomnia_exit(); - } -} - -static void cli_handle_enter(Cli* cli) { - cli_normalize_line(cli); - - if(furi_string_size(cli->line) == 0) { - cli_prompt(cli); - return; - } - - // Command and args container - FuriString* command; - command = furi_string_alloc(); - FuriString* args; - args = furi_string_alloc(); - - // Split command and args - size_t ws = furi_string_search_char(cli->line, ' '); - if(ws == FURI_STRING_FAILURE) { - furi_string_set(command, cli->line); - } else { - furi_string_set_n(command, cli->line, 0, ws); - furi_string_set_n(args, cli->line, ws, furi_string_size(cli->line)); - furi_string_trim(args); - } - - // Search for command - furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - CliCommand* cli_command_ptr = CliCommandTree_get(cli->commands, command); - - if(cli_command_ptr) { //-V547 - CliCommand cli_command; - memcpy(&cli_command, cli_command_ptr, sizeof(CliCommand)); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - cli_nl(cli); - cli_execute_command(cli, &cli_command, args); - } else { - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - cli_nl(cli); - printf( - "`%s` command not found, use `help` or `?` to list all available commands", - furi_string_get_cstr(command)); - cli_putc(cli, CliSymbolAsciiBell); - } - - cli_reset(cli); - cli_prompt(cli); - - // Cleanup command and args - furi_string_free(command); - furi_string_free(args); -} - -static void cli_handle_autocomplete(Cli* cli) { - cli_normalize_line(cli); - - if(furi_string_size(cli->line) == 0) { - return; - } - - cli_nl(cli); - - // Prepare common base for autocomplete - FuriString* common; - common = furi_string_alloc(); - // Iterate throw commands - for - M_EACH(cli_command, cli->commands, CliCommandTree_t) { - // Process only if starts with line buffer - if(furi_string_start_with(*cli_command->key_ptr, cli->line)) { - // Show autocomplete option - printf("%s\r\n", furi_string_get_cstr(*cli_command->key_ptr)); - // Process common base for autocomplete - if(furi_string_size(common) > 0) { - // Choose shortest string - const size_t key_size = furi_string_size(*cli_command->key_ptr); - const size_t common_size = furi_string_size(common); - const size_t min_size = key_size > common_size ? common_size : key_size; - size_t i = 0; - while(i < min_size) { - // Stop when do not match - if(furi_string_get_char(*cli_command->key_ptr, i) != - furi_string_get_char(common, i)) { - break; - } - i++; - } - // Cut right part if any - furi_string_left(common, i); - } else { - // Start with something - furi_string_set(common, *cli_command->key_ptr); - } - } - } - // Replace line buffer if autocomplete better - if(furi_string_size(common) > furi_string_size(cli->line)) { - furi_string_set(cli->line, common); - cli->cursor_position = furi_string_size(cli->line); - } - // Cleanup - furi_string_free(common); - // Show prompt - cli_prompt(cli); -} - -static void cli_handle_escape(Cli* cli, char c) { - if(c == 'A') { - // Use previous command if line buffer is empty - if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) { - // Set line buffer and cursor position - furi_string_set(cli->line, cli->last_line); - cli->cursor_position = furi_string_size(cli->line); - // Show new line to user - printf("%s", furi_string_get_cstr(cli->line)); - } - } else if(c == 'B') { - } else if(c == 'C') { - if(cli->cursor_position < furi_string_size(cli->line)) { - cli->cursor_position++; - printf("\e[C"); - } - } else if(c == 'D') { - if(cli->cursor_position > 0) { - cli->cursor_position--; - printf("\e[D"); - } - } - fflush(stdout); -} - -void cli_process_input(Cli* cli) { - char in_chr = cli_getc(cli); - size_t rx_len; - - if(in_chr == CliSymbolAsciiTab) { - cli_handle_autocomplete(cli); - } else if(in_chr == CliSymbolAsciiSOH) { - furi_delay_ms(33); // We are too fast, Minicom is not ready yet - cli_motd(); - cli_prompt(cli); - } else if(in_chr == CliSymbolAsciiETX) { - cli_reset(cli); - cli_prompt(cli); - } else if(in_chr == CliSymbolAsciiEOT) { - cli_reset(cli); - } else if(in_chr == CliSymbolAsciiEsc) { - rx_len = cli_read(cli, (uint8_t*)&in_chr, 1); - if((rx_len > 0) && (in_chr == '[')) { - cli_read(cli, (uint8_t*)&in_chr, 1); - cli_handle_escape(cli, in_chr); - } else { - cli_putc(cli, CliSymbolAsciiBell); - } - } else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) { - cli_handle_backspace(cli); - } else if(in_chr == CliSymbolAsciiCR) { - cli_handle_enter(cli); - } else if( - (in_chr >= 0x20 && in_chr < 0x7F) && //-V560 - (furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) { - if(cli->cursor_position == furi_string_size(cli->line)) { - furi_string_push_back(cli->line, in_chr); - cli_putc(cli, in_chr); - } else { - // Insert character to line buffer - const char in_str[2] = {in_chr, 0}; - furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str); - - // Print character in replace mode - printf("\e[4h%c\e[4l", in_chr); - fflush(stdout); - } - cli->cursor_position++; - } else { - cli_putc(cli, CliSymbolAsciiBell); - } -} - -void cli_add_command( - Cli* cli, - const char* name, - CliCommandFlag flags, - CliCallback callback, - void* context) { - furi_check(cli); - FuriString* name_str; - name_str = furi_string_alloc_set(name); - furi_string_trim(name_str); - - size_t name_replace; - do { - name_replace = furi_string_replace(name_str, " ", "_"); - } while(name_replace != FURI_STRING_FAILURE); - - CliCommand c; - c.callback = callback; - c.context = context; - c.flags = flags; - - furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - CliCommandTree_set_at(cli->commands, name_str, c); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - - furi_string_free(name_str); -} - -void cli_delete_command(Cli* cli, const char* name) { - furi_check(cli); - FuriString* name_str; - name_str = furi_string_alloc_set(name); - furi_string_trim(name_str); - - size_t name_replace; - do { - name_replace = furi_string_replace(name_str, " ", "_"); - } while(name_replace != FURI_STRING_FAILURE); - - furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - CliCommandTree_erase(cli->commands, name_str); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - - furi_string_free(name_str); -} - -void cli_session_open(Cli* cli, void* session) { - furi_check(cli); - - furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - cli->session = session; - if(cli->session != NULL) { - cli->session->init(); - furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL); - } else { - furi_thread_set_stdout_callback(NULL, NULL); - } - furi_semaphore_release(cli->idle_sem); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); -} - -void cli_session_close(Cli* cli) { - furi_check(cli); - - furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - if(cli->session != NULL) { - cli->session->deinit(); - } - cli->session = NULL; - furi_thread_set_stdout_callback(NULL, NULL); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); -} - -int32_t cli_srv(void* p) { - UNUSED(p); - Cli* cli = cli_alloc(); - - // Init basic cli commands - cli_commands_init(cli); - - furi_record_create(RECORD_CLI, cli); - - if(cli->session != NULL) { - furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL); - } else { - furi_thread_set_stdout_callback(NULL, NULL); - } - - if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { - cli_session_open(cli, &cli_vcp); - } else { - FURI_LOG_W(TAG, "Skipping start in special boot mode"); - } - - while(1) { - if(cli->session != NULL) { - cli_process_input(cli); - } else { - furi_check(furi_semaphore_acquire(cli->idle_sem, FuriWaitForever) == FuriStatusOk); - } - } - - return 0; -} - -void cli_plugin_wrapper(const char* name, Cli* cli, FuriString* args, void* context) { - PluginManager* manager = - plugin_manager_alloc(CLI_PLUGIN_APP_ID, CLI_PLUGIN_API_VERSION, firmware_api_interface); - FuriString* path = - furi_string_alloc_printf(EXT_PATH("apps_data/cli/plugins/%s_cli.fal"), name); - PluginManagerError error = plugin_manager_load_single(manager, furi_string_get_cstr(path)); - if(error == PluginManagerErrorNone) { - const CliCallback handler = plugin_manager_get_ep(manager, 0); - handler(cli, args, context); - } else { - printf("CLI plugin failed (code %" PRIu16 "), update firmware or check logs\r\n", error); - } - furi_string_free(path); - plugin_manager_free(manager); -} diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index bb84670a7..ca787d3db 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -1,134 +1,13 @@ -/** - * @file cli.h - * Cli API - */ - #pragma once -#include -#ifdef __cplusplus -extern "C" { -#endif +/* + * Compatibility header for ease of porting existing apps. + * In short: + * Cli* is replaced with with CliRegistry* + * cli_* functions are replaced with cli_registry_* functions + * (i.e., cli_add_command() is now cli_registry_add_command()) +*/ -typedef enum { - CliSymbolAsciiSOH = 0x01, - CliSymbolAsciiETX = 0x03, - CliSymbolAsciiEOT = 0x04, - CliSymbolAsciiBell = 0x07, - CliSymbolAsciiBackspace = 0x08, - CliSymbolAsciiTab = 0x09, - CliSymbolAsciiLF = 0x0A, - CliSymbolAsciiCR = 0x0D, - CliSymbolAsciiEsc = 0x1B, - CliSymbolAsciiUS = 0x1F, - CliSymbolAsciiSpace = 0x20, - CliSymbolAsciiDel = 0x7F, -} CliSymbols; - -typedef enum { - CliCommandFlagDefault = 0, /**< Default, loader lock is used */ - CliCommandFlagParallelSafe = - (1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */ - CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */ -} CliCommandFlag; +#include #define RECORD_CLI "cli" - -/** Cli type anonymous structure */ -typedef struct Cli Cli; - -/** Cli callback function pointer. Implement this interface and use - * add_cli_command - * @param args string with what was passed after command - * @param context pointer to whatever you gave us on cli_add_command - */ -typedef void (*CliCallback)(Cli* cli, FuriString* args, void* context); - -/** Add cli command Registers you command callback - * - * @param cli pointer to cli instance - * @param name command name - * @param flags CliCommandFlag - * @param callback callback function - * @param context pointer to whatever we need to pass to callback - */ -void cli_add_command( - Cli* cli, - const char* name, - CliCommandFlag flags, - CliCallback callback, - void* context); - -/** Print unified cmd usage tip - * - * @param cmd cmd name - * @param usage usage tip - * @param arg arg passed by user - */ -void cli_print_usage(const char* cmd, const char* usage, const char* arg); - -/** Delete cli command - * - * @param cli pointer to cli instance - * @param name command name - */ -void cli_delete_command(Cli* cli, const char* name); - -/** Read from terminal - * - * @param cli Cli instance - * @param buffer pointer to buffer - * @param size size of buffer in bytes - * - * @return bytes read - */ -size_t cli_read(Cli* cli, uint8_t* buffer, size_t size); - -/** Non-blocking read from terminal - * - * @param cli Cli instance - * @param buffer pointer to buffer - * @param size size of buffer in bytes - * @param timeout timeout value in ms - * - * @return bytes read - */ -size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout); - -/** Non-blocking check for interrupt command received - * - * @param cli Cli instance - * - * @return true if received - */ -bool cli_cmd_interrupt_received(Cli* cli); - -/** Write to terminal Do it only from inside of cli call. - * - * @param cli Cli instance - * @param buffer pointer to buffer - * @param size size of buffer in bytes - */ -void cli_write(Cli* cli, const uint8_t* buffer, size_t size); - -/** Read character - * - * @param cli Cli instance - * - * @return char - */ -char cli_getc(Cli* cli); - -/** New line Send new ine sequence - */ -void cli_nl(Cli* cli); - -void cli_session_open(Cli* cli, void* session); - -void cli_session_close(Cli* cli); - -bool cli_is_connected(Cli* cli); - -#ifdef __cplusplus -} -#endif diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index 010a7dfbe..f6337265d 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include void cli_command_gpio_print_usage(void) { printf("Usage:\r\n"); @@ -70,8 +72,8 @@ static GpioParseReturn gpio_command_parse(FuriString* args, size_t* pin_num, uin return ret; } -void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_gpio_mode(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -93,7 +95,7 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { if(gpio_pins[num].debug) { //-V779 printf( "Changing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); - char c = cli_getc(cli); + char c = getchar(); if(c != 'y' && c != 'Y') { printf("Cancelled.\r\n"); return; @@ -110,8 +112,8 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { } } -void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_gpio_read(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -131,7 +133,8 @@ void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) { printf("Pin %s <= %u", gpio_pins[num].name, val); } -void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { +void cli_command_gpio_set(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -159,7 +162,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { if(gpio_pins[num].debug) { printf( "Setting this pin may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); - char c = cli_getc(cli); + char c = getchar(); if(c != 'y' && c != 'Y') { printf("Cancelled.\r\n"); return; @@ -170,7 +173,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { printf("Pin %s => %u", gpio_pins[num].name, !!value); } -void cli_command_gpio(Cli* cli, FuriString* args, void* context) { +void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -181,17 +184,17 @@ void cli_command_gpio(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "mode") == 0) { - cli_command_gpio_mode(cli, args, context); + cli_command_gpio_mode(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "set") == 0) { - cli_command_gpio_set(cli, args, context); + cli_command_gpio_set(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "read") == 0) { - cli_command_gpio_read(cli, args, context); + cli_command_gpio_read(pipe, args, context); break; } diff --git a/applications/services/cli/cli_command_gpio.h b/applications/services/cli/cli_command_gpio.h index 7ae5aa625..c1911fb65 100644 --- a/applications/services/cli/cli_command_gpio.h +++ b/applications/services/cli/cli_command_gpio.h @@ -1,5 +1,5 @@ #pragma once -#include "cli_i.h" +#include -void cli_command_gpio(Cli* cli, FuriString* args, void* context); +void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context); diff --git a/applications/services/cli/cli_commands.h b/applications/services/cli/cli_commands.h deleted file mode 100644 index 184eeb373..000000000 --- a/applications/services/cli/cli_commands.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "cli_i.h" - -void cli_commands_init(Cli* cli); diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h deleted file mode 100644 index 4361e07b5..000000000 --- a/applications/services/cli/cli_i.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include "cli.h" - -#include -#include - -#include -#include -#include - -#include "cli_vcp.h" - -#define CLI_LINE_SIZE_MAX -#define CLI_COMMANDS_TREE_RANK 4 - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - CliCallback callback; - void* context; - uint32_t flags; -} CliCommand; - -struct CliSession { - void (*init)(void); - void (*deinit)(void); - size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout); - size_t (*rx_stdin)(uint8_t* buffer, size_t size, uint32_t timeout, void* context); - void (*tx)(const uint8_t* buffer, size_t size); - void (*tx_stdout)(const char* data, size_t size, void* context); - bool (*is_connected)(void); -}; - -BPTREE_DEF2( - CliCommandTree, - CLI_COMMANDS_TREE_RANK, - FuriString*, - FURI_STRING_OPLIST, - CliCommand, - M_POD_OPLIST) - -#define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST) - -struct Cli { - CliCommandTree_t commands; - FuriMutex* mutex; - FuriSemaphore* idle_sem; - FuriString* last_line; - FuriString* line; - CliSession* session; - - size_t cursor_position; -}; - -Cli* cli_alloc(void); - -void cli_reset(Cli* cli); - -void cli_putc(Cli* cli, char c); - -void cli_stdout_callback(void* _cookie, const char* data, size_t size); - -// Wraps CLI commands to load from plugin file -// Must call from CLI context, like dummy CLI command callback -// You need to setup the plugin to compile correctly separately -#define CLI_PLUGIN_APP_ID "cli" -#define CLI_PLUGIN_API_VERSION 1 -void cli_plugin_wrapper(const char* name, Cli* cli, FuriString* args, void* context); - -#ifdef __cplusplus -} -#endif diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_main_commands.c similarity index 73% rename from applications/services/cli/cli_commands.c rename to applications/services/cli/cli_main_commands.c index 1480a8912..508a650de 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_main_commands.c @@ -1,5 +1,6 @@ -#include "cli_commands.h" +#include "cli_main_commands.h" #include "cli_command_gpio.h" +#include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" @@ -34,8 +36,8 @@ void cli_command_info_callback(const char* key, const char* value, bool last, vo * @param args The arguments * @param context The context */ -void cli_command_info(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_info(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); if(context) { furi_hal_info_get(cli_command_info_callback, '_', NULL); @@ -53,56 +55,16 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) { } } -void cli_command_help(Cli* cli, FuriString* args, void* context) { - UNUSED(args); - UNUSED(context); - printf("Commands available:"); - - // Command count - const size_t commands_count = CliCommandTree_size(cli->commands); - const size_t commands_count_mid = commands_count / 2 + commands_count % 2; - - // Use 2 iterators from start and middle to show 2 columns - CliCommandTree_it_t it_left; - CliCommandTree_it(it_left, cli->commands); - CliCommandTree_it_t it_right; - CliCommandTree_it(it_right, cli->commands); - for(size_t i = 0; i < commands_count_mid; i++) - CliCommandTree_next(it_right); - - // Iterate throw tree - for(size_t i = 0; i < commands_count_mid; i++) { - printf("\r\n"); - // Left Column - if(!CliCommandTree_end_p(it_left)) { - printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr)); - CliCommandTree_next(it_left); - } - // Right Column - if(!CliCommandTree_end_p(it_right)) { - printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr)); - CliCommandTree_next(it_right); - } - }; - - if(furi_string_size(args) > 0) { - cli_nl(cli); - printf("`"); - printf("%s", furi_string_get_cstr(args)); - printf("` command not found"); - } -} - -void cli_command_uptime(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_uptime(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); printf("Uptime: %luh%lum%lus", uptime / 60 / 60, uptime / 60 % 60, uptime % 60); } -void cli_command_date(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_date(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); DateTime datetime = {0}; @@ -170,21 +132,12 @@ void cli_command_date(Cli* cli, FuriString* args, void* context) { } } -void cli_command_src(Cli* cli, FuriString* args, void* context) { - // Quality of life feature for people exploring CLI on lab.flipper.net/cli - // By Yousef AK - UNUSED(cli); - UNUSED(args); - UNUSED(context); - - printf("https://github.com/DarkFlippers/unleashed-firmware"); -} - #define CLI_COMMAND_LOG_RING_SIZE 2048 #define CLI_COMMAND_LOG_BUFFER_SIZE 64 void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* context) { - furi_stream_buffer_send(context, buffer, size, 0); + PipeSide* pipe = context; + pipe_send(pipe, buffer, size); } bool cli_command_log_level_set_from_string(FuriString* level) { @@ -206,16 +159,13 @@ bool cli_command_log_level_set_from_string(FuriString* level) { return false; } -void cli_command_log(Cli* cli, FuriString* args, void* context) { +void cli_command_log(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); - FuriStreamBuffer* ring = furi_stream_buffer_alloc(CLI_COMMAND_LOG_RING_SIZE, 1); - uint8_t buffer[CLI_COMMAND_LOG_BUFFER_SIZE]; FuriLogLevel previous_level = furi_log_get_level(); bool restore_log_level = false; if(furi_string_size(args) > 0) { if(!cli_command_log_level_set_from_string(args)) { - furi_stream_buffer_free(ring); return; } restore_log_level = true; @@ -227,16 +177,15 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { FuriLogHandler log_handler = { .callback = cli_command_log_tx_callback, - .context = ring, + .context = pipe, }; furi_log_add_handler(log_handler); printf("Use to list available log levels\r\n"); printf("Press CTRL+C to stop...\r\n"); - while(!cli_cmd_interrupt_received(cli)) { - size_t ret = furi_stream_buffer_receive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50); - cli_write(cli, buffer, ret); + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { + furi_delay_ms(100); } furi_log_remove_handler(log_handler); @@ -245,12 +194,10 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { // There will be strange behaviour if log level is set from settings while log command is running furi_log_set_level(previous_level); } - - furi_stream_buffer_free(ring); } -void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_sysctl_debug(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "0")) { furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); @@ -263,8 +210,8 @@ void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) { } } -void cli_command_sysctl_heap_track(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_sysctl_heap_track(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "none")) { furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeNone); @@ -298,7 +245,7 @@ void cli_command_sysctl_print_usage(void) { #endif } -void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { +void cli_command_sysctl(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -309,12 +256,12 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "debug") == 0) { - cli_command_sysctl_debug(cli, args, context); + cli_command_sysctl_debug(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "heap_track") == 0) { - cli_command_sysctl_heap_track(cli, args, context); + cli_command_sysctl_heap_track(pipe, args, context); break; } @@ -324,8 +271,8 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -void cli_command_vibro(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_vibro(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "0")) { @@ -351,8 +298,8 @@ void cli_command_vibro(Cli* cli, FuriString* args, void* context) { } } -void cli_command_led(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_led(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); // Get first word as light name NotificationMessage notification_led_message; @@ -406,23 +353,23 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) { furi_record_close(RECORD_NOTIFICATION); } -static void cli_command_top(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void cli_command_top(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int interval = 1000; args_read_int_and_trim(args, &interval); FuriThreadList* thread_list = furi_thread_list_alloc(); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { uint32_t tick = furi_get_tick(); furi_thread_enumerate(thread_list); - if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0 + if(interval) printf(ANSI_CURSOR_POS("1", "1")); uint32_t uptime = tick / furi_kernel_get_tick_frequency(); printf( - "Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\r\n", + "Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", furi_thread_list_size(thread_list), (double)furi_thread_list_get_isr_time(thread_list), uptime / 60 / 60, @@ -430,14 +377,16 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { uptime % 60); printf( - "Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n", + "Heap: total %zu, free %zu, minimum %zu, max block %zu" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", memmgr_get_total_heap(), memmgr_get_free_heap(), memmgr_get_minimum_free_heap(), memmgr_heap_get_max_free_block()); printf( - "%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n", + "%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", "AppID", "Name", "State", @@ -446,12 +395,13 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { "Stack", "Stack Min", "Heap", - "CPU"); + "%CPU"); for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) { const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i); printf( - "%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n", + "%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", item->app_id, item->name, item->state, @@ -463,6 +413,9 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { (double)item->cpu); } + printf(ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END)); + fflush(stdout); + if(interval > 0) { furi_delay_ms(interval); } else { @@ -472,8 +425,8 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { furi_thread_list_free(thread_list); } -void cli_command_free(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_free(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -486,16 +439,16 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); } -void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_free_blocks(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); memmgr_heap_printf_free_blocks(); } -void cli_command_i2c(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_i2c(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -517,26 +470,53 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) { furi_hal_i2c_release(&furi_hal_i2c_handle_external); } -void cli_commands_init(Cli* cli) { - cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); - cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL); - cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true); - cli_add_command(cli, "source", CliCommandFlagParallelSafe, cli_command_src, NULL); +/** + * Echoes any bytes it receives except ASCII ETX (0x03, Ctrl+C) + */ +void cli_command_echo(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(args); + UNUSED(context); - cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); - cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL); + uint8_t buffer[256]; - cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime, NULL); - cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL); - cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); - cli_add_command(cli, "l", CliCommandFlagParallelSafe, cli_command_log, NULL); - cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL); - cli_add_command(cli, "top", CliCommandFlagParallelSafe, cli_command_top, NULL); - cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL); - cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); + while(true) { + size_t to_read = CLAMP(pipe_bytes_available(pipe), sizeof(buffer), 1UL); + size_t read = pipe_receive(pipe, buffer, to_read); + if(read < to_read) break; - cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); - cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL); - cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL); - cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL); + if(memchr(buffer, CliKeyETX, read)) break; + + size_t written = pipe_send(pipe, buffer, read); + if(written < read) break; + } +} + +void cli_main_commands_init(CliRegistry* registry) { + cli_registry_add_command( + registry, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); + cli_registry_add_command(registry, "info", CliCommandFlagParallelSafe, cli_command_info, NULL); + cli_registry_add_command( + registry, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true); + + cli_registry_add_command( + registry, "uptime", CliCommandFlagParallelSafe, cli_command_uptime, NULL); + cli_registry_add_command(registry, "date", CliCommandFlagParallelSafe, cli_command_date, NULL); + cli_registry_add_command(registry, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); + cli_registry_add_command(registry, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL); + cli_registry_add_command(registry, "top", CliCommandFlagParallelSafe, cli_command_top, NULL); + cli_registry_add_command(registry, "free", CliCommandFlagParallelSafe, cli_command_free, NULL); + cli_registry_add_command( + registry, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); + cli_registry_add_command(registry, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL); + + cli_registry_add_command(registry, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); + cli_registry_add_command(registry, "led", CliCommandFlagDefault, cli_command_led, NULL); + cli_registry_add_command(registry, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL); + cli_registry_add_command(registry, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL); +} + +void cli_on_system_start(void) { + CliRegistry* registry = cli_registry_alloc(); + cli_main_commands_init(registry); + furi_record_create(RECORD_CLI, registry); } diff --git a/applications/services/cli/cli_main_commands.h b/applications/services/cli/cli_main_commands.h new file mode 100644 index 000000000..ebee4ba1e --- /dev/null +++ b/applications/services/cli/cli_main_commands.h @@ -0,0 +1,9 @@ +#pragma once + +#include "cli.h" +#include +#include + +#define CLI_APPID "cli" + +void cli_main_commands_init(CliRegistry* registry); diff --git a/applications/services/cli/cli_main_shell.c b/applications/services/cli/cli_main_shell.c new file mode 100644 index 000000000..7550bef04 --- /dev/null +++ b/applications/services/cli/cli_main_shell.c @@ -0,0 +1,46 @@ +#include "cli_main_shell.h" +#include "cli_main_commands.h" +#include +#include +#include + +void cli_main_motd(void* context) { + UNUSED(context); + printf(ANSI_FLIPPER_BRAND_ORANGE + "\r\n" + " _.-------.._ -,\r\n" + " .-\"```\"--..,,_/ /`-, -, \\ \r\n" + " .:\" /:/ /'\\ \\ ,_..., `. | |\r\n" + " / ,----/:/ /`\\ _\\~`_-\"` _;\r\n" + " ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n" + " | | | 0 | | .-' ,/` /\r\n" + " | ,..\\ \\ ,.-\"` ,/` /\r\n" + " ; : `/`\"\"\\` ,/--==,/-----,\r\n" + " | `-...| -.___-Z:_______J...---;\r\n" + " : ` _-'\r\n" + " _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n" + "| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n" + "| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n" + "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" + "\r\n" ANSI_FG_BR_WHITE "Welcome to Flipper Zero Command Line Interface!\r\n" + "Read the manual: https://docs.flipper.net/development/cli\r\n" + "Run `help` or `?` to list available commands\r\n" + "\r\n" ANSI_RESET); + + const Version* firmware_version = furi_hal_version_get_firmware_version(); + if(firmware_version) { + printf( + "Firmware version: %s %s (%s%s built on %s)\r\n", + version_get_gitbranch(firmware_version), + version_get_version(firmware_version), + version_get_githash(firmware_version), + version_get_dirty_flag(firmware_version) ? "-dirty" : "", + version_get_builddate(firmware_version)); + } +} + +const CliCommandExternalConfig cli_main_ext_config = { + .search_directory = "/ext/apps_data/cli/plugins", + .fal_prefix = "cli_", + .appid = CLI_APPID, +}; diff --git a/applications/services/cli/cli_main_shell.h b/applications/services/cli/cli_main_shell.h new file mode 100644 index 000000000..576839990 --- /dev/null +++ b/applications/services/cli/cli_main_shell.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +void cli_main_motd(void* context); + +extern const CliCommandExternalConfig cli_main_ext_config; diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 315baa3a2..1f9c77b08 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -1,324 +1,327 @@ -#include "cli_i.h" // IWYU pragma: keep +#include "cli_vcp.h" #include #include #include +#include +#include +#include +#include +#include "cli_main_shell.h" +#include "cli_main_commands.h" #define TAG "CliVcp" -#define USB_CDC_PKT_LEN CDC_DATA_SZ -#define VCP_RX_BUF_SIZE (USB_CDC_PKT_LEN * 3) -#define VCP_TX_BUF_SIZE (USB_CDC_PKT_LEN * 3) +#define USB_CDC_PKT_LEN CDC_DATA_SZ +#define VCP_BUF_SIZE (USB_CDC_PKT_LEN * 3) +#define VCP_IF_NUM 0 +#define VCP_MESSAGE_Q_LEN 8 -#define VCP_IF_NUM 0 - -#ifdef CLI_VCP_DEBUG -#define VCP_DEBUG(...) FURI_LOG_D(TAG, __VA_ARGS__) +#ifdef CLI_VCP_TRACE +#define VCP_TRACE(...) FURI_LOG_T(__VA_ARGS__) #else -#define VCP_DEBUG(...) +#define VCP_TRACE(...) #endif -typedef enum { - VcpEvtStop = (1 << 0), - VcpEvtConnect = (1 << 1), - VcpEvtDisconnect = (1 << 2), - VcpEvtStreamRx = (1 << 3), - VcpEvtRx = (1 << 4), - VcpEvtStreamTx = (1 << 5), - VcpEvtTx = (1 << 6), -} WorkerEvtFlags; - -#define VCP_THREAD_FLAG_ALL \ - (VcpEvtStop | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | \ - VcpEvtStreamTx) - typedef struct { - FuriThread* thread; + enum { + CliVcpMessageTypeEnable, + CliVcpMessageTypeDisable, + } type; + FuriApiLock api_lock; + union {}; +} CliVcpMessage; - FuriStreamBuffer* tx_stream; - FuriStreamBuffer* rx_stream; +typedef enum { + CliVcpInternalEventConnected, + CliVcpInternalEventDisconnected, + CliVcpInternalEventTxDone, + CliVcpInternalEventRx, +} CliVcpInternalEvent; - volatile bool connected; - volatile bool running; +struct CliVcp { + FuriEventLoop* event_loop; + FuriMessageQueue* message_queue; // is_currently_transmitting) return; + if(!cli_vcp->own_pipe) return; -static void cli_vcp_init(void) { - if(vcp == NULL) { - vcp = malloc(sizeof(CliVcp)); - vcp->tx_stream = furi_stream_buffer_alloc(VCP_TX_BUF_SIZE, 1); - vcp->rx_stream = furi_stream_buffer_alloc(VCP_RX_BUF_SIZE, 1); + uint8_t buf[USB_CDC_PKT_LEN]; + size_t to_receive_from_pipe = MIN(sizeof(buf), pipe_bytes_available(cli_vcp->own_pipe)); + size_t length = pipe_receive(cli_vcp->own_pipe, buf, to_receive_from_pipe); + if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) { + VCP_TRACE(TAG, "cdc_send length=%zu", length); + cli_vcp->is_currently_transmitting = true; + furi_hal_cdc_send(VCP_IF_NUM, buf, length); } - furi_assert(vcp->thread == NULL); - - vcp->connected = false; - - vcp->thread = furi_thread_alloc_ex("CliVcpWorker", 1024, vcp_worker, NULL); - furi_thread_start(vcp->thread); - - FURI_LOG_I(TAG, "Init OK"); + cli_vcp->previous_tx_length = length; } -static void cli_vcp_deinit(void) { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStop); - furi_thread_join(vcp->thread); - furi_thread_free(vcp->thread); - vcp->thread = NULL; +/** + * Called in the following cases: + * - new data arrived at the endpoint; + * - data was read out of the pipe. + */ +static void cli_vcp_maybe_receive_data(CliVcp* cli_vcp) { + if(!cli_vcp->own_pipe) return; + if(pipe_spaces_available(cli_vcp->own_pipe) < USB_CDC_PKT_LEN) return; + + uint8_t buf[USB_CDC_PKT_LEN]; + size_t length = furi_hal_cdc_receive(VCP_IF_NUM, buf, sizeof(buf)); + VCP_TRACE(TAG, "cdc_receive length=%zu", length); + furi_check(pipe_send(cli_vcp->own_pipe, buf, length) == length); } -static int32_t vcp_worker(void* context) { - UNUSED(context); - bool tx_idle = true; - size_t missed_rx = 0; - uint8_t last_tx_pkt_len = 0; +// ============= +// CDC callbacks +// ============= - // Switch USB to VCP mode (if it is not set yet) - vcp->usb_if_prev = furi_hal_usb_get_config(); - if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) { +static void cli_vcp_signal_internal_event(CliVcp* cli_vcp, CliVcpInternalEvent event) { + furi_check(furi_message_queue_put(cli_vcp->internal_evt_queue, &event, 0) == FuriStatusOk); +} + +static void cli_vcp_cdc_tx_done(void* context) { + CliVcp* cli_vcp = context; + cli_vcp->is_currently_transmitting = false; + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventTxDone); +} + +static void cli_vcp_cdc_rx(void* context) { + CliVcp* cli_vcp = context; + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventRx); +} + +static void cli_vcp_cdc_state_callback(void* context, CdcState state) { + CliVcp* cli_vcp = context; + if(state == CdcStateDisconnected) { + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected); + } + // `Connected` events are generated by DTR going active +} + +static void cli_vcp_cdc_ctrl_line_callback(void* context, CdcCtrlLine ctrl_lines) { + CliVcp* cli_vcp = context; + if(ctrl_lines & CdcCtrlLineDTR) { + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventConnected); + } else { + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected); + } +} + +static CdcCallbacks cdc_callbacks = { + .tx_ep_callback = cli_vcp_cdc_tx_done, + .rx_ep_callback = cli_vcp_cdc_rx, + .state_callback = cli_vcp_cdc_state_callback, + .ctrl_line_callback = cli_vcp_cdc_ctrl_line_callback, + .config_callback = NULL, +}; + +// ====================== +// Pipe callback handlers +// ====================== + +static void cli_vcp_data_from_shell(PipeSide* pipe, void* context) { + UNUSED(pipe); + CliVcp* cli_vcp = context; + cli_vcp_maybe_send_data(cli_vcp); +} + +static void cli_vcp_shell_ready(PipeSide* pipe, void* context) { + UNUSED(pipe); + CliVcp* cli_vcp = context; + cli_vcp_maybe_receive_data(cli_vcp); +} + +/** + * Processes messages arriving from other threads + */ +static void cli_vcp_message_received(FuriEventLoopObject* object, void* context) { + CliVcp* cli_vcp = context; + CliVcpMessage message; + furi_check(furi_message_queue_get(object, &message, 0) == FuriStatusOk); + + switch(message.type) { + case CliVcpMessageTypeEnable: + if(cli_vcp->is_enabled) break; + FURI_LOG_D(TAG, "Enabling"); + cli_vcp->is_enabled = true; + + // switch usb mode + cli_vcp->previous_interface = furi_hal_usb_get_config(); furi_hal_usb_set_config(&usb_cdc_single, NULL); + furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_callbacks, cli_vcp); + break; + + case CliVcpMessageTypeDisable: + if(!cli_vcp->is_enabled) break; + FURI_LOG_D(TAG, "Disabling"); + cli_vcp->is_enabled = false; + + // restore usb mode + furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL); + furi_hal_usb_set_config(cli_vcp->previous_interface, NULL); + break; } - furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL); - FURI_LOG_D(TAG, "Start"); - vcp->running = true; - - while(1) { - uint32_t flags = - furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); - furi_assert(!(flags & FuriFlagError)); - - // VCP session opened - if(flags & VcpEvtConnect) { - VCP_DEBUG("Connect"); - - if(vcp->connected == false) { - vcp->connected = true; - furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever); - } - } - - // VCP session closed - if(flags & VcpEvtDisconnect) { - VCP_DEBUG("Disconnect"); - - if(vcp->connected == true) { - vcp->connected = false; - furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); - furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); - } - } - - // Rx buffer was read, maybe there is enough space for new data? - if((flags & VcpEvtStreamRx) && (missed_rx > 0)) { - VCP_DEBUG("StreamRx"); - - if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { - flags |= VcpEvtRx; - missed_rx--; - } - } - - // New data received - if(flags & VcpEvtRx) { - if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { - int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); - VCP_DEBUG("Rx %ld", len); - - if(len > 0) { - furi_check( - furi_stream_buffer_send( - vcp->rx_stream, vcp->data_buffer, len, FuriWaitForever) == - (size_t)len); - } - } else { - VCP_DEBUG("Rx missed"); - missed_rx++; - } - } - - // New data in Tx buffer - if(flags & VcpEvtStreamTx) { - VCP_DEBUG("StreamTx"); - - if(tx_idle) { - flags |= VcpEvtTx; - } - } - - // CDC write transfer done - if(flags & VcpEvtTx) { - size_t len = - furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); - - VCP_DEBUG("Tx %d", len); - - if(len > 0) { // Some data left in Tx buffer. Sending it now - tx_idle = false; - furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len); - last_tx_pkt_len = len; - } else { // There is nothing to send. - if(last_tx_pkt_len == 64) { - // Send extra zero-length packet if last packet len is 64 to indicate transfer end - furi_hal_cdc_send(VCP_IF_NUM, NULL, 0); - } else { - // Set flag to start next transfer instantly - tx_idle = true; - } - last_tx_pkt_len = 0; - } - } - - if(flags & VcpEvtStop) { - vcp->connected = false; - vcp->running = false; - furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL); - // Restore previous USB mode (if it was set during init) - if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) { - furi_hal_usb_unlock(); - furi_hal_usb_set_config(vcp->usb_if_prev, NULL); - } - furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); - furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); - break; - } - } - FURI_LOG_D(TAG, "End"); - return 0; + api_lock_unlock(message.api_lock); } -static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { - furi_assert(vcp); - furi_assert(buffer); +/** + * Processes messages arriving from CDC event callbacks + */ +static void cli_vcp_internal_event_happened(FuriEventLoopObject* object, void* context) { + CliVcp* cli_vcp = context; + CliVcpInternalEvent event; + furi_check(furi_message_queue_get(object, &event, 0) == FuriStatusOk); - if(vcp->running == false) { + switch(event) { + case CliVcpInternalEventRx: { + VCP_TRACE(TAG, "Rx"); + cli_vcp_maybe_receive_data(cli_vcp); + break; + } + + case CliVcpInternalEventTxDone: { + VCP_TRACE(TAG, "TxDone"); + cli_vcp_maybe_send_data(cli_vcp); + break; + } + + case CliVcpInternalEventDisconnected: { + if(!cli_vcp->is_connected) return; + FURI_LOG_D(TAG, "Disconnected"); + cli_vcp->is_connected = false; + + // disconnect our side of the pipe + pipe_detach_from_event_loop(cli_vcp->own_pipe); + pipe_free(cli_vcp->own_pipe); + cli_vcp->own_pipe = NULL; + break; + } + + case CliVcpInternalEventConnected: { + if(cli_vcp->is_connected) return; + FURI_LOG_D(TAG, "Connected"); + cli_vcp->is_connected = true; + + // wait for previous shell to stop + furi_check(!cli_vcp->own_pipe); + if(cli_vcp->shell) { + cli_shell_join(cli_vcp->shell); + cli_shell_free(cli_vcp->shell); + pipe_free(cli_vcp->shell_pipe); + } + + // start shell thread + PipeSideBundle bundle = pipe_alloc(VCP_BUF_SIZE, 1); + cli_vcp->own_pipe = bundle.alices_side; + cli_vcp->shell_pipe = bundle.bobs_side; + pipe_attach_to_event_loop(cli_vcp->own_pipe, cli_vcp->event_loop); + pipe_set_callback_context(cli_vcp->own_pipe, cli_vcp); + pipe_set_data_arrived_callback( + cli_vcp->own_pipe, cli_vcp_data_from_shell, FuriEventLoopEventFlagEdge); + pipe_set_space_freed_callback( + cli_vcp->own_pipe, cli_vcp_shell_ready, FuriEventLoopEventFlagEdge); + furi_delay_ms(33); // we are too fast, minicom isn't ready yet + cli_vcp->shell = cli_shell_alloc( + cli_main_motd, NULL, cli_vcp->shell_pipe, cli_vcp->main_registry, &cli_main_ext_config); + cli_shell_start(cli_vcp->shell); + break; + } + } +} + +// ============ +// Thread stuff +// ============ + +static CliVcp* cli_vcp_alloc(void) { + CliVcp* cli_vcp = malloc(sizeof(CliVcp)); + + cli_vcp->event_loop = furi_event_loop_alloc(); + + cli_vcp->message_queue = furi_message_queue_alloc(VCP_MESSAGE_Q_LEN, sizeof(CliVcpMessage)); + furi_event_loop_subscribe_message_queue( + cli_vcp->event_loop, + cli_vcp->message_queue, + FuriEventLoopEventIn, + cli_vcp_message_received, + cli_vcp); + + cli_vcp->internal_evt_queue = + furi_message_queue_alloc(VCP_MESSAGE_Q_LEN, sizeof(CliVcpInternalEvent)); + furi_event_loop_subscribe_message_queue( + cli_vcp->event_loop, + cli_vcp->internal_evt_queue, + FuriEventLoopEventIn, + cli_vcp_internal_event_happened, + cli_vcp); + + cli_vcp->main_registry = furi_record_open(RECORD_CLI); + + return cli_vcp; +} + +int32_t cli_vcp_srv(void* p) { + UNUSED(p); + + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { + FURI_LOG_W(TAG, "Skipping start in special boot mode"); + furi_thread_suspend(furi_thread_get_current_id()); return 0; } - VCP_DEBUG("rx %u start", size); + CliVcp* cli_vcp = cli_vcp_alloc(); + furi_record_create(RECORD_CLI_VCP, cli_vcp); + furi_event_loop_run(cli_vcp->event_loop); - size_t rx_cnt = 0; - - while(size > 0) { - size_t batch_size = size; - if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE; - - size_t len = furi_stream_buffer_receive(vcp->rx_stream, buffer, batch_size, timeout); - VCP_DEBUG("rx %u ", batch_size); - - if(len == 0) break; - if(vcp->running == false) { - // EOT command is received after VCP session close - rx_cnt += len; - break; - } - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamRx); - size -= len; - buffer += len; - rx_cnt += len; - } - - VCP_DEBUG("rx %u end", size); - return rx_cnt; + return 0; } -static size_t cli_vcp_rx_stdin(uint8_t* data, size_t size, uint32_t timeout, void* context) { - UNUSED(context); - return cli_vcp_rx(data, size, timeout); +// ========== +// Public API +// ========== + +static void cli_vcp_synchronous_request(CliVcp* cli_vcp, CliVcpMessage* message) { + message->api_lock = api_lock_alloc_locked(); + furi_message_queue_put(cli_vcp->message_queue, message, FuriWaitForever); + api_lock_wait_unlock_and_free(message->api_lock); } -static void cli_vcp_tx(const uint8_t* buffer, size_t size) { - furi_assert(vcp); - furi_assert(buffer); - - if(vcp->running == false) { - return; - } - - VCP_DEBUG("tx %u start", size); - - while(size > 0 && vcp->connected) { - size_t batch_size = size; - if(batch_size > USB_CDC_PKT_LEN) batch_size = USB_CDC_PKT_LEN; - - furi_stream_buffer_send(vcp->tx_stream, buffer, batch_size, FuriWaitForever); - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamTx); - VCP_DEBUG("tx %u", batch_size); - - size -= batch_size; - buffer += batch_size; - } - - VCP_DEBUG("tx %u end", size); +void cli_vcp_enable(CliVcp* cli_vcp) { + furi_check(cli_vcp); + CliVcpMessage message = { + .type = CliVcpMessageTypeEnable, + }; + cli_vcp_synchronous_request(cli_vcp, &message); } -static void cli_vcp_tx_stdout(const char* data, size_t size, void* context) { - UNUSED(context); - cli_vcp_tx((const uint8_t*)data, size); +void cli_vcp_disable(CliVcp* cli_vcp) { + furi_check(cli_vcp); + CliVcpMessage message = { + .type = CliVcpMessageTypeDisable, + }; + cli_vcp_synchronous_request(cli_vcp, &message); } - -static void vcp_state_callback(void* context, uint8_t state) { - UNUSED(context); - if(state == 0) { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect); - } -} - -static void vcp_on_cdc_control_line(void* context, uint8_t state) { - UNUSED(context); - // bit 0: DTR state, bit 1: RTS state - bool dtr = state & (1 << 0); - - if(dtr == true) { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtConnect); - } else { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect); - } -} - -static void vcp_on_cdc_rx(void* context) { - UNUSED(context); - uint32_t ret = furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtRx); - furi_check(!(ret & FuriFlagError)); -} - -static void vcp_on_cdc_tx_complete(void* context) { - UNUSED(context); - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtTx); -} - -static bool cli_vcp_is_connected(void) { - furi_assert(vcp); - return vcp->connected; -} - -CliSession cli_vcp = { - cli_vcp_init, - cli_vcp_deinit, - cli_vcp_rx, - cli_vcp_rx_stdin, - cli_vcp_tx, - cli_vcp_tx_stdout, - cli_vcp_is_connected, -}; diff --git a/applications/services/cli/cli_vcp.h b/applications/services/cli/cli_vcp.h index 3aef2ef70..10e286183 100644 --- a/applications/services/cli/cli_vcp.h +++ b/applications/services/cli/cli_vcp.h @@ -9,9 +9,12 @@ extern "C" { #endif -typedef struct CliSession CliSession; +#define RECORD_CLI_VCP "cli_vcp" -extern CliSession cli_vcp; +typedef struct CliVcp CliVcp; + +void cli_vcp_enable(CliVcp* cli_vcp); +void cli_vcp_disable(CliVcp* cli_vcp); #ifdef __cplusplus } diff --git a/applications/services/cli/commands/hello_world.c b/applications/services/cli/commands/hello_world.c new file mode 100644 index 000000000..b77f3e663 --- /dev/null +++ b/applications/services/cli/commands/hello_world.c @@ -0,0 +1,10 @@ +#include "../cli_main_commands.h" + +static void execute(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + UNUSED(context); + puts("Hello, World!"); +} + +CLI_COMMAND_INTERFACE(hello_world, execute, CliCommandFlagParallelSafe, 768, CLI_APPID); diff --git a/applications/services/cli/commands/neofetch.c b/applications/services/cli/commands/neofetch.c new file mode 100644 index 000000000..0e50a0d8d --- /dev/null +++ b/applications/services/cli/commands/neofetch.c @@ -0,0 +1,160 @@ +#include "../cli_main_commands.h" +#include +#include +#include +#include +#include +#include + +static void execute(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + UNUSED(context); + + static const char* const neofetch_logo[] = { + " _.-------.._ -,", + " .-\"```\"--..,,_/ /`-, -, \\ ", + " .:\" /:/ /'\\ \\ ,_..., `. | |", + " / ,----/:/ /`\\ _\\~`_-\"` _;", + " ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ", + " | | | 0 | | .-' ,/` /", + " | ,..\\ \\ ,.-\"` ,/` /", + "; : `/`\"\"\\` ,/--==,/-----,", + "| `-...| -.___-Z:_______J...---;", + ": ` _-'", + }; +#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE + + // Determine logo parameters + size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0; + for(size_t i = 0; i < logo_height; i++) + logo_width = MAX(logo_width, strlen(neofetch_logo[i])); + logo_width += 4; // space between logo and info + + // Format hostname delimiter + const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr()); + char delimiter[64]; + memset(delimiter, '-', size_of_hostname); + delimiter[size_of_hostname] = '\0'; + + // Get heap info + size_t heap_total = memmgr_get_total_heap(); + size_t heap_used = heap_total - memmgr_get_free_heap(); + uint16_t heap_percent = (100 * heap_used) / heap_total; + + // Get storage info + Storage* storage = furi_record_open(RECORD_STORAGE); + uint64_t ext_total, ext_free, ext_used, ext_percent; + storage_common_fs_info(storage, "/ext", &ext_total, &ext_free); + ext_used = ext_total - ext_free; + ext_percent = (100 * ext_used) / ext_total; + ext_used /= 1024 * 1024; + ext_total /= 1024 * 1024; + furi_record_close(RECORD_STORAGE); + + // Get battery info + uint16_t charge_percent = furi_hal_power_get_pct(); + const char* charge_state; + if(furi_hal_power_is_charging()) { + if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) { + charge_state = "charging"; + } else { + charge_state = "charged"; + } + } else { + charge_state = "discharging"; + } + + // Get misc info + uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); + const Version* version = version_get(); + uint16_t major, minor; + furi_hal_info_get_api_version(&major, &minor); + + // Print ASCII art with info + const size_t info_height = 16; + for(size_t i = 0; i < MAX(logo_height, info_height); i++) { + printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : ""); + switch(i) { + case 0: // you@ + printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr()); + break; + case 1: // delimiter + printf(ANSI_RESET "%s", delimiter); + break; + case 2: // OS: FURI (SDK .) + printf( + "OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)", + version_get_version(version), + version_get_gitbranch(version), + version_get_version(version), + version_get_githash(version), + major, + minor); + break; + case 3: // Host: + printf( + "Host" ANSI_RESET ": %s %s", + furi_hal_version_get_model_code(), + furi_hal_version_get_device_name_ptr()); + break; + case 4: // Kernel: FreeRTOS .. + printf( + "Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d", + tskKERNEL_VERSION_MAJOR, + tskKERNEL_VERSION_MINOR, + tskKERNEL_VERSION_BUILD); + break; + case 5: // Uptime: ?h?m?s + printf( + "Uptime" ANSI_RESET ": %luh%lum%lus", + uptime / 60 / 60, + uptime / 60 % 60, + uptime % 60); + break; + case 6: // ST7567 128x64 @ 1 bpp in 1.4" + printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\""); + break; + case 7: // DE: GuiSrv + printf("DE" ANSI_RESET ": GuiSrv"); + break; + case 8: // Shell: CliSrv + printf("Shell" ANSI_RESET ": CliShell"); + break; + case 9: // CPU: STM32WB55RG @ 64 MHz + printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz"); + break; + case 10: // Memory: / B (??%) + printf( + "Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent); + break; + case 11: // Disk (/ext): / MiB (??%) + printf( + "Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)", + ext_used, + ext_total, + ext_percent); + break; + case 12: // Battery: ??% () + printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state); + break; + case 13: // empty space + break; + case 14: // Colors (line 1) + for(size_t j = 30; j <= 37; j++) + printf("\e[%dm███", j); + break; + case 15: // Colors (line 2) + for(size_t j = 90; j <= 97; j++) + printf("\e[%dm███", j); + break; + default: + break; + } + printf("\r\n"); + } + printf(ANSI_RESET); +#undef NEOFETCH_COLOR +} + +CLI_COMMAND_INTERFACE(neofetch, execute, CliCommandFlagParallelSafe, 2048, CLI_APPID); diff --git a/applications/services/cli/commands/subshell_demo.c b/applications/services/cli/commands/subshell_demo.c new file mode 100644 index 000000000..f0013c4a0 --- /dev/null +++ b/applications/services/cli/commands/subshell_demo.c @@ -0,0 +1,43 @@ +#include "../cli_main_commands.h" +#include +#include +#include + +#define RAINBOW_SUBCOMMAND \ + ANSI_FG_RED "s" ANSI_FG_YELLOW "u" ANSI_FG_BLUE "b" ANSI_FG_GREEN "c" ANSI_FG_MAGENTA \ + "o" ANSI_FG_RED "m" ANSI_FG_YELLOW "m" ANSI_FG_BLUE "a" ANSI_FG_GREEN \ + "n" ANSI_FG_MAGENTA "d" + +static void subcommand(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + UNUSED(context); + printf("This is a ✨" RAINBOW_SUBCOMMAND ANSI_RESET "✨!"); +} + +static void motd(void* context) { + UNUSED(context); + printf("\r\n"); + printf("+------------------------------------+\r\n"); + printf("| Hello world! |\r\n"); + printf("| This is the " ANSI_FG_GREEN "MOTD" ANSI_RESET " for our " ANSI_FG_BLUE + "subshell" ANSI_RESET "! |\r\n"); + printf("+------------------------------------+\r\n"); +} + +static void execute(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(args); + UNUSED(context); + CliRegistry* registry = cli_registry_alloc(); + cli_registry_add_command(registry, "subcommand", CliCommandFlagParallelSafe, subcommand, NULL); + + CliShell* shell = cli_shell_alloc(motd, NULL, pipe, registry, NULL); + cli_shell_set_prompt(shell, "subshell"); + cli_shell_start(shell); + cli_shell_join(shell); + + cli_shell_free(shell); + cli_registry_free(registry); +} + +CLI_COMMAND_INTERFACE(subshell_demo, execute, CliCommandFlagParallelSafe, 2048, CLI_APPID); diff --git a/applications/services/crypto/application.fam b/applications/services/crypto/application.fam index 7771c5ed2..1d07ca409 100644 --- a/applications/services/crypto/application.fam +++ b/applications/services/crypto/application.fam @@ -2,5 +2,5 @@ App( appid="crypto_start", apptype=FlipperAppType.STARTUP, entry_point="crypto_on_system_start", - order=10, + order=20, ) diff --git a/applications/services/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c index 21fd2ebcf..1942620b5 100644 --- a/applications/services/crypto/crypto_cli.c +++ b/applications/services/crypto/crypto_cli.c @@ -2,7 +2,10 @@ #include #include -#include +#include +#include +#include +#include void crypto_cli_print_usage(void) { printf("Usage:\r\n"); @@ -17,7 +20,7 @@ void crypto_cli_print_usage(void) { "\tstore_key \t - Store key in secure enclave. !!! NON-REVERSIBLE OPERATION - READ MANUAL FIRST !!!\r\n"); } -void crypto_cli_encrypt(Cli* cli, FuriString* args) { +void crypto_cli_encrypt(PipeSide* pipe, FuriString* args) { int key_slot = 0; bool key_loaded = false; uint8_t iv[16]; @@ -44,15 +47,15 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) { FuriString* input; input = furi_string_alloc(); char c; - while(cli_read(cli, (uint8_t*)&c, 1) == 1) { - if(c == CliSymbolAsciiETX) { + while(pipe_receive(pipe, (uint8_t*)&c, 1) == 1) { + if(c == CliKeyETX) { printf("\r\n"); break; } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); furi_string_push_back(input, c); - } else if(c == CliSymbolAsciiCR) { + } else if(c == CliKeyCR) { printf("\r\n"); furi_string_cat(input, "\r\n"); } @@ -92,7 +95,7 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) { } } -void crypto_cli_decrypt(Cli* cli, FuriString* args) { +void crypto_cli_decrypt(PipeSide* pipe, FuriString* args) { int key_slot = 0; bool key_loaded = false; uint8_t iv[16]; @@ -119,15 +122,15 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { FuriString* hex_input; hex_input = furi_string_alloc(); char c; - while(cli_read(cli, (uint8_t*)&c, 1) == 1) { - if(c == CliSymbolAsciiETX) { + while(pipe_receive(pipe, (uint8_t*)&c, 1) == 1) { + if(c == CliKeyETX) { printf("\r\n"); break; } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); furi_string_push_back(hex_input, c); - } else if(c == CliSymbolAsciiCR) { + } else if(c == CliKeyCR) { printf("\r\n"); } } @@ -164,8 +167,8 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { } } -void crypto_cli_has_key(Cli* cli, FuriString* args) { - UNUSED(cli); +void crypto_cli_has_key(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); int key_slot = 0; uint8_t iv[16] = {0}; @@ -186,8 +189,8 @@ void crypto_cli_has_key(Cli* cli, FuriString* args) { } while(0); } -void crypto_cli_store_key(Cli* cli, FuriString* args) { - UNUSED(cli); +void crypto_cli_store_key(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); int key_slot = 0; int key_size = 0; FuriString* key_type; @@ -279,7 +282,7 @@ void crypto_cli_store_key(Cli* cli, FuriString* args) { furi_string_free(key_type); } -static void crypto_cli(Cli* cli, FuriString* args, void* context) { +static void crypto_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -291,22 +294,22 @@ static void crypto_cli(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "encrypt") == 0) { - crypto_cli_encrypt(cli, args); + crypto_cli_encrypt(pipe, args); break; } if(furi_string_cmp_str(cmd, "decrypt") == 0) { - crypto_cli_decrypt(cli, args); + crypto_cli_decrypt(pipe, args); break; } if(furi_string_cmp_str(cmd, "has_key") == 0) { - crypto_cli_has_key(cli, args); + crypto_cli_has_key(pipe, args); break; } if(furi_string_cmp_str(cmd, "store_key") == 0) { - crypto_cli_store_key(cli, args); + crypto_cli_store_key(pipe, args); break; } @@ -318,8 +321,8 @@ static void crypto_cli(Cli* cli, FuriString* args, void* context) { void crypto_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command(registry, "crypto", CliCommandFlagDefault, crypto_cli, NULL); furi_record_close(RECORD_CLI); #else UNUSED(crypto_cli); diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 843dbebb0..eed5537c6 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -1,6 +1,5 @@ #include "desktop_i.h" -#include #include #include @@ -30,9 +29,7 @@ static void desktop_loader_callback(const void* message, void* context) { if(event->type == LoaderEventTypeApplicationBeforeLoad) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); - } else if( - event->type == LoaderEventTypeApplicationLoadFailed || - event->type == LoaderEventTypeApplicationStopped) { + } else if(event->type == LoaderEventTypeNoMoreAppsInQueue) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); } } @@ -149,7 +146,7 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { if((desktop->settings.usb_inhibit_auto_lock) && (furi_hal_usb_is_locked())) { return (0); } - + desktop_lock(desktop); } } else if(event == DesktopGlobalSaveSettings) { @@ -405,9 +402,9 @@ void desktop_lock(Desktop* desktop) { furi_hal_rtc_set_flag(FuriHalRtcFlagLock); if(desktop_pin_code_is_set()) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_record_close(RECORD_CLI); + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_disable(cli_vcp); + furi_record_close(RECORD_CLI_VCP); } desktop_auto_lock_inhibit(desktop); @@ -435,9 +432,9 @@ void desktop_unlock(Desktop* desktop) { furi_hal_rtc_set_pin_fails(0); if(desktop_pin_code_is_set()) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_enable(cli_vcp); + furi_record_close(RECORD_CLI_VCP); } DesktopStatus status = {.locked = false}; @@ -534,6 +531,10 @@ int32_t desktop_srv(void* p) { if(desktop_pin_code_is_set()) { desktop_lock(desktop); + } else { + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_enable(cli_vcp); + furi_record_close(RECORD_CLI_VCP); } if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) { diff --git a/applications/services/desktop/desktop_settings.c b/applications/services/desktop/desktop_settings.c index f5d0cf896..bc85b29b5 100644 --- a/applications/services/desktop/desktop_settings.c +++ b/applications/services/desktop/desktop_settings.c @@ -7,7 +7,7 @@ #define TAG "DesktopSettings" #define DESKTOP_SETTINGS_VER_14 (14) -#define DESKTOP_SETTINGS_VER (16) +#define DESKTOP_SETTINGS_VER (17) #define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME) #define DESKTOP_SETTINGS_MAGIC (0x17) diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h index 784b1eeba..6f81d99cb 100644 --- a/applications/services/desktop/desktop_settings.h +++ b/applications/services/desktop/desktop_settings.h @@ -14,6 +14,7 @@ typedef enum { FavoriteAppLeftLong, FavoriteAppRightShort, FavoriteAppRightLong, + FavoriteAppOkLong, FavoriteAppNumber, } FavoriteAppShortcut; diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index 4fdcc3400..b329cc16d 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -174,6 +174,11 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { desktop, &desktop->settings.favorite_apps[FavoriteAppRightLong]); consumed = true; break; + case DesktopMainEventOpenFavoriteOkLong: + desktop_scene_main_start_favorite( + desktop, &desktop->settings.favorite_apps[FavoriteAppOkLong]); + consumed = true; + break; case DesktopAnimationEventCheckAnimation: animation_manager_check_blocking_process(desktop->animation_manager); diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index ba91a30cc..8eeea00e2 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -8,6 +8,7 @@ typedef enum { DesktopMainEventOpenFavoriteLeftLong, DesktopMainEventOpenFavoriteRightShort, DesktopMainEventOpenFavoriteRightLong, + DesktopMainEventOpenFavoriteOkLong, DesktopMainEventOpenMenu, DesktopMainEventOpenDebug, DesktopMainEventOpenPowerOff, diff --git a/applications/services/desktop/views/desktop_view_main.c b/applications/services/desktop/views/desktop_view_main.c index 57cfa1a3e..9ea144364 100644 --- a/applications/services/desktop/views/desktop_view_main.c +++ b/applications/services/desktop/views/desktop_view_main.c @@ -74,6 +74,7 @@ bool desktop_main_input_callback(InputEvent* event, void* context) { if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { main_view->callback(DesktopAnimationEventNewIdleAnimation, main_view->context); } + main_view->callback(DesktopMainEventOpenFavoriteOkLong, main_view->context); } } } else { diff --git a/applications/services/expansion/application.fam b/applications/services/expansion/application.fam index dbdde0a52..f85450e40 100644 --- a/applications/services/expansion/application.fam +++ b/applications/services/expansion/application.fam @@ -8,5 +8,5 @@ App( ], requires=["rpc_start"], provides=["expansion_settings"], - order=150, + order=100, ) diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index a000efd48..ac2a5935b 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -1,6 +1,8 @@ #include "expansion_worker.h" +#include #include + #include #include @@ -250,9 +252,13 @@ static bool expansion_worker_handle_state_connected( if(!expansion_worker_rpc_session_open(instance)) break; instance->state = ExpansionWorkerStateRpcActive; } else if(command == ExpansionFrameControlCommandEnableOtg) { - if(!furi_hal_power_is_otg_enabled()) furi_hal_power_enable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } else if(command == ExpansionFrameControlCommandDisableOtg) { - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } else { break; } diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 24ea57bc0..68a287310 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -249,7 +249,7 @@ static void gui_redraw(Gui* gui) { canvas_reset(gui->canvas); - if(gui->lockdown) { + if(gui_is_lockdown(gui)) { gui_redraw_desktop(gui); bool need_attention = (gui_view_port_find_enabled(gui->layers[GuiLayerWindow]) != 0 || @@ -299,7 +299,7 @@ static void gui_input(Gui* gui, InputEvent* input_event) { ViewPort* view_port = NULL; - if(gui->lockdown) { + if(gui_is_lockdown(gui)) { view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); } else { view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]); @@ -495,6 +495,23 @@ void gui_set_lockdown(Gui* gui, bool lockdown) { gui_update(gui); } +void gui_set_lockdown_inhibit(Gui* gui, bool inhibit) { + furi_check(gui); + + gui_lock(gui); + gui->lockdown_inhibit = inhibit; + gui_unlock(gui); + + // Request redraw + gui_update(gui); +} + +bool gui_is_lockdown(const Gui* gui) { + furi_check(gui); + + return gui->lockdown && !gui->lockdown_inhibit; +} + Canvas* gui_direct_draw_acquire(Gui* gui) { furi_check(gui); diff --git a/applications/services/gui/gui.h b/applications/services/gui/gui.h index 1b5987eda..7eb19b91d 100644 --- a/applications/services/gui/gui.h +++ b/applications/services/gui/gui.h @@ -110,6 +110,23 @@ size_t gui_get_framebuffer_size(const Gui* gui); */ void gui_set_lockdown(Gui* gui, bool lockdown); +/** Inhibit lockdown mode + * + * Lockdown mode can be inhibited by calling this function with inhibit set to true. + * This is used to show information even when flipper is locked. + * + * @param gui Gui instance + * @param inhibit true to inhibit lockdown mode + */ +void gui_set_lockdown_inhibit(Gui* gui, bool inhibit); + +/** Check if Gui is in lockdown mode + * + * @param gui Gui instance + * @return bool true if Gui is in lockdown mode + */ +bool gui_is_lockdown(const Gui* gui); + /** Acquire Direct Draw lock and get Canvas instance * * This method return Canvas instance for use in monopoly mode. Direct draw lock diff --git a/applications/services/gui/gui_i.h b/applications/services/gui/gui_i.h index 8bd3215f9..f146ad1fc 100644 --- a/applications/services/gui/gui_i.h +++ b/applications/services/gui/gui_i.h @@ -51,6 +51,7 @@ struct Gui { // Layers and Canvas bool lockdown; + bool lockdown_inhibit; bool direct_draw; ViewPortArray_t layers[GuiLayerMAX]; Canvas* canvas; diff --git a/applications/services/gui/modules/button_panel.c b/applications/services/gui/modules/button_panel.c index 341cef525..6e95c58a1 100644 --- a/applications/services/gui/modules/button_panel.c +++ b/applications/services/gui/modules/button_panel.c @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -46,6 +47,7 @@ ARRAY_DEF(ButtonMatrix, ButtonArray_t); struct ButtonPanel { View* view; + bool freeze_input; }; typedef struct { @@ -63,7 +65,7 @@ static void button_panel_process_up(ButtonPanel* button_panel); static void button_panel_process_down(ButtonPanel* button_panel); static void button_panel_process_left(ButtonPanel* button_panel); static void button_panel_process_right(ButtonPanel* button_panel); -static void button_panel_process_ok(ButtonPanel* button_panel); +static void button_panel_process_ok(ButtonPanel* button_panel, InputType input); static void button_panel_view_draw_callback(Canvas* canvas, void* _model); static bool button_panel_view_input_callback(InputEvent* event, void* context); @@ -358,7 +360,7 @@ static void button_panel_process_right(ButtonPanel* button_panel) { true); } -void button_panel_process_ok(ButtonPanel* button_panel) { +void button_panel_process_ok(ButtonPanel* button_panel, InputType type) { ButtonItem* button_item = NULL; with_view_model( @@ -371,7 +373,7 @@ void button_panel_process_ok(ButtonPanel* button_panel) { true); if(button_item && button_item->callback) { - button_item->callback(button_item->callback_context, button_item->index); + button_item->callback(button_item->callback_context, button_item->index, type); } } @@ -379,8 +381,15 @@ static bool button_panel_view_input_callback(InputEvent* event, void* context) { ButtonPanel* button_panel = context; furi_assert(button_panel); bool consumed = false; - - if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + if((event->type == InputTypePress) || (event->type == InputTypeRelease)) { + button_panel->freeze_input = (event->type == InputTypePress); + } + consumed = true; + button_panel_process_ok(button_panel, event->type); + } + if(!button_panel->freeze_input && + (!(event->type == InputTypePress) && !(event->type == InputTypeRelease))) { switch(event->key) { case InputKeyUp: consumed = true; @@ -398,10 +407,6 @@ static bool button_panel_view_input_callback(InputEvent* event, void* context) { consumed = true; button_panel_process_right(button_panel); break; - case InputKeyOk: - consumed = true; - button_panel_process_ok(button_panel); - break; default: break; } diff --git a/applications/services/gui/modules/button_panel.h b/applications/services/gui/modules/button_panel.h index daba51d3e..cf5a0a3c6 100644 --- a/applications/services/gui/modules/button_panel.h +++ b/applications/services/gui/modules/button_panel.h @@ -15,7 +15,7 @@ extern "C" { typedef struct ButtonPanel ButtonPanel; /** Callback type to call for handling selecting button_panel items */ -typedef void (*ButtonItemCallback)(void* context, uint32_t index); +typedef void (*ButtonItemCallback)(void* context, uint32_t index, InputType type); /** Allocate new button_panel module. * diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 74f93320f..648e213c0 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -13,8 +13,12 @@ struct Submenu { typedef struct { FuriString* label; uint32_t index; - SubmenuItemCallback callback; + union { + SubmenuItemCallback callback; + SubmenuItemCallbackEx callback_ex; + }; void* callback_context; + bool has_extended_events; bool locked; FuriString* locked_message; } SubmenuItem; @@ -70,7 +74,7 @@ typedef struct { static void submenu_process_up(Submenu* submenu); static void submenu_process_down(Submenu* submenu); -static void submenu_process_ok(Submenu* submenu); +static void submenu_process_ok(Submenu* submenu, InputType input_type); static size_t submenu_items_on_screen(bool header, bool vertical) { size_t res = (vertical) ? 8 : 4; @@ -196,6 +200,9 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { with_view_model( submenu->view, SubmenuModel * model, { model->locked_message_visible = false; }, true); consumed = true; + } else if(event->key == InputKeyOk) { + consumed = true; + submenu_process_ok(submenu, event->type); } else if(event->type == InputTypeShort) { switch(event->key) { case InputKeyUp: @@ -206,10 +213,6 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { consumed = true; submenu_process_down(submenu); break; - case InputKeyOk: - consumed = true; - submenu_process_ok(submenu); - break; default: break; } @@ -310,6 +313,7 @@ void submenu_add_lockable_item( item->index = index; item->callback = callback; item->callback_context = callback_context; + item->has_extended_events = false; item->locked = locked; if(locked) { furi_string_set_str(item->locked_message, locked_message); @@ -318,6 +322,30 @@ void submenu_add_lockable_item( true); } +void submenu_add_item_ex( + Submenu* submenu, + const char* label, + uint32_t index, + SubmenuItemCallbackEx callback, + void* callback_context) { + SubmenuItem* item = NULL; + furi_check(label); + furi_check(submenu); + + with_view_model( + submenu->view, + SubmenuModel * model, + { + item = SubmenuItemArray_push_new(model->items); + furi_string_set_str(item->label, label); + item->index = index; + item->callback_ex = callback; + item->callback_context = callback_context; + item->has_extended_events = true; + }, + true); +} + void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label) { furi_check(submenu); furi_check(label); @@ -465,7 +493,7 @@ void submenu_process_down(Submenu* submenu) { true); } -void submenu_process_ok(Submenu* submenu) { +void submenu_process_ok(Submenu* submenu, InputType input_type) { SubmenuItem* item = NULL; with_view_model( @@ -483,8 +511,15 @@ void submenu_process_ok(Submenu* submenu) { }, true); - if(item && !item->locked && item->callback) { + if(!item) return; + if(item->locked) { + return; + } + + if(!item->has_extended_events && input_type == InputTypeShort && item->callback) { item->callback(item->callback_context, item->index); + } else if(item->has_extended_events && item->callback_ex) { + item->callback_ex(item->callback_context, input_type, item->index); } } diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index d77f570c2..750167543 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -14,6 +14,7 @@ extern "C" { /** Submenu anonymous structure */ typedef struct Submenu Submenu; typedef void (*SubmenuItemCallback)(void* context, uint32_t index); +typedef void (*SubmenuItemCallbackEx)(void* context, InputType input_type, uint32_t index); /** Allocate and initialize submenu * @@ -73,6 +74,22 @@ void submenu_add_lockable_item( bool locked, const char* locked_message); +/** Add item to submenu with extended press events + * + * @param submenu Submenu instance + * @param label menu item label + * @param index menu item index, used for callback, may be + * the same with other items + * @param callback menu item extended callback + * @param callback_context menu item callback context + */ +void submenu_add_item_ex( + Submenu* submenu, + const char* label, + uint32_t index, + SubmenuItemCallbackEx callback, + void* callback_context); + /** Change label of an existing item * * @param submenu Submenu instance diff --git a/applications/services/gui/modules/widget.c b/applications/services/gui/modules/widget.c index ea3315d8d..f8d2fc346 100644 --- a/applications/services/gui/modules/widget.c +++ b/applications/services/gui/modules/widget.c @@ -195,14 +195,27 @@ void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* i widget_add_element(widget, icon_element); } -void widget_add_frame_element( +void widget_add_rect_element( Widget* widget, uint8_t x, uint8_t y, uint8_t width, uint8_t height, - uint8_t radius) { + uint8_t radius, + bool fill) { furi_check(widget); - WidgetElement* frame_element = widget_element_frame_create(x, y, width, height, radius); - widget_add_element(widget, frame_element); + WidgetElement* rect_element = widget_element_rect_create(x, y, width, height, radius, fill); + widget_add_element(widget, rect_element); +} + +void widget_add_circle_element(Widget* widget, uint8_t x, uint8_t y, uint8_t radius, bool fill) { + furi_check(widget); + WidgetElement* circle_element = widget_element_circle_create(x, y, radius, fill); + widget_add_element(widget, circle_element); +} + +void widget_add_line_element(Widget* widget, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { + furi_check(widget); + WidgetElement* line_element = widget_element_line_create(x1, y1, x2, y2); + widget_add_element(widget, line_element); } diff --git a/applications/services/gui/modules/widget.h b/applications/services/gui/modules/widget.h index 21a939c13..f522b2ffa 100644 --- a/applications/services/gui/modules/widget.h +++ b/applications/services/gui/modules/widget.h @@ -152,21 +152,57 @@ void widget_add_button_element( void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* icon); /** Add Frame Element + * + * @param widget Widget instance + * @param x top left x coordinate + * @param y top left y coordinate + * @param width frame width + * @param height frame height + * @param radius frame radius + * + * @warning deprecated, use widget_add_rect_element instead + */ +#define widget_add_frame_element(widget, x, y, width, height, radius) \ + widget_add_rect_element((widget), (x), (y), (width), (height), (radius), false) + +/** Add Rect Element * * @param widget Widget instance * @param x top left x coordinate * @param y top left y coordinate - * @param width frame width - * @param height frame height - * @param radius frame radius + * @param width rect width + * @param height rect height + * @param radius corner radius + * @param fill whether to fill the box or not */ -void widget_add_frame_element( +void widget_add_rect_element( Widget* widget, uint8_t x, uint8_t y, uint8_t width, uint8_t height, - uint8_t radius); + uint8_t radius, + bool fill); + +/** Add Circle Element + * + * @param widget Widget instance + * @param x center x coordinate + * @param y center y coordinate + * @param radius circle radius + * @param fill whether to fill the circle or not + */ +void widget_add_circle_element(Widget* widget, uint8_t x, uint8_t y, uint8_t radius, bool fill); + +/** Add Line Element + * + * @param widget Widget instance + * @param x1 first x coordinate + * @param y1 first y coordinate + * @param x2 second x coordinate + * @param y2 second y coordinate + */ +void widget_add_line_element(Widget* widget, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); #ifdef __cplusplus } diff --git a/applications/services/gui/modules/widget_elements/widget_element.h b/applications/services/gui/modules/widget_elements/widget_element.h index 473fabd04..8f14053f4 100644 --- a/applications/services/gui/modules/widget_elements/widget_element.h +++ b/applications/services/gui/modules/widget_elements/widget_element.h @@ -5,6 +5,8 @@ #pragma once +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/applications/services/gui/modules/widget_elements/widget_element_circle.c b/applications/services/gui/modules/widget_elements/widget_element_circle.c new file mode 100644 index 000000000..47a952429 --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element_circle.c @@ -0,0 +1,45 @@ +#include "widget_element_i.h" + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t radius; + bool fill; +} GuiCircleModel; + +static void gui_circle_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + GuiCircleModel* model = element->model; + if(model->fill) { + canvas_draw_disc(canvas, model->x, model->y, model->radius); + } else { + canvas_draw_circle(canvas, model->x, model->y, model->radius); + } +} + +static void gui_circle_free(WidgetElement* gui_circle) { + furi_assert(gui_circle); + + free(gui_circle->model); + free(gui_circle); +} + +WidgetElement* widget_element_circle_create(uint8_t x, uint8_t y, uint8_t radius, bool fill) { + // Allocate and init model + GuiCircleModel* model = malloc(sizeof(GuiCircleModel)); + model->x = x; + model->y = y; + model->radius = radius; + model->fill = fill; + + // Allocate and init Element + WidgetElement* gui_circle = malloc(sizeof(WidgetElement)); + gui_circle->parent = NULL; + gui_circle->input = NULL; + gui_circle->draw = gui_circle_draw; + gui_circle->free = gui_circle_free; + gui_circle->model = model; + + return gui_circle; +} diff --git a/applications/services/gui/modules/widget_elements/widget_element_frame.c b/applications/services/gui/modules/widget_elements/widget_element_frame.c deleted file mode 100644 index a7265348e..000000000 --- a/applications/services/gui/modules/widget_elements/widget_element_frame.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "widget_element_i.h" - -typedef struct { - uint8_t x; - uint8_t y; - uint8_t width; - uint8_t height; - uint8_t radius; -} GuiFrameModel; - -static void gui_frame_draw(Canvas* canvas, WidgetElement* element) { - furi_assert(canvas); - furi_assert(element); - GuiFrameModel* model = element->model; - canvas_draw_rframe(canvas, model->x, model->y, model->width, model->height, model->radius); -} - -static void gui_frame_free(WidgetElement* gui_frame) { - furi_assert(gui_frame); - - free(gui_frame->model); - free(gui_frame); -} - -WidgetElement* widget_element_frame_create( - uint8_t x, - uint8_t y, - uint8_t width, - uint8_t height, - uint8_t radius) { - // Allocate and init model - GuiFrameModel* model = malloc(sizeof(GuiFrameModel)); - model->x = x; - model->y = y; - model->width = width; - model->height = height; - model->radius = radius; - - // Allocate and init Element - WidgetElement* gui_frame = malloc(sizeof(WidgetElement)); - gui_frame->parent = NULL; - gui_frame->input = NULL; - gui_frame->draw = gui_frame_draw; - gui_frame->free = gui_frame_free; - gui_frame->model = model; - - return gui_frame; -} diff --git a/applications/services/gui/modules/widget_elements/widget_element_i.h b/applications/services/gui/modules/widget_elements/widget_element_i.h index 2bced5576..adaf09168 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_i.h +++ b/applications/services/gui/modules/widget_elements/widget_element_i.h @@ -73,14 +73,16 @@ WidgetElement* widget_element_button_create( /** Create icon element */ WidgetElement* widget_element_icon_create(uint8_t x, uint8_t y, const Icon* icon); -/** Create frame element */ -WidgetElement* widget_element_frame_create( +/** Create rect element */ +WidgetElement* widget_element_rect_create( uint8_t x, uint8_t y, uint8_t width, uint8_t height, - uint8_t radius); + uint8_t radius, + bool fill); +/** Create text scroll element */ WidgetElement* widget_element_text_scroll_create( uint8_t x, uint8_t y, @@ -88,6 +90,12 @@ WidgetElement* widget_element_text_scroll_create( uint8_t height, const char* text); +/** Create circle element */ +WidgetElement* widget_element_circle_create(uint8_t x, uint8_t y, uint8_t radius, bool fill); + +/** Create line element */ +WidgetElement* widget_element_line_create(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); + #ifdef __cplusplus } #endif diff --git a/applications/services/gui/modules/widget_elements/widget_element_line.c b/applications/services/gui/modules/widget_elements/widget_element_line.c new file mode 100644 index 000000000..4cccdc976 --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element_line.c @@ -0,0 +1,41 @@ +#include "widget_element_i.h" + +typedef struct { + uint8_t x1; + uint8_t y1; + uint8_t x2; + uint8_t y2; +} GuiLineModel; + +static void gui_line_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + GuiLineModel* model = element->model; + canvas_draw_line(canvas, model->x1, model->y1, model->x2, model->y2); +} + +static void gui_line_free(WidgetElement* gui_line) { + furi_assert(gui_line); + + free(gui_line->model); + free(gui_line); +} + +WidgetElement* widget_element_line_create(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { + // Allocate and init model + GuiLineModel* model = malloc(sizeof(GuiLineModel)); + model->x1 = x1; + model->y1 = y1; + model->x2 = x2; + model->y2 = y2; + + // Allocate and init Element + WidgetElement* gui_line = malloc(sizeof(WidgetElement)); + gui_line->parent = NULL; + gui_line->input = NULL; + gui_line->draw = gui_line_draw; + gui_line->free = gui_line_free; + gui_line->model = model; + + return gui_line; +} diff --git a/applications/services/gui/modules/widget_elements/widget_element_rect.c b/applications/services/gui/modules/widget_elements/widget_element_rect.c new file mode 100644 index 000000000..6539f09a8 --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element_rect.c @@ -0,0 +1,55 @@ +#include "widget_element_i.h" + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t width; + uint8_t height; + uint8_t radius; + bool fill; +} GuiRectModel; + +static void gui_rect_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + GuiRectModel* model = element->model; + if(model->fill) { + canvas_draw_rbox(canvas, model->x, model->y, model->width, model->height, model->radius); + } else { + canvas_draw_rframe(canvas, model->x, model->y, model->width, model->height, model->radius); + } +} + +static void gui_rect_free(WidgetElement* gui_rect) { + furi_assert(gui_rect); + + free(gui_rect->model); + free(gui_rect); +} + +WidgetElement* widget_element_rect_create( + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + uint8_t radius, + bool fill) { + // Allocate and init model + GuiRectModel* model = malloc(sizeof(GuiRectModel)); + model->x = x; + model->y = y; + model->width = width; + model->height = height; + model->radius = radius; + model->fill = fill; + + // Allocate and init Element + WidgetElement* gui_rect = malloc(sizeof(WidgetElement)); + gui_rect->parent = NULL; + gui_rect->input = NULL; + gui_rect->draw = gui_rect_draw; + gui_rect->free = gui_rect_free; + gui_rect->model = model; + + return gui_rect; +} diff --git a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c index 4c9c39dff..491ffc6bc 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c +++ b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c @@ -19,8 +19,8 @@ typedef struct { uint8_t width; uint8_t height; FuriString* text; - uint8_t scroll_pos_total; - uint8_t scroll_pos_current; + uint16_t scroll_pos_total; + uint16_t scroll_pos_current; bool text_formatted; } WidgetElementTextScrollModel; diff --git a/applications/services/gui/scene_manager.c b/applications/services/gui/scene_manager.c index 35556e322..47d168007 100644 --- a/applications/services/gui/scene_manager.c +++ b/applications/services/gui/scene_manager.c @@ -230,6 +230,11 @@ bool scene_manager_search_and_switch_to_another_scene( } } +uint32_t scene_manager_get_current_scene(SceneManager* scene_manager) { + furi_check(scene_manager); + return *SceneManagerIdStack_back(scene_manager->scene_id_stack); +} + void scene_manager_stop(SceneManager* scene_manager) { furi_check(scene_manager); diff --git a/applications/services/gui/scene_manager.h b/applications/services/gui/scene_manager.h index 54dfa9cd4..8dad92aac 100644 --- a/applications/services/gui/scene_manager.h +++ b/applications/services/gui/scene_manager.h @@ -170,6 +170,14 @@ bool scene_manager_search_and_switch_to_another_scene( SceneManager* scene_manager, uint32_t scene_id); +/** Get id of current scene + * + * @param scene_manager SceneManager instance + * + * @return Scene ID + */ +uint32_t scene_manager_get_current_scene(SceneManager* scene_manager); + /** Exit from current scene * * @param scene_manager SceneManager instance diff --git a/applications/services/input/application.fam b/applications/services/input/application.fam index d344fc350..af6a91339 100644 --- a/applications/services/input/application.fam +++ b/applications/services/input/application.fam @@ -7,4 +7,5 @@ App( stack_size=1 * 1024, order=80, sdk_headers=["input.h"], + provides=["input_settings"], ) diff --git a/applications/services/input/input.c b/applications/services/input/input.c index 6cbafb795..c3f7c264f 100644 --- a/applications/services/input/input.c +++ b/applications/services/input/input.c @@ -4,14 +4,19 @@ #include #include #include -#include #include +#include +#include +#include +#include #define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2) #define INPUT_PRESS_TICKS 150 #define INPUT_LONG_PRESS_COUNTS 2 #define INPUT_THREAD_FLAG_ISR 0x00000001 +#define TAG "Input" + /** Input pin state */ typedef struct { const InputPin* pin; @@ -25,7 +30,7 @@ typedef struct { } InputPinState; /** Input CLI command handler */ -void input_cli(Cli* cli, FuriString* args, void* context); +void input_cli(PipeSide* pipe, FuriString* args, void* context); // #define INPUT_DEBUG @@ -87,13 +92,20 @@ int32_t input_srv(void* p) { uint32_t counter = 1; furi_record_create(RECORD_INPUT_EVENTS, event_pubsub); + //define object input_settings, take memory load (or init) settings and create record for access to settings structure from outside + InputSettings* settings = malloc(sizeof(InputSettings)); + furi_record_create(RECORD_INPUT_SETTINGS, settings); + input_settings_load(settings); + #ifdef INPUT_DEBUG furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull); #endif #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command( + registry, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub); + furi_record_close(RECORD_CLI); #endif InputPinState pin_states[input_pins_count]; @@ -149,6 +161,14 @@ int32_t input_srv(void* p) { // Send Press/Release event event.type = pin_states[i].state ? InputTypePress : InputTypeRelease; furi_pubsub_publish(event_pubsub, &event); + // vibro signal if user setup vibro touch level in Settings-Input. + if(settings->vibro_touch_level) { + //delay 1 ticks for compatibility with rgb_backlight_mod + furi_delay_tick(1); + furi_hal_vibro_on(true); + furi_delay_tick(settings->vibro_touch_level); + furi_hal_vibro_on(false); + }; } } diff --git a/applications/services/input/input.h b/applications/services/input/input.h index 5233b4a01..92dbfeb68 100644 --- a/applications/services/input/input.h +++ b/applications/services/input/input.h @@ -6,12 +6,15 @@ #pragma once #include +#include "input_settings.h" +#include #ifdef __cplusplus extern "C" { #endif #define RECORD_INPUT_EVENTS "input_events" +#define RECORD_INPUT_SETTINGS "input_settings" #define INPUT_SEQUENCE_SOURCE_HARDWARE (0u) #define INPUT_SEQUENCE_SOURCE_SOFTWARE (1u) diff --git a/applications/services/input/input_cli.c b/applications/services/input/input_cli.c index 8e711c895..d5ea57418 100644 --- a/applications/services/input/input_cli.c +++ b/applications/services/input/input_cli.c @@ -1,8 +1,9 @@ #include "input.h" #include -#include +#include #include +#include static void input_cli_usage(void) { printf("Usage:\r\n"); @@ -19,7 +20,7 @@ static void input_cli_dump_events_callback(const void* value, void* ctx) { furi_message_queue_put(input_queue, value, FuriWaitForever); } -static void input_cli_dump(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) { +static void input_cli_dump(PipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) { UNUSED(args); FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); FuriPubSubSubscription* input_subscription = @@ -27,7 +28,7 @@ static void input_cli_dump(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) InputEvent input_event; printf("Press CTRL+C to stop\r\n"); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { if(furi_message_queue_get(input_queue, &input_event, 100) == FuriStatusOk) { printf( "key: %s type: %s\r\n", @@ -47,8 +48,8 @@ static void input_cli_send_print_usage(void) { printf("\t\t \t - one of 'press', 'release', 'short', 'long'\r\n"); } -static void input_cli_send(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) { - UNUSED(cli); +static void input_cli_send(PipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) { + UNUSED(pipe); InputEvent event; FuriString* key_str; key_str = furi_string_alloc(); @@ -97,8 +98,7 @@ static void input_cli_send(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) furi_string_free(key_str); } -void input_cli(Cli* cli, FuriString* args, void* context) { - furi_assert(cli); +void input_cli(PipeSide* pipe, FuriString* args, void* context) { furi_assert(context); FuriPubSub* event_pubsub = context; FuriString* cmd; @@ -110,11 +110,11 @@ void input_cli(Cli* cli, FuriString* args, void* context) { break; } if(furi_string_cmp_str(cmd, "dump") == 0) { - input_cli_dump(cli, args, event_pubsub); + input_cli_dump(pipe, args, event_pubsub); break; } if(furi_string_cmp_str(cmd, "send") == 0) { - input_cli_send(cli, args, event_pubsub); + input_cli_send(pipe, args, event_pubsub); break; } diff --git a/applications/services/input/input_settings.c b/applications/services/input/input_settings.c new file mode 100644 index 000000000..f1f18ba3d --- /dev/null +++ b/applications/services/input/input_settings.c @@ -0,0 +1,58 @@ +#include "input_settings.h" +#include "input_settings_filename.h" + +#include +#include + +#define TAG "InputSettings" + +#define INPUT_SETTINGS_VER (1) // version nnumber + +#define INPUT_SETTINGS_PATH INT_PATH(INPUT_SETTINGS_FILE_NAME) +#define INPUT_SETTINGS_MAGIC (0x29) + +void input_settings_load(InputSettings* settings) { + furi_assert(settings); + + bool success = false; + + //a useless cycle do-while, may will be used in future with anoter condition + do { + // take version from settings file metadata, if cant then break and fill settings with 0 and save to settings file; + uint8_t version; + if(!saved_struct_get_metadata(INPUT_SETTINGS_PATH, NULL, &version, NULL)) break; + + // if config actual version - load it directly + if(version == INPUT_SETTINGS_VER) { + success = saved_struct_load( + INPUT_SETTINGS_PATH, + settings, + sizeof(InputSettings), + INPUT_SETTINGS_MAGIC, + INPUT_SETTINGS_VER); + } + // in case of another config version we exit from useless cycle to next step + } while(false); + + // fill settings with 0 and save to settings file; + if(!success) { + FURI_LOG_W(TAG, "Failed to load file, using defaults"); + memset(settings, 0, sizeof(InputSettings)); + input_settings_save(settings); + } +} + +void input_settings_save(const InputSettings* settings) { + furi_assert(settings); + + const bool success = saved_struct_save( + INPUT_SETTINGS_PATH, + settings, + sizeof(InputSettings), + INPUT_SETTINGS_MAGIC, + INPUT_SETTINGS_VER); + + if(!success) { + FURI_LOG_E(TAG, "Failed to save file"); + } +} diff --git a/applications/services/input/input_settings.h b/applications/services/input/input_settings.h new file mode 100644 index 000000000..376e8e226 --- /dev/null +++ b/applications/services/input/input_settings.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +typedef struct { + uint8_t vibro_touch_level; +} InputSettings; + +#ifdef __cplusplus +extern "C" { +#endif + +void input_settings_load(InputSettings* settings); +void input_settings_save(const InputSettings* settings); + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/input/input_settings_filename.h b/applications/services/input/input_settings_filename.h new file mode 100644 index 000000000..025ed6ad5 --- /dev/null +++ b/applications/services/input/input_settings_filename.h @@ -0,0 +1,3 @@ +#pragma once + +#define INPUT_SETTINGS_FILE_NAME ".input.settings" diff --git a/applications/services/loader/application.fam b/applications/services/loader/application.fam index f4d006e07..5600bdf62 100644 --- a/applications/services/loader/application.fam +++ b/applications/services/loader/application.fam @@ -19,5 +19,5 @@ App( apptype=FlipperAppType.STARTUP, entry_point="loader_on_system_start", requires=["loader"], - order=90, + order=80, ) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 102bcf325..01de5af21 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -168,6 +168,13 @@ static void loader_show_gui_error( furi_record_close(RECORD_DIALOGS); } +static void loader_generic_synchronous_request(Loader* loader, LoaderMessage* message) { + furi_check(loader); + message->api_lock = api_lock_alloc_locked(); + furi_message_queue_put(loader->queue, message, FuriWaitForever); + api_lock_wait_unlock_and_free(message->api_lock); +} + LoaderStatus loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) { furi_check(loader); @@ -203,16 +210,12 @@ void loader_start_detached_with_gui_error(Loader* loader, const char* name, cons } bool loader_lock(Loader* loader) { - furi_check(loader); - - LoaderMessage message; LoaderMessageBoolResult result; - message.type = LoaderMessageTypeLock; - message.api_lock = api_lock_alloc_locked(); - message.bool_value = &result; - furi_message_queue_put(loader->queue, &message, FuriWaitForever); - api_lock_wait_unlock_and_free(message.api_lock); - + LoaderMessage message = { + .type = LoaderMessageTypeLock, + .bool_value = &result, + }; + loader_generic_synchronous_request(loader, &message); return result.value; } @@ -226,16 +229,12 @@ void loader_unlock(Loader* loader) { } bool loader_is_locked(Loader* loader) { - furi_check(loader); - - LoaderMessage message; LoaderMessageBoolResult result; - message.type = LoaderMessageTypeIsLocked; - message.api_lock = api_lock_alloc_locked(); - message.bool_value = &result; - furi_message_queue_put(loader->queue, &message, FuriWaitForever); - api_lock_wait_unlock_and_free(message.api_lock); - + LoaderMessage message = { + .type = LoaderMessageTypeIsLocked, + .bool_value = &result, + }; + loader_generic_synchronous_request(loader, &message); return result.value; } @@ -257,42 +256,63 @@ FuriPubSub* loader_get_pubsub(Loader* loader) { } bool loader_signal(Loader* loader, uint32_t signal, void* arg) { - furi_check(loader); - LoaderMessageBoolResult result; - LoaderMessage message = { .type = LoaderMessageTypeSignal, - .api_lock = api_lock_alloc_locked(), .signal.signal = signal, .signal.arg = arg, .bool_value = &result, }; - - furi_message_queue_put(loader->queue, &message, FuriWaitForever); - api_lock_wait_unlock_and_free(message.api_lock); - + loader_generic_synchronous_request(loader, &message); return result.value; } bool loader_get_application_name(Loader* loader, FuriString* name) { - furi_check(loader); - LoaderMessageBoolResult result; - LoaderMessage message = { .type = LoaderMessageTypeGetApplicationName, - .api_lock = api_lock_alloc_locked(), .application_name = name, .bool_value = &result, }; - - furi_message_queue_put(loader->queue, &message, FuriWaitForever); - api_lock_wait_unlock_and_free(message.api_lock); - + loader_generic_synchronous_request(loader, &message); return result.value; } +bool loader_get_application_launch_path(Loader* loader, FuriString* name) { + LoaderMessageBoolResult result; + LoaderMessage message = { + .type = LoaderMessageTypeGetApplicationLaunchPath, + .application_name = name, + .bool_value = &result, + }; + loader_generic_synchronous_request(loader, &message); + return result.value; +} + +void loader_enqueue_launch( + Loader* loader, + const char* name, + const char* args, + LoaderDeferredLaunchFlag flags) { + LoaderMessage message = { + .type = LoaderMessageTypeEnqueueLaunch, + .defer_start = + { + .name_or_path = strdup(name), + .args = args ? strdup(args) : NULL, + .flags = flags, + }, + }; + loader_generic_synchronous_request(loader, &message); +} + +void loader_clear_launch_queue(Loader* loader) { + LoaderMessage message = { + .type = LoaderMessageTypeClearLaunchQueue, + }; + loader_generic_synchronous_request(loader, &message); +} + // callbacks static void loader_menu_closed_callback(void* context) { @@ -329,12 +349,10 @@ static Loader* loader_alloc(void) { Loader* loader = malloc(sizeof(Loader)); loader->pubsub = furi_pubsub_alloc(); loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage)); - loader->loader_menu = NULL; - loader->loader_applications = NULL; - loader->app.args = NULL; - loader->app.thread = NULL; - loader->app.insomniac = false; - loader->app.fap = NULL; + loader->gui = furi_record_open(RECORD_GUI); + loader->view_holder = view_holder_alloc(); + loader->loading = loading_alloc(); + view_holder_attach_to_gui(loader->view_holder, loader->gui); return loader; } @@ -402,9 +420,6 @@ static void loader_start_internal_app( const FlipperInternalApplication* app, const char* args) { FURI_LOG_I(TAG, "Starting %s", app->name); - LoaderEvent event; - event.type = LoaderEventTypeApplicationBeforeLoad; - furi_pubsub_publish(loader->pubsub, &event); // store args furi_assert(loader->app.args == NULL); @@ -493,10 +508,6 @@ static LoaderMessageLoaderStatusResult loader_start_external_app( result.value = loader_make_success_status(error_message); result.error = LoaderStatusErrorUnknown; - LoaderEvent event; - event.type = LoaderEventTypeApplicationBeforeLoad; - furi_pubsub_publish(loader->pubsub, &event); - do { loader->app.fap = flipper_application_alloc(storage, firmware_api_interface); size_t start = furi_get_tick(); @@ -593,6 +604,7 @@ static LoaderMessageLoaderStatusResult loader_start_external_app( if(result.value != LoaderStatusOk) { flipper_application_free(loader->app.fap); loader->app.fap = NULL; + LoaderEvent event; event.type = LoaderEventTypeApplicationLoadFailed; furi_pubsub_publish(loader->pubsub, &event); } @@ -642,6 +654,10 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name( status.value = loader_make_success_status(error_message); status.error = LoaderStatusErrorUnknown; + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); + do { // check lock if(loader_do_is_locked(loader)) { @@ -706,6 +722,10 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name( LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name); } while(false); + if(status.value == LoaderStatusOk) { + loader->app.launch_path = furi_string_alloc_set_str(name); + } + return status; } @@ -723,6 +743,58 @@ static void loader_do_unlock(Loader* loader) { loader->app.thread = NULL; } +static void loader_do_emit_queue_empty_event(Loader* loader) { + if(loader_do_is_locked(loader)) return; + FURI_LOG_I(TAG, "Launch queue empty"); + LoaderEvent event; + event.type = LoaderEventTypeNoMoreAppsInQueue; + furi_pubsub_publish(loader->pubsub, &event); +} + +static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record); + +static void loader_do_next_deferred_launch_if_available(Loader* loader) { + LoaderDeferredLaunchRecord record; + if(loader_queue_pop(&loader->launch_queue, &record)) { + loader_do_deferred_launch(loader, &record); + loader_queue_item_clear(&record); + } else { + loader_do_emit_queue_empty_event(loader); + } +} + +static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record) { + furi_assert(loader); + furi_assert(record); + + bool is_successful = false; + FuriString* error_message = furi_string_alloc(); + view_holder_set_view(loader->view_holder, loading_get_view(loader->loading)); + view_holder_send_to_front(loader->view_holder); + + do { + const char* app_name_str = record->name_or_path; + const char* app_args = record->args; + FURI_LOG_I(TAG, "Deferred launch: %s", app_name_str); + + LoaderMessageLoaderStatusResult result = + loader_do_start_by_name(loader, app_name_str, app_args, error_message); + if(result.value == LoaderStatusOk) { + is_successful = true; + break; + } + + if(record->flags & LoaderDeferredLaunchFlagGui) + loader_show_gui_error(result, app_name_str, error_message); + + loader_do_next_deferred_launch_if_available(loader); + } while(false); + + view_holder_set_view(loader->view_holder, NULL); + furi_string_free(error_message); + return is_successful; +} + static void loader_do_app_closed(Loader* loader) { furi_assert(loader->app.thread); @@ -747,11 +819,15 @@ static void loader_do_app_closed(Loader* loader) { loader->app.thread = NULL; } + furi_string_free(loader->app.launch_path); + FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); LoaderEvent event; event.type = LoaderEventTypeApplicationStopped; furi_pubsub_publish(loader->pubsub, &event); + + loader_do_next_deferred_launch_if_available(loader); } static bool loader_is_application_running(Loader* loader) { @@ -776,6 +852,15 @@ static bool loader_do_get_application_name(Loader* loader, FuriString* name) { return false; } +static bool loader_do_get_application_launch_path(Loader* loader, FuriString* path) { + if(loader_is_application_running(loader)) { + furi_string_set(path, loader->app.launch_path); + return true; + } + + return false; +} + // app int32_t loader_srv(void* p) { @@ -798,16 +883,20 @@ int32_t loader_srv(void* p) { while(true) { if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) { switch(message.type) { - case LoaderMessageTypeStartByName: - *(message.status_value) = loader_do_start_by_name( + case LoaderMessageTypeStartByName: { + LoaderMessageLoaderStatusResult status = loader_do_start_by_name( loader, message.start.name, message.start.args, message.start.error_message); + *(message.status_value) = status; + if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader); api_lock_unlock(message.api_lock); break; + } case LoaderMessageTypeStartByNameDetachedWithGuiError: { FuriString* error_message = furi_string_alloc(); LoaderMessageLoaderStatusResult status = loader_do_start_by_name( loader, message.start.name, message.start.args, error_message); loader_show_gui_error(status, message.start.name, error_message); + if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader); if(message.start.name) free((void*)message.start.name); if(message.start.args) free((void*)message.start.args); furi_string_free(error_message); @@ -846,6 +935,19 @@ int32_t loader_srv(void* p) { loader_do_get_application_name(loader, message.application_name); api_lock_unlock(message.api_lock); break; + case LoaderMessageTypeGetApplicationLaunchPath: + message.bool_value->value = + loader_do_get_application_launch_path(loader, message.application_name); + api_lock_unlock(message.api_lock); + break; + case LoaderMessageTypeEnqueueLaunch: + furi_check(loader_queue_push(&loader->launch_queue, &message.defer_start)); + api_lock_unlock(message.api_lock); + break; + case LoaderMessageTypeClearLaunchQueue: + loader_queue_clear(&loader->launch_queue); + api_lock_unlock(message.api_lock); + break; } } } diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index 567a2483d..67f801308 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -22,13 +22,19 @@ typedef enum { typedef enum { LoaderEventTypeApplicationBeforeLoad, LoaderEventTypeApplicationLoadFailed, - LoaderEventTypeApplicationStopped + LoaderEventTypeApplicationStopped, + LoaderEventTypeNoMoreAppsInQueue, //type == LoaderEventTypeApplicationStopped) { + if(event->type == LoaderEventTypeNoMoreAppsInQueue) { furi_thread_flags_set(thread_id, APPLICATION_STOP_EVENT); } } diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c index f3ea30df2..265779e8f 100644 --- a/applications/services/loader/loader_cli.c +++ b/applications/services/loader/loader_cli.c @@ -1,11 +1,13 @@ #include "loader.h" #include -#include +#include +#include #include #include #include #include +#include static void loader_cli_print_usage(void) { printf("Usage:\r\n"); @@ -110,8 +112,8 @@ static void loader_cli_signal(FuriString* args, Loader* loader) { } } -static void loader_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void loader_cli(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); Loader* loader = furi_record_open(RECORD_LOADER); @@ -140,8 +142,9 @@ static void loader_cli(Cli* cli, FuriString* args, void* context) { void loader_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command( + registry, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL); furi_record_close(RECORD_CLI); #else UNUSED(loader_cli); diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index 92f1e88e0..2bf42c655 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -2,11 +2,20 @@ #include #include #include + +#include +#include +#include + +#include + #include "loader.h" #include "loader_menu.h" #include "loader_applications.h" +#include "loader_queue.h" typedef struct { + FuriString* launch_path; char* args; FuriThread* thread; bool insomniac; @@ -19,6 +28,12 @@ struct Loader { LoaderMenu* loader_menu; LoaderApplications* loader_applications; LoaderAppData app; + + LoaderLaunchQueue launch_queue; + + Gui* gui; + ViewHolder* view_holder; + Loading* loading; }; typedef enum { @@ -33,6 +48,9 @@ typedef enum { LoaderMessageTypeStartByNameDetachedWithGuiError, LoaderMessageTypeSignal, LoaderMessageTypeGetApplicationName, + LoaderMessageTypeGetApplicationLaunchPath, + LoaderMessageTypeEnqueueLaunch, + LoaderMessageTypeClearLaunchQueue, } LoaderMessageType; typedef struct { @@ -72,6 +90,7 @@ typedef struct { union { LoaderMessageStartByName start; + LoaderDeferredLaunchRecord defer_start; LoaderMessageSignal signal; FuriString* application_name; }; diff --git a/applications/services/loader/loader_menu.c b/applications/services/loader/loader_menu.c index ad4a4c7d5..525861f55 100644 --- a/applications/services/loader/loader_menu.c +++ b/applications/services/loader/loader_menu.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "loader.h" #include "loader_menu.h" @@ -71,10 +72,16 @@ static void loader_menu_applications_callback(void* context, uint32_t index) { loader_menu_start(name); } -static void loader_menu_settings_menu_callback(void* context, uint32_t index) { +static void + loader_menu_settings_menu_callback(void* context, InputType input_type, uint32_t index) { UNUSED(context); - const char* name = FLIPPER_SETTINGS_APPS[index].name; - loader_menu_start(name); + if(input_type == InputTypeShort) { + const char* name = FLIPPER_SETTINGS_APPS[index].name; + loader_menu_start(name); + } else if(input_type == InputTypeLong) { + const char* name = FLIPPER_SETTINGS_APPS[index].name; + archive_favorites_handle_setting_pin_unpin(name, NULL); + } } static void loader_menu_switch_to_settings(void* context, uint32_t index) { @@ -130,7 +137,7 @@ static void loader_menu_build_menu(LoaderMenuApp* app, LoaderMenu* menu) { static void loader_menu_build_submenu(LoaderMenuApp* app, LoaderMenu* loader_menu) { for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { - submenu_add_item( + submenu_add_item_ex( app->settings_menu, FLIPPER_SETTINGS_APPS[i].name, i, diff --git a/applications/services/loader/loader_queue.c b/applications/services/loader/loader_queue.c new file mode 100644 index 000000000..517dcad75 --- /dev/null +++ b/applications/services/loader/loader_queue.c @@ -0,0 +1,32 @@ +#include "loader_queue.h" + +void loader_queue_item_clear(LoaderDeferredLaunchRecord* item) { + free(item->args); + free(item->name_or_path); +} + +bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) { + if(!queue->item_cnt) return false; + + *item = queue->items[0]; + queue->item_cnt--; + memmove( + &queue->items[0], &queue->items[1], queue->item_cnt * sizeof(LoaderDeferredLaunchRecord)); + + return true; +} + +bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) { + if(queue->item_cnt == LOADER_QUEUE_MAX_SIZE) return false; + + queue->items[queue->item_cnt] = *item; + queue->item_cnt++; + + return true; +} + +void loader_queue_clear(LoaderLaunchQueue* queue) { + for(size_t i = 0; i < queue->item_cnt; i++) + loader_queue_item_clear(&queue->items[i]); + queue->item_cnt = 0; +} diff --git a/applications/services/loader/loader_queue.h b/applications/services/loader/loader_queue.h new file mode 100644 index 000000000..c40130e39 --- /dev/null +++ b/applications/services/loader/loader_queue.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include "loader.h" + +#define LOADER_QUEUE_MAX_SIZE 4 + +typedef struct { + char* name_or_path; + char* args; + LoaderDeferredLaunchFlag flags; +} LoaderDeferredLaunchRecord; + +typedef struct { + LoaderDeferredLaunchRecord items[LOADER_QUEUE_MAX_SIZE]; + size_t item_cnt; +} LoaderLaunchQueue; + +/** + * @brief Frees internal data in a `DeferredLaunchRecord` + * + * @param[out] item Record to clear + */ +void loader_queue_item_clear(LoaderDeferredLaunchRecord* item); + +/** + * @brief Fetches the next item from the launch queue + * + * @param[inout] queue Queue instance + * @param[out] item Item output + * + * @return `true` if `item` was populated, `false` if queue is empty + */ +bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item); + +/** + * @brief Puts an item into the launch queue + * + * @param[inout] queue Queue instance + * @param[in] item Item to put in the queue + * + * @return `true` if the item was put into the queue, `false` if there's no more + * space left + */ +bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item); + +/** + * @brief Clears the launch queue + * + * @param[inout] queue Queue instance + */ +void loader_queue_clear(LoaderLaunchQueue* queue); diff --git a/applications/services/locale/application.fam b/applications/services/locale/application.fam index c762d02d6..09f49161f 100644 --- a/applications/services/locale/application.fam +++ b/applications/services/locale/application.fam @@ -4,6 +4,6 @@ App( apptype=FlipperAppType.STARTUP, entry_point="locale_on_system_start", cdefines=["SRV_LOCALE"], - order=90, + order=70, sdk_headers=["locale.h"], ) diff --git a/applications/services/namechanger/application.fam b/applications/services/namechanger/application.fam index 0eaeab987..2edeb22df 100644 --- a/applications/services/namechanger/application.fam +++ b/applications/services/namechanger/application.fam @@ -4,5 +4,5 @@ App( entry_point="namechanger_on_system_start", requires=["storage", "cli", "bt"], conflicts=["updater"], - order=600, -) \ No newline at end of file + order=1300, +) diff --git a/applications/services/namechanger/namechanger.c b/applications/services/namechanger/namechanger.c index 904a4d405..6beef8d5e 100644 --- a/applications/services/namechanger/namechanger.c +++ b/applications/services/namechanger/namechanger.c @@ -1,7 +1,6 @@ #include "namechanger.h" #include #include -#include #include #include #include @@ -79,7 +78,7 @@ int32_t namechanger_on_system_start(void* p) { // Wait for all required services to start and create their records uint8_t timeout = 0; - while(!furi_record_exists(RECORD_CLI) || !furi_record_exists(RECORD_BT) || + while(!furi_record_exists(RECORD_CLI_VCP) || !furi_record_exists(RECORD_BT) || !furi_record_exists(RECORD_STORAGE)) { timeout++; if(timeout > 250) { @@ -91,11 +90,11 @@ int32_t namechanger_on_system_start(void* p) { // Hehe bad code now here, bad bad bad, very bad, bad example, dont take it, make it better if(namechanger_init()) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); + CliVcp* cli = furi_record_open(RECORD_CLI_VCP); + cli_vcp_disable(cli); furi_delay_ms(2); // why i added delays here - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); + cli_vcp_enable(cli); + furi_record_close(RECORD_CLI_VCP); furi_delay_ms(3); Bt* bt = furi_record_open(RECORD_BT); diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 35d2fe675..ef9677f31 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -10,7 +10,8 @@ #include "notification_messages.h" #include "notification_app.h" -#define TAG "NotificationSrv" +#define TAG "NotificationSrv" +#define COLOR_COUNT (sizeof(colors) / sizeof(RGBBacklightColor)) static const uint8_t minimal_delay = 100; static const uint8_t led_off_values[NOTIFICATION_LED_COUNT] = {0x00, 0x00, 0x00}; @@ -32,6 +33,248 @@ static uint8_t notification_settings_get_display_brightness(NotificationApp* app static uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value); static uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app); +// --- RGB BACKLIGHT --- + +// local variable for local use +uint8_t rgb_backlight_installed_variable = 0; + +typedef struct { + char* name; + uint8_t red; + uint8_t green; + uint8_t blue; +} RGBBacklightColor; + +// use one type RGBBacklightColor for current_leds_settings and for static colors definition +static RGBBacklightColor current_led[] = { + {"LED0", 0, 0, 0}, + {"LED1", 0, 0, 0}, + {"LED2", 0, 0, 0}, +}; + +static const RGBBacklightColor colors[] = { + {"Orange", 255, 60, 0}, + {"Yellow", 255, 144, 0}, + {"Spring", 167, 255, 0}, + {"Lime", 0, 255, 0}, + {"Aqua", 0, 255, 127}, + {"Cyan", 0, 210, 210}, + {"Azure", 0, 127, 255}, + {"Blue", 0, 0, 255}, + {"Purple", 127, 0, 255}, + {"Magenta", 210, 0, 210}, + {"Pink", 255, 0, 127}, + {"Red", 255, 0, 0}, + {"White", 254, 210, 200}, + {"OFF", 0, 0, 0}, +}; + +uint8_t rgb_backlight_get_color_count(void) { + return COLOR_COUNT; +} + +const char* rgb_backlight_get_color_text(uint8_t index) { + return colors[index].name; +} + +// function for changind local variable from outside; +void set_rgb_backlight_installed_variable(uint8_t var) { + rgb_backlight_installed_variable = var; +} + +// update led current colors by static +void rgb_backlight_set_led_static_color(uint8_t led, uint8_t index) { + if(led < SK6805_get_led_count()) { + uint8_t r = colors[index].red; + uint8_t g = colors[index].green; + uint8_t b = colors[index].blue; + + current_led[led].red = r; + current_led[led].green = g; + current_led[led].blue = b; + + SK6805_set_led_color(led, r, g, b); + } +} + +// HSV to RGB based on +// https://www.radiokot.ru/forum/viewtopic.php?p=3000181&ysclid=m88wvoz34w244644702 +// https://radiolaba.ru/microcotrollers/tsvetnaya-lampa.html#comment-1790 +// https://alexgyver.ru/lessons/arduino-rgb/?ysclid=m88voflppa24464916 +// led number (0-2), hue (0..255), sat (0..255), val (0...1) +void rgb_backlight_set_led_custom_hsv_color(uint8_t led, uint16_t hue, uint8_t sat, float V) { + // init value + float r = 1.0f; + float g = 1.0f; + float b = 1.0f; + + // from (0..255) to (0..1) + float H = hue / 255.0f; + float S = sat / 255.0f; + + uint8_t i = trunc(H * 6); + float f = H * 6 - i; + float p = V * (1 - S); + float q = V * (1 - f * S); + float t = V * (1 - (1 - f) * S); + + switch(i) { + case 0: + r = V, g = t, b = p; + break; + case 1: + r = q, g = V, b = p; + break; + case 2: + r = p, g = V, b = t; + break; + case 3: + r = p, g = q, b = V; + break; + case 4: + r = t, g = p, b = V; + break; + case 5: + r = V, g = p, b = q; + break; + } + + // from (0..1) to (0..255) + current_led[led].red = r * 255; + current_led[led].green = g * 255; + current_led[led].blue = b * 255; +} + +// set current_* colors to led and update backlight +void rgb_backlight_update(float brightness) { + if(rgb_backlight_installed_variable > 0) { + for(uint8_t i = 0; i < SK6805_get_led_count(); i++) { + uint8_t r = current_led[i].red * brightness * 1.0f; + uint8_t g = current_led[i].green * brightness * 1.0f; + uint8_t b = current_led[i].blue * brightness * 1.0f; + SK6805_set_led_color(i, r, g, b); + } + SK6805_update(); + } +} + +// start furi timer for rainbow +void rainbow_timer_start(NotificationApp* app) { + if(furi_timer_is_running(app->rainbow_timer)) { + furi_timer_stop(app->rainbow_timer); + } + furi_timer_start(app->rainbow_timer, furi_ms_to_ticks(app->settings.rgb.rainbow_speed_ms)); +} + +// stop furi timer for rainbow +void rainbow_timer_stop(NotificationApp* app) { + if(furi_timer_is_running(app->rainbow_timer)) { + furi_timer_stop(app->rainbow_timer); + } +} + +// if rgb_backlight_installed then apply rainbow colors to backlight and start/restart/stop rainbow_timer +void rainbow_timer_starter(NotificationApp* app) { + if((app->settings.rgb.rainbow_mode > 0) && (app->settings.rgb.rgb_backlight_installed)) { + rainbow_timer_start(app); + } +} + +static void rainbow_timer_callback(void* context) { + furi_assert(context); + NotificationApp* app = context; + + if(app->settings.rgb.rgb_backlight_installed) { + app->rainbow_hue += app->settings.rgb.rainbow_step; + if(app->rainbow_hue > 254) { + app->rainbow_hue = 0; + } + + uint8_t wide = app->settings.rgb.rainbow_wide; + + switch(app->settings.rgb.rainbow_mode) { + //rainbow mode + case 1: + for(uint8_t i = 0; i < SK6805_get_led_count(); i++) { + rgb_backlight_set_led_custom_hsv_color( + i, + app->rainbow_hue, + app->settings.rgb.rainbow_saturation, + app->settings.display_brightness); + } + break; + + //wave mode + case 2: + uint16_t j = app->rainbow_hue + wide; + uint16_t k = app->rainbow_hue + wide * 2; + + if(app->rainbow_hue > (254 - wide)) { + j = j - 255; + } + if(app->rainbow_hue > (254 - wide * 2)) { + k = k - 255; + } + + rgb_backlight_set_led_custom_hsv_color( + 0, + app->rainbow_hue, + app->settings.rgb.rainbow_saturation, + app->settings.display_brightness); + rgb_backlight_set_led_custom_hsv_color( + 1, j, app->settings.rgb.rainbow_saturation, app->settings.display_brightness); + rgb_backlight_set_led_custom_hsv_color( + 2, k, app->settings.rgb.rainbow_saturation, app->settings.display_brightness); + break; + + default: + break; + } + + rgb_backlight_update(app->settings.led_brightness * app->current_night_shift); + } +} + +// --- RGB BACKLIGHT END--- + +// --- NIGHT SHIFT --- + +void night_shift_timer_start(NotificationApp* app) { + if(app->settings.night_shift != 1) { + if(furi_timer_is_running(app->night_shift_timer)) { + furi_timer_stop(app->night_shift_timer); + } + furi_timer_start(app->night_shift_timer, furi_ms_to_ticks(2000)); + } +} + +void night_shift_timer_stop(NotificationApp* app) { + if(furi_timer_is_running(app->night_shift_timer)) { + furi_timer_stop(app->night_shift_timer); + } +} + +// every callback time we check current time and current night_shift_settings value +void night_shift_timer_callback(void* context) { + furi_assert(context); + NotificationApp* app = context; + DateTime current_date_time; + + // take system time and convert to minutes + furi_hal_rtc_get_datetime(¤t_date_time); + uint32_t time = current_date_time.hour * 60 + current_date_time.minute; + + // if current time not in night_shift range then current_night_shift = 1 else = settings value; + // set values to stock and rgb backlights + if((time > app->settings.night_shift_end) && (time < app->settings.night_shift_start)) { + app->current_night_shift = 1.0f; + } else { + app->current_night_shift = app->settings.night_shift; + } +} + +// --- NIGHT SHIFT END --- + void notification_message_save_settings(NotificationApp* app) { NotificationAppMessage m = { .type = SaveSettingsMessage, .back_event = furi_event_flag_alloc()}; @@ -128,7 +371,11 @@ static void notification_reset_notification_layer( } if(reset_mask & reset_display_mask) { if(!float_is_equal(display_brightness_set, app->settings.display_brightness)) { - furi_hal_light_set(LightBacklight, app->settings.display_brightness * 0xFF); + // --- NIGHT SHIFT --- + furi_hal_light_set( + LightBacklight, + app->settings.display_brightness * 0xFF * app->current_night_shift * 1.0f); + // --- NIGHT SHIFT END--- } furi_timer_start(app->display_timer, notification_settings_display_off_delay_ticks(app)); } @@ -213,26 +460,38 @@ static void notification_process_notification_message( // if on - switch on and start timer // if off - switch off and stop timer // on timer - switch off + // --- NIGHT SHIFT --- if(notification_message->data.led.value > 0x00) { notification_apply_notification_led_layer( &app->display, - notification_message->data.led.value * display_brightness_setting); + notification_message->data.led.value * display_brightness_setting * + app->current_night_shift * 1.0f); reset_mask |= reset_display_mask; + + //start rgb_mod_rainbow_timer when display backlight is ON and all corresponding settings is ON too + rainbow_timer_starter(app); + // --- NIGHT SHIFT END --- } else { reset_mask &= ~reset_display_mask; notification_reset_notification_led_layer(&app->display); if(furi_timer_is_running(app->display_timer)) { furi_timer_stop(app->display_timer); } + //stop rgb_mod_rainbow_timer when display backlight is OFF + if(furi_timer_is_running(app->rainbow_timer)) { + rainbow_timer_stop(app); + } } break; case NotificationMessageTypeLedDisplayBacklightEnforceOn: furi_check(app->display_led_lock < UINT8_MAX); app->display_led_lock++; + // --- NIGHT SHIFT --- if(app->display_led_lock == 1) { notification_apply_internal_led_layer( &app->display, - notification_message->data.led.value * display_brightness_setting); + notification_message->data.led.value * display_brightness_setting * + app->current_night_shift * 1.0f); } break; case NotificationMessageTypeLedDisplayBacklightEnforceAuto: @@ -241,8 +500,10 @@ static void notification_process_notification_message( if(app->display_led_lock == 0) { notification_apply_internal_led_layer( &app->display, - notification_message->data.led.value * display_brightness_setting); + notification_message->data.led.value * display_brightness_setting * + app->current_night_shift * 1.0f); } + // --- NIGHT SHIFT END --- } else { FURI_LOG_E(TAG, "Incorrect BacklightEnforce use"); } @@ -550,6 +811,33 @@ static NotificationApp* notification_app_alloc(void) { furi_pubsub_subscribe(app->event_record, input_event_callback, app); notification_message(app, &sequence_display_backlight_on); + // --- NIGHT SHIFT --- + app->current_night_shift = 1.0f; + app->current_night_shift = 1.0f; + app->settings.night_shift = 1.0f; + app->settings.night_shift_start = 1020; + app->settings.night_shift_end = 300; + app->night_shift_timer = + furi_timer_alloc(night_shift_timer_callback, FuriTimerTypePeriodic, app); + // --- NIGHT SHIFT END --- + + // init working variables + app->rainbow_hue = 1; + app->current_night_shift = 1.0f; + + // init rgb.segings values + app->settings.rgb.rgb_backlight_installed = 0; + app->settings.rgb.led_2_color_index = 0; + app->settings.rgb.led_1_color_index = 0; + app->settings.rgb.led_0_color_index = 0; + app->settings.rgb.rainbow_speed_ms = 100; + app->settings.rgb.rainbow_step = 1; + app->settings.rgb.rainbow_saturation = 255; + app->settings.rgb.rainbow_wide = 50; + + // set inital value, later it will be rewriten by loading settings from file + app->settings.lcd_inversion = false; + return app; } @@ -573,6 +861,20 @@ static void notification_apply_settings(NotificationApp* app) { } notification_apply_lcd_contrast(app); + + // --- NIGHT SHIFT --- + // if night_shift enabled then start timer for controlling current_night_shift multiplicator value depent from current time + if(app->settings.night_shift != 1) { + night_shift_timer_start(app); + } + // --- NIGHT SHIFT END --- + + // check RECORD_GUI is exist (insurance on boot time) then use it to setup lcd inversion mode from loaded settings; + if(furi_record_exists(RECORD_GUI)) { + Gui* gui = furi_record_open(RECORD_GUI); + u8x8_d_st756x_set_inversion(&gui->canvas->fb.u8x8, app->settings.lcd_inversion); + furi_record_close(RECORD_GUI); + } } static void notification_init_settings(NotificationApp* app) { @@ -603,6 +905,34 @@ int32_t notification_srv(void* p) { furi_record_create(RECORD_NOTIFICATION, app); + // --- RGB BACKLIGHT SECTION --- + + //setup local variable + set_rgb_backlight_installed_variable(app->settings.rgb.rgb_backlight_installed); + + // define rainbow_timer and they callback + app->rainbow_timer = furi_timer_alloc(rainbow_timer_callback, FuriTimerTypePeriodic, app); + + // if rgb_backlight_installed then start rainbow or set leds colors from saved settings (default index = 0) + if(app->settings.rgb.rgb_backlight_installed) { + if(app->settings.rgb.rainbow_mode > 0) { + rainbow_timer_start(app); + } else { + rgb_backlight_set_led_static_color(2, app->settings.rgb.led_2_color_index); + rgb_backlight_set_led_static_color(1, app->settings.rgb.led_1_color_index); + rgb_backlight_set_led_static_color(0, app->settings.rgb.led_0_color_index); + rgb_backlight_update(app->settings.display_brightness * app->current_night_shift); + } + // if rgb_backlight not installed then set default static orange color(index=0) to all leds (0-2) and force light on + } else { + rgb_backlight_set_led_static_color(2, 0); + rgb_backlight_set_led_static_color(1, 0); + rgb_backlight_set_led_static_color(0, 0); + SK6805_update(); + } + + // --- RGB BACKLIGHT SECTION END --- + NotificationAppMessage message; while(1) { furi_check(furi_message_queue_get(app->queue, &message, FuriWaitForever) == FuriStatusOk); diff --git a/applications/services/notification/notification_app.h b/applications/services/notification/notification_app.h index e19546574..239bf69c0 100644 --- a/applications/services/notification/notification_app.h +++ b/applications/services/notification/notification_app.h @@ -3,6 +3,8 @@ #include "notification.h" #include "notification_messages.h" #include "notification_settings_filename.h" +#include +#include #define NOTIFICATION_LED_COUNT 3 #define NOTIFICATION_EVENT_COMPLETE 0x00000001U @@ -33,9 +35,26 @@ typedef struct { Light light; } NotificationLedLayer; -#define NOTIFICATION_SETTINGS_VERSION 0x02 +#define NOTIFICATION_SETTINGS_VERSION 0x05 #define NOTIFICATION_SETTINGS_PATH INT_PATH(NOTIFICATION_SETTINGS_FILE_NAME) +typedef struct { + //Common settings + uint8_t rgb_backlight_installed; + + // static gradient mode settings + uint8_t led_2_color_index; + uint8_t led_1_color_index; + uint8_t led_0_color_index; + + // rainbow mode setings + uint32_t rainbow_mode; + uint32_t rainbow_speed_ms; + uint16_t rainbow_step; + uint8_t rainbow_saturation; + uint8_t rainbow_wide; +} RGBBacklightSettings; + typedef struct { uint8_t version; float display_brightness; @@ -44,6 +63,11 @@ typedef struct { uint32_t display_off_delay_ms; int8_t contrast; bool vibro_on; + float night_shift; + uint32_t night_shift_start; + uint32_t night_shift_end; + bool lcd_inversion; + RGBBacklightSettings rgb; } NotificationSettings; struct NotificationApp { @@ -56,6 +80,25 @@ struct NotificationApp { uint8_t display_led_lock; NotificationSettings settings; + + FuriTimer* night_shift_timer; + float current_night_shift; + + FuriTimer* rainbow_timer; + uint16_t rainbow_hue; + uint8_t rainbow_red; + uint8_t rainbow_green; + uint8_t rainbow_blue; }; void notification_message_save_settings(NotificationApp* app); +void night_shift_timer_start(NotificationApp* app); +void night_shift_timer_stop(NotificationApp* app); +void rgb_backlight_update(float brightness); +void rgb_backlight_set_led_static_color(uint8_t led, uint8_t index); +void rainbow_timer_start(NotificationApp* app); +void rainbow_timer_stop(NotificationApp* app); +void rainbow_timer_starter(NotificationApp* app); +const char* rgb_backlight_get_color_text(uint8_t index); +uint8_t rgb_backlight_get_color_count(void); +void set_rgb_backlight_installed_variable(uint8_t var); diff --git a/applications/services/power/application.fam b/applications/services/power/application.fam index f14d88c54..0e69ec6ec 100644 --- a/applications/services/power/application.fam +++ b/applications/services/power/application.fam @@ -22,5 +22,5 @@ App( apptype=FlipperAppType.STARTUP, entry_point="power_on_system_start", requires=["power"], - order=80, + order=50, ) diff --git a/applications/services/power/power_cli.c b/applications/services/power/power_cli.c index 93d0f232a..f630cb2e8 100644 --- a/applications/services/power/power_cli.c +++ b/applications/services/power/power_cli.c @@ -1,12 +1,14 @@ #include "power_cli.h" #include -#include +#include +#include #include #include +#include -void power_cli_off(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_off(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); UNUSED(args); Power* power = furi_record_open(RECORD_POWER); printf("It's now safe to disconnect USB from your flipper\r\n"); @@ -14,33 +16,36 @@ void power_cli_off(Cli* cli, FuriString* args) { power_off(power); } -void power_cli_reboot(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_reboot(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); UNUSED(args); Power* power = furi_record_open(RECORD_POWER); power_reboot(power, PowerBootModeNormal); } -void power_cli_reboot2dfu(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_reboot2dfu(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); UNUSED(args); Power* power = furi_record_open(RECORD_POWER); power_reboot(power, PowerBootModeDfu); } -void power_cli_5v(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_5v(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); + Power* power = furi_record_open(RECORD_POWER); if(!furi_string_cmp(args, "0")) { - furi_hal_power_disable_otg(); + power_enable_otg(power, false); } else if(!furi_string_cmp(args, "1")) { - furi_hal_power_enable_otg(); + power_enable_otg(power, true); } else { cli_print_usage("power_otg", "<1|0>", furi_string_get_cstr(args)); } + + furi_record_close(RECORD_POWER); } -void power_cli_3v3(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_3v3(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); if(!furi_string_cmp(args, "0")) { furi_hal_power_disable_external_3_3v(); } else if(!furi_string_cmp(args, "1")) { @@ -64,7 +69,7 @@ static void power_cli_command_print_usage(void) { } } -void power_cli(Cli* cli, FuriString* args, void* context) { +void power_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -76,28 +81,28 @@ void power_cli(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "off") == 0) { - power_cli_off(cli, args); + power_cli_off(pipe, args); break; } if(furi_string_cmp_str(cmd, "reboot") == 0) { - power_cli_reboot(cli, args); + power_cli_reboot(pipe, args); break; } if(furi_string_cmp_str(cmd, "reboot2dfu") == 0) { - power_cli_reboot2dfu(cli, args); + power_cli_reboot2dfu(pipe, args); break; } if(furi_string_cmp_str(cmd, "5v") == 0) { - power_cli_5v(cli, args); + power_cli_5v(pipe, args); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "3v3") == 0) { - power_cli_3v3(cli, args); + power_cli_3v3(pipe, args); break; } } @@ -110,10 +115,8 @@ void power_cli(Cli* cli, FuriString* args, void* context) { void power_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - - cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli, NULL); - + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command(registry, "power", CliCommandFlagParallelSafe, power_cli, NULL); furi_record_close(RECORD_CLI); #else UNUSED(power_cli); diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 511c64c78..31fcfcbd2 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -247,6 +247,7 @@ static bool power_update_info(Power* power) { .is_charging = furi_hal_power_is_charging(), .gauge_is_ok = furi_hal_power_gauge_is_ok(), .is_shutdown_requested = furi_hal_power_is_shutdown_requested(), + .is_otg_enabled = furi_hal_power_is_otg_enabled(), .charge = furi_hal_power_get_pct(), .health = furi_hal_power_get_bat_health_pct(), .capacity_remaining = furi_hal_power_get_battery_remaining_capacity(), @@ -415,13 +416,18 @@ void power_api_set_settings(Power* power, const PowerSettings* settings) { //start furi timer for autopoweroff static void power_start_auto_poweroff_timer(Power* power) { + if(furi_timer_is_running(power->auto_poweroff_timer)) { + furi_timer_stop(power->auto_poweroff_timer); + } furi_timer_start( power->auto_poweroff_timer, furi_ms_to_ticks(power->settings.auto_poweroff_delay_ms)); } //stop furi timer for autopoweroff static void power_stop_auto_poweroff_timer(Power* power) { - furi_timer_stop(power->auto_poweroff_timer); + if(furi_timer_is_running(power->auto_poweroff_timer)) { + furi_timer_stop(power->auto_poweroff_timer); + } } static uint32_t power_is_running_auto_poweroff_timer(Power* power) { @@ -442,7 +448,7 @@ static void power_auto_poweroff_timer_callback(void* context) { Power* power = context; //Dont poweroff device if charger connected - if (furi_hal_power_is_charging()) { + if(furi_hal_power_is_charging()) { FURI_LOG_D(TAG, "We dont auto_power_off until battery is charging"); power_start_auto_poweroff_timer(power); } else { @@ -539,6 +545,30 @@ static void power_message_callback(FuriEventLoopObject* object, void* context) { power_settings_load(&power->settings); power_settings_apply(power); break; + case PowerMessageTypeSwitchOTG: + power->is_otg_requested = *msg.bool_param; + if(power->is_otg_requested) { + // Only try to enable if VBUS voltage is low, otherwise charger will refuse + if(power->info.voltage_vbus < 4.5f) { + size_t retries = 5; + while(retries-- > 0) { + if(furi_hal_power_enable_otg()) { + break; + } + } + if(!retries) { + FURI_LOG_W(TAG, "Failed to enable OTG, will try later"); + } + } else { + FURI_LOG_W( + TAG, + "Postponing OTG enable: VBUS(%0.1f) >= 4.5v", + (double)power->info.voltage_vbus); + } + } else { + furi_hal_power_disable_otg(); + } + break; default: furi_crash(); } @@ -548,6 +578,24 @@ static void power_message_callback(FuriEventLoopObject* object, void* context) { } } +static void power_charge_supress(Power* power) { + // if charge_supress_percent selected (not OFF) and current charge level equal or higher than selected level + // then we start supression if we not supress it before. + if(power->settings.charge_supress_percent && + power->info.charge >= power->settings.charge_supress_percent) { + if(!power->charge_is_supressed) { + power->charge_is_supressed = true; + furi_hal_power_suppress_charge_enter(); + } + // disable supression if charge_supress_percent OFF but charge still supressed + } else { + if(power->charge_is_supressed) { + power->charge_is_supressed = false; + furi_hal_power_suppress_charge_exit(); + } + } +} + static void power_tick_callback(void* context) { furi_assert(context); Power* power = context; @@ -560,13 +608,24 @@ static void power_tick_callback(void* context) { power_check_charging_state(power); // Check and notify about battery level change power_check_battery_level_change(power); + // charge supress arm/disarm + power_charge_supress(power); // Update battery view port if(need_refresh) { view_port_update(power->battery_view_port); } - // Check OTG status and disable it in case of fault - if(furi_hal_power_is_otg_enabled()) { - furi_hal_power_check_otg_status(); + // Check OTG status, disable in case of a fault + if(furi_hal_power_check_otg_fault()) { + FURI_LOG_E(TAG, "OTG fault detected, disabling OTG"); + furi_hal_power_disable_otg(); + power->is_otg_requested = false; + } + + // Change OTG state if needed (i.e. after disconnecting USB power) + if(power->is_otg_requested && + (!power->info.is_otg_enabled && power->info.voltage_vbus < 4.5f)) { + FURI_LOG_D(TAG, "OTG requested but not enabled, enabling OTG"); + furi_hal_power_enable_otg(); } } @@ -585,7 +644,7 @@ static void power_storage_callback(const void* message, void* context) { } } -// load inital settings from file for power service +// loading and initializing power service settings static void power_init_settings(Power* power) { Storage* storage = furi_record_open(RECORD_STORAGE); furi_pubsub_subscribe(storage_get_pubsub(storage), power_storage_callback, power); @@ -598,6 +657,7 @@ static void power_init_settings(Power* power) { power_settings_load(&power->settings); power_settings_apply(power); furi_record_close(RECORD_STORAGE); + power->charge_is_supressed = false; } static Power* power_alloc(void) { @@ -615,7 +675,7 @@ static Power* power_alloc(void) { free(settings); // auto_poweroff - //---define subscription to loader events message (info about started apps) and defina callback for this + //---define subscription to loader events message (info about started apps) and define callback for this Loader* loader = furi_record_open(RECORD_LOADER); furi_pubsub_subscribe(loader_get_pubsub(loader), power_loader_callback, power); power->input_events_pubsub = furi_record_open(RECORD_INPUT_EVENTS); @@ -661,7 +721,7 @@ int32_t power_srv(void* p) { Power* power = power_alloc(); - // load inital settings for power service + // power service settings initialization power_init_settings(power); power_update_info(power); diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h index 59947fa52..42ff7a3b5 100644 --- a/applications/services/power/power_service/power.h +++ b/applications/services/power/power_service/power.h @@ -39,6 +39,7 @@ typedef struct { bool gauge_is_ok; bool is_charging; bool is_shutdown_requested; + bool is_otg_enabled; float current_charger; float current_gauge; @@ -108,6 +109,19 @@ void power_api_get_settings(Power* instance, PowerSettings* settings); // set settings from app to service void power_api_set_settings(Power* instance, const PowerSettings* settings); +/** Enable or disable OTG + * + * @param power Power instance + * @param enable true - enable, false - disable + */ +void power_enable_otg(Power* power, bool enable); + +/** Check OTG status + * + * @return true if OTG is requested + */ +bool power_is_otg_enabled(Power* power); + #ifdef __cplusplus } #endif diff --git a/applications/services/power/power_service/power_api.c b/applications/services/power/power_service/power_api.c index 6f7515f5e..f634f15e3 100644 --- a/applications/services/power/power_service/power_api.c +++ b/applications/services/power/power_service/power_api.c @@ -70,3 +70,22 @@ void power_enable_low_battery_level_notification(Power* power, bool enable) { furi_check( furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk); } + +void power_enable_otg(Power* power, bool enable) { + furi_check(power); + + PowerMessage msg = { + .type = PowerMessageTypeSwitchOTG, + .bool_param = &enable, + .lock = api_lock_alloc_locked(), + }; + + furi_check( + furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk); + api_lock_wait_unlock_and_free(msg.lock); +} + +bool power_is_otg_enabled(Power* power) { + furi_check(power); + return power->is_otg_requested; +} diff --git a/applications/services/power/power_service/power_i.h b/applications/services/power/power_service/power_i.h index 40b4a1ef4..3e37ba674 100644 --- a/applications/services/power/power_service/power_i.h +++ b/applications/services/power/power_service/power_i.h @@ -34,6 +34,7 @@ struct Power { bool battery_low; bool show_battery_low_warning; uint8_t displayBatteryPercentage; + bool is_otg_requested; uint8_t battery_level; uint8_t power_off_timeout; PowerSettings settings; @@ -41,6 +42,7 @@ struct Power { bool app_running; FuriPubSub* input_events_pubsub; FuriPubSubSubscription* input_events_subscription; + bool charge_is_supressed; }; typedef enum { @@ -57,6 +59,7 @@ typedef enum { PowerMessageTypeGetSettings, PowerMessageTypeSetSettings, PowerMessageTypeReloadSettings, + PowerMessageTypeSwitchOTG, } PowerMessageType; typedef struct { diff --git a/applications/services/power/power_service/power_settings.c b/applications/services/power/power_service/power_settings.c index 139323a7a..3e101d25a 100644 --- a/applications/services/power/power_service/power_settings.c +++ b/applications/services/power/power_service/power_settings.c @@ -6,15 +6,16 @@ #define TAG "PowerSettings" -#define POWER_SETTINGS_VER_0 (0) // OLD version number -#define POWER_SETTINGS_VER (1) // NEW actual version nnumber +#define POWER_SETTINGS_VER_1 (1) // Previous version number +#define POWER_SETTINGS_VER (2) // New version number -#define POWER_SETTINGS_PATH INT_PATH(POWER_SETTINGS_FILE_NAME) -#define POWER_SETTINGS_MAGIC (0x18) +#define POWER_SETTINGS_PATH INT_PATH(POWER_SETTINGS_FILE_NAME) +#define POWER_SETTINGS_MAGIC_V1 (0x19) +#define POWER_SETTINGS_MAGIC (0x21) typedef struct { - //inital set - empty -} PowerSettingsV0; + uint32_t auto_poweroff_delay_ms; +} PowerSettingsPrevious; void power_settings_load(PowerSettings* settings) { furi_assert(settings); @@ -25,7 +26,8 @@ void power_settings_load(PowerSettings* settings) { uint8_t version; if(!saved_struct_get_metadata(POWER_SETTINGS_PATH, NULL, &version, NULL)) break; - if(version == POWER_SETTINGS_VER) { // if config actual version - load it directly + // if config actual version - load it directly + if(version == POWER_SETTINGS_VER) { success = saved_struct_load( POWER_SETTINGS_PATH, settings, @@ -33,23 +35,23 @@ void power_settings_load(PowerSettings* settings) { POWER_SETTINGS_MAGIC, POWER_SETTINGS_VER); - } else if( - version == - POWER_SETTINGS_VER_0) { // if config previous version - load it and manual set new settings to inital value - PowerSettingsV0* settings_v0 = malloc(sizeof(PowerSettingsV0)); + // if config previous version - load it and manual set new settings to inital value + } else if(version == POWER_SETTINGS_VER_1) { + PowerSettingsPrevious* settings_previous = malloc(sizeof(PowerSettingsPrevious)); success = saved_struct_load( POWER_SETTINGS_PATH, - settings_v0, - sizeof(PowerSettingsV0), - POWER_SETTINGS_MAGIC, - POWER_SETTINGS_VER_0); - + settings_previous, + sizeof(PowerSettingsPrevious), + POWER_SETTINGS_MAGIC_V1, + POWER_SETTINGS_VER_1); + // new settings initialization if(success) { - settings->auto_poweroff_delay_ms = 0; + settings->auto_poweroff_delay_ms = settings_previous->auto_poweroff_delay_ms; + settings->charge_supress_percent = 0; } - free(settings_v0); + free(settings_previous); } } while(false); diff --git a/applications/services/power/power_service/power_settings.h b/applications/services/power/power_service/power_settings.h index 65d8e079e..63f24e097 100644 --- a/applications/services/power/power_service/power_settings.h +++ b/applications/services/power/power_service/power_settings.h @@ -1,16 +1,20 @@ #pragma once #include +#include typedef struct { uint32_t auto_poweroff_delay_ms; + uint8_t charge_supress_percent; } PowerSettings; #ifdef __cplusplus extern "C" { #endif + void power_settings_load(PowerSettings* settings); void power_settings_save(const PowerSettings* settings); + #ifdef __cplusplus } #endif diff --git a/applications/services/region/application.fam b/applications/services/region/application.fam deleted file mode 100644 index a4cdc94ea..000000000 --- a/applications/services/region/application.fam +++ /dev/null @@ -1,10 +0,0 @@ -App( - appid="region", - name="RegionSrv", - apptype=FlipperAppType.STARTUP, - targets=["f7"], - entry_point="region_on_system_start", - cdefines=["SRV_REGION"], - requires=["storage"], - order=170, -) diff --git a/applications/services/region/region.c b/applications/services/region/region.c deleted file mode 100644 index bed676f9b..000000000 --- a/applications/services/region/region.c +++ /dev/null @@ -1,140 +0,0 @@ -#include - -#include -#include - -#include -#include - -#define TAG "RegionSrv" - -#define SUBGHZ_REGION_FILENAME INT_PATH(".region_data") - -static bool region_istream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { - File* file = istream->state; - size_t ret = storage_file_read(file, buf, count); - return count == ret; -} - -static bool region_istream_decode_band(pb_istream_t* stream, const pb_field_t* field, void** arg) { - UNUSED(field); - - FuriHalRegion* region = *arg; - - PB_Region_Band band = {0}; - if(!pb_decode(stream, PB_Region_Band_fields, &band)) { - FURI_LOG_E(TAG, "PB Region band decode error: %s", PB_GET_ERROR(stream)); - return false; - } - - region->bands_count += 1; - region = realloc( //-V701 - region, - sizeof(FuriHalRegion) + sizeof(FuriHalRegionBand) * region->bands_count); - size_t pos = region->bands_count - 1; - region->bands[pos].start = band.start; - region->bands[pos].end = band.end; - region->bands[pos].power_limit = band.power_limit; - region->bands[pos].duty_cycle = band.duty_cycle; - *arg = region; - - FURI_LOG_I( - TAG, - "Add allowed band: start %luHz, stop %luHz, power_limit %ddBm, duty_cycle %u%%", - band.start, - band.end, - band.power_limit, - band.duty_cycle); - return true; -} - -static int32_t region_load_file(void* context) { - UNUSED(context); - - Storage* storage = furi_record_open(RECORD_STORAGE); - File* file = storage_file_alloc(storage); - - PB_Region pb_region = {0}; - pb_region.bands.funcs.decode = region_istream_decode_band; - - do { - FileInfo fileinfo = {0}; - - if(storage_common_stat(storage, SUBGHZ_REGION_FILENAME, &fileinfo) != FSE_OK || - fileinfo.size == 0) { - FURI_LOG_W(TAG, "Region file missing or empty"); - break; - - } else if(!storage_file_open(file, SUBGHZ_REGION_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) { - FURI_LOG_E(TAG, "Failed to open region file"); - break; - } - - pb_istream_t istream = { - .callback = region_istream_read, - .state = file, - .errmsg = NULL, - .bytes_left = fileinfo.size, - }; - - pb_region.bands.arg = malloc(sizeof(FuriHalRegion)); - - if(!pb_decode(&istream, PB_Region_fields, &pb_region)) { - FURI_LOG_E(TAG, "Failed to decode region file"); - free(pb_region.bands.arg); - break; - } - - FuriHalRegion* region = pb_region.bands.arg; - - memcpy( - region->country_code, - pb_region.country_code->bytes, - MIN(pb_region.country_code->size, sizeof(region->country_code) - 1)); - - furi_hal_region_set(region); - - FURI_LOG_I(TAG, "Dynamic region set: %s", region->country_code); - } while(0); - - pb_release(PB_Region_fields, &pb_region); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - - return 0; -} - -static void - region_loader_release_callback(FuriThread* thread, FuriThreadState state, void* context) { - UNUSED(context); - - if(state == FuriThreadStateStopped) { - furi_thread_free(thread); - } -} - -static void region_storage_callback(const void* message, void* context) { - UNUSED(context); - const StorageEvent* event = message; - - if(event->type == StorageEventTypeCardMount) { - FuriThread* loader = furi_thread_alloc_ex(NULL, 2048, region_load_file, NULL); - furi_thread_set_state_callback(loader, region_loader_release_callback); - furi_thread_start(loader); - } -} - -int32_t region_on_system_start(void* p) { - UNUSED(p); - - Storage* storage = furi_record_open(RECORD_STORAGE); - furi_pubsub_subscribe(storage_get_pubsub(storage), region_storage_callback, NULL); - - if(storage_sd_status(storage) != FSE_OK) { - FURI_LOG_D(TAG, "SD Card not ready, skipping dynamic region"); - return 0; - } - - region_load_file(NULL); - return 0; -} diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index a18fcf1f7..a29819a12 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -9,7 +9,8 @@ #include -#include +#include +#include #include #include #include @@ -435,9 +436,14 @@ void rpc_on_system_start(void* p) { rpc->busy_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command( - cli, "start_rpc_session", CliCommandFlagParallelSafe, rpc_cli_command_start_session, rpc); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command( + registry, + "start_rpc_session", + CliCommandFlagParallelSafe, + rpc_cli_command_start_session, + rpc); + furi_record_close(RECORD_CLI); furi_record_create(RECORD_RPC, rpc); } diff --git a/applications/services/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c index 4612752a8..fda059ec8 100644 --- a/applications/services/rpc/rpc_cli.c +++ b/applications/services/rpc/rpc_cli.c @@ -1,25 +1,26 @@ -#include +#include +#include #include #include #include +#include #define TAG "RpcCli" typedef struct { - Cli* cli; + PipeSide* pipe; bool session_close_request; FuriSemaphore* terminate_semaphore; } CliRpc; -#define CLI_READ_BUFFER_SIZE 64 +#define CLI_READ_BUFFER_SIZE 64UL static void rpc_cli_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) { furi_assert(context); furi_assert(bytes); furi_assert(bytes_len > 0); CliRpc* cli_rpc = context; - - cli_write(cli_rpc->cli, bytes, bytes_len); + pipe_send(cli_rpc->pipe, bytes, bytes_len); } static void rpc_cli_session_close_callback(void* context) { @@ -36,9 +37,9 @@ static void rpc_cli_session_terminated_callback(void* context) { furi_semaphore_release(cli_rpc->terminate_semaphore); } -void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { +void rpc_cli_command_start_session(PipeSide* pipe, FuriString* args, void* context) { UNUSED(args); - furi_assert(cli); + furi_assert(pipe); furi_assert(context); Rpc* rpc = context; @@ -53,7 +54,7 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { return; } - CliRpc cli_rpc = {.cli = cli, .session_close_request = false}; + CliRpc cli_rpc = {.pipe = pipe, .session_close_request = false}; cli_rpc.terminate_semaphore = furi_semaphore_alloc(1, 0); rpc_session_set_context(rpc_session, &cli_rpc); rpc_session_set_send_bytes_callback(rpc_session, rpc_cli_send_bytes_callback); @@ -64,8 +65,9 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { size_t size_received = 0; while(1) { - size_received = cli_read_timeout(cli_rpc.cli, buffer, CLI_READ_BUFFER_SIZE, 50); - if(!cli_is_connected(cli_rpc.cli) || cli_rpc.session_close_request) { + size_t to_receive = CLAMP(pipe_bytes_available(cli_rpc.pipe), CLI_READ_BUFFER_SIZE, 1UL); + size_received = pipe_receive(cli_rpc.pipe, buffer, to_receive); + if(size_received < to_receive || cli_rpc.session_close_request) { break; } diff --git a/applications/services/rpc/rpc_gpio.c b/applications/services/rpc/rpc_gpio.c index 9952bec38..d05783afc 100644 --- a/applications/services/rpc/rpc_gpio.c +++ b/applications/services/rpc/rpc_gpio.c @@ -4,6 +4,7 @@ #include #include #include +#include static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) { switch(rpc_pin) { @@ -218,12 +219,16 @@ void rpc_system_gpio_set_otg_mode(const PB_Main* request, void* context) { const PB_Gpio_GpioOtgMode mode = request->content.gpio_set_otg_mode.mode; + Power* power = furi_record_open(RECORD_POWER); + if(mode == PB_Gpio_GpioOtgMode_OFF) { - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); + power_enable_otg(power, false); } else { - if(!furi_hal_power_is_otg_enabled()) furi_hal_power_enable_otg(); + power_enable_otg(power, true); } + furi_record_close(RECORD_POWER); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); } diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index 0342df2b6..df1f17de4 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #ifdef __cplusplus extern "C" { @@ -46,7 +46,7 @@ void rpc_desktop_free(void* ctx); void rpc_debug_print_message(const PB_Main* message); void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size); -void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context); +void rpc_cli_command_start_session(PipeSide* pipe, FuriString* args, void* context); PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error); diff --git a/applications/services/storage/application.fam b/applications/services/storage/application.fam index 7aa721cc3..047500fa3 100644 --- a/applications/services/storage/application.fam +++ b/applications/services/storage/application.fam @@ -16,5 +16,5 @@ App( apptype=FlipperAppType.STARTUP, entry_point="storage_on_system_start", requires=["storage"], - order=90, + order=60, ) diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 441b58da6..2dab63e53 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -1,7 +1,9 @@ #include #include -#include +#include +#include +#include #include #include #include @@ -10,6 +12,7 @@ #include #include #include +#include #define MAX_NAME_LENGTH 255 @@ -19,8 +22,8 @@ static void storage_cli_print_error(FS_Error error) { printf("Storage error: %s\r\n", storage_error_get_desc(error)); } -static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_info(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -69,13 +72,14 @@ static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_format(Cli* cli, FuriString* path, FuriString* args) { +static void storage_cli_format(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) { storage_cli_print_error(FSE_NOT_IMPLEMENTED); } else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) { printf("Formatting SD card, All data will be lost! Are you sure (y/n)?\r\n"); - char answer = cli_getc(cli); + char answer = getchar(); if(answer == 'y' || answer == 'Y') { Storage* api = furi_record_open(RECORD_STORAGE); printf("Formatting, please wait...\r\n"); @@ -96,8 +100,8 @@ static void storage_cli_format(Cli* cli, FuriString* path, FuriString* args) { } } -static void storage_cli_list(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_list(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); if(furi_string_cmp_str(path, "/") == 0) { printf("\t[D] int\r\n"); @@ -134,13 +138,13 @@ static void storage_cli_list(Cli* cli, FuriString* path, FuriString* args) { } } -static void storage_cli_tree(Cli* cli, FuriString* path, FuriString* args) { +static void storage_cli_tree(PipeSide* pipe, FuriString* path, FuriString* args) { UNUSED(args); if(furi_string_cmp_str(path, "/") == 0) { furi_string_set(path, STORAGE_INT_PATH_PREFIX); - storage_cli_tree(cli, path, NULL); + storage_cli_tree(pipe, path, NULL); furi_string_set(path, STORAGE_EXT_PATH_PREFIX); - storage_cli_tree(cli, path, NULL); + storage_cli_tree(pipe, path, NULL); } else { Storage* api = furi_record_open(RECORD_STORAGE); DirWalk* dir_walk = dir_walk_alloc(api); @@ -176,8 +180,8 @@ static void storage_cli_tree(Cli* cli, FuriString* path, FuriString* args) { } } -static void storage_cli_read(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_read(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -208,7 +212,8 @@ static void storage_cli_read(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) { +static void storage_cli_write(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -222,9 +227,9 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) { uint32_t read_index = 0; while(true) { - uint8_t symbol = cli_getc(cli); + uint8_t symbol = getchar(); - if(symbol == CliSymbolAsciiETX) { + if(symbol == CliKeyETX) { size_t write_size = read_index % buffer_size; if(write_size > 0) { @@ -263,7 +268,8 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args) { +static void storage_cli_read_chunks(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -280,7 +286,7 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args uint8_t* data = malloc(buffer_size); while(file_size > 0) { printf("\r\nReady?\r\n"); - cli_getc(cli); + getchar(); size_t read_size = storage_file_read(file, data, buffer_size); for(size_t i = 0; i < read_size; i++) { @@ -302,31 +308,34 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args furi_record_close(RECORD_STORAGE); } -static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args) { +static void storage_cli_write_chunk(PipeSide* pipe, FuriString* path, FuriString* args) { Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); - uint32_t buffer_size; - if(strint_to_uint32(furi_string_get_cstr(args), NULL, &buffer_size, 10) != + uint32_t need_to_read; + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &need_to_read, 10) != StrintParseNoError) { storage_cli_print_usage(); } else { if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { printf("Ready\r\n"); + const size_t buffer_size = 1024; + uint8_t* buffer = malloc(buffer_size); - if(buffer_size) { - uint8_t* buffer = malloc(buffer_size); + while(need_to_read) { + size_t to_read_this_time = MIN(buffer_size, need_to_read); + size_t read_this_time = pipe_receive(pipe, buffer, to_read_this_time); + if(read_this_time != to_read_this_time) break; - size_t read_bytes = cli_read(cli, buffer, buffer_size); - - size_t written_size = storage_file_write(file, buffer, read_bytes); - - if(written_size != buffer_size) { + size_t wrote_this_time = storage_file_write(file, buffer, read_this_time); + if(wrote_this_time != read_this_time) { storage_cli_print_error(storage_file_get_error(file)); + break; } - - free(buffer); + need_to_read -= read_this_time; } + + free(buffer); } else { storage_cli_print_error(storage_file_get_error(file)); } @@ -337,8 +346,8 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args furi_record_close(RECORD_STORAGE); } -static void storage_cli_stat(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_stat(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -379,8 +388,8 @@ static void storage_cli_stat(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_timestamp(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_timestamp(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -396,8 +405,8 @@ static void storage_cli_timestamp(Cli* cli, FuriString* path, FuriString* args) furi_record_close(RECORD_STORAGE); } -static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) { - UNUSED(cli); +static void storage_cli_copy(PipeSide* pipe, FuriString* old_path, FuriString* args) { + UNUSED(pipe); Storage* api = furi_record_open(RECORD_STORAGE); FuriString* new_path; new_path = furi_string_alloc(); @@ -417,8 +426,8 @@ static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_remove(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_remove(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); FS_Error error = storage_common_remove(api, furi_string_get_cstr(path)); @@ -430,8 +439,8 @@ static void storage_cli_remove(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args) { - UNUSED(cli); +static void storage_cli_rename(PipeSide* pipe, FuriString* old_path, FuriString* args) { + UNUSED(pipe); Storage* api = furi_record_open(RECORD_STORAGE); FuriString* new_path; new_path = furi_string_alloc(); @@ -451,8 +460,8 @@ static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args) furi_record_close(RECORD_STORAGE); } -static void storage_cli_mkdir(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_mkdir(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); FS_Error error = storage_common_mkdir(api, furi_string_get_cstr(path)); @@ -464,8 +473,8 @@ static void storage_cli_mkdir(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_md5(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_md5(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -491,8 +500,8 @@ static bool tar_extract_file_callback(const char* name, bool is_directory, void* return true; } -static void storage_cli_extract(Cli* cli, FuriString* old_path, FuriString* args) { - UNUSED(cli); +static void storage_cli_extract(PipeSide* pipe, FuriString* old_path, FuriString* args) { + UNUSED(pipe); FuriString* new_path = furi_string_alloc(); if(!args_read_probably_quoted_string_and_trim(args, new_path)) { @@ -526,7 +535,7 @@ static void storage_cli_extract(Cli* cli, FuriString* old_path, FuriString* args furi_record_close(RECORD_STORAGE); } -typedef void (*StorageCliCommandCallback)(Cli* cli, FuriString* path, FuriString* args); +typedef void (*StorageCliCommandCallback)(PipeSide* pipe, FuriString* path, FuriString* args); typedef struct { const char* command; @@ -631,7 +640,7 @@ static void storage_cli_print_usage(void) { } } -void storage_cli(Cli* cli, FuriString* args, void* context) { +void storage_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; FuriString* path; @@ -653,7 +662,7 @@ void storage_cli(Cli* cli, FuriString* args, void* context) { for(; i < COUNT_OF(storage_cli_commands); ++i) { const StorageCliCommand* command_descr = &storage_cli_commands[i]; if(furi_string_cmp_str(cmd, command_descr->command) == 0) { - command_descr->impl(cli, path, args); + command_descr->impl(pipe, path, args); break; } } @@ -667,11 +676,12 @@ void storage_cli(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context) { +static void storage_cli_factory_reset(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); printf("All data will be lost! Are you sure (y/n)?\r\n"); - char c = cli_getc(cli); + char c = getchar(); if(c == 'y' || c == 'Y') { printf("Data will be wiped after reboot.\r\n"); @@ -687,10 +697,15 @@ static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context) void storage_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, RECORD_STORAGE, CliCommandFlagParallelSafe, storage_cli, NULL); - cli_add_command( - cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command( + registry, + "storage", + CliCommandFlagParallelSafe | CliCommandFlagUseShellThread, + storage_cli, + NULL); + cli_registry_add_command( + registry, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL); furi_record_close(RECORD_CLI); #else UNUSED(storage_cli_factory_reset); diff --git a/applications/settings/application.fam b/applications/settings/application.fam index 1d6db35a7..2ad4aa030 100644 --- a/applications/settings/application.fam +++ b/applications/settings/application.fam @@ -6,6 +6,7 @@ App( "passport", "system_settings", "clock_settings", + "input_settings", "about", ], ) diff --git a/applications/settings/clock_settings/clock_settings_alarm.c b/applications/settings/clock_settings/clock_settings_alarm.c index 7b096ef70..aea285f98 100644 --- a/applications/settings/clock_settings/clock_settings_alarm.c +++ b/applications/settings/clock_settings/clock_settings_alarm.c @@ -11,9 +11,16 @@ #define TAG "ClockSettingsAlarm" +#define SNOOZE_MINUTES 9 +#define TIMEOUT_MINUTES 10 + typedef struct { DateTime now; + DateTime snooze_until; + DateTime alarm_start; IconAnimation* icon; + + bool is_snooze; } ClockSettingsAlramModel; const NotificationSequence sequence_alarm = { @@ -47,12 +54,15 @@ static void clock_settings_alarm_draw_callback(Canvas* canvas, void* ctx) { ClockSettingsAlramModel* model = ctx; char buffer[64] = {}; + // Clock icon canvas_draw_icon_animation(canvas, 5, 6, model->icon); + // Time canvas_set_font(canvas, FontBigNumbers); snprintf(buffer, sizeof(buffer), "%02u:%02u", model->now.hour, model->now.minute); canvas_draw_str(canvas, 58, 32, buffer); + // Date canvas_set_font(canvas, FontPrimary); snprintf( buffer, @@ -62,6 +72,11 @@ static void clock_settings_alarm_draw_callback(Canvas* canvas, void* ctx) { model->now.month, model->now.year); canvas_draw_str(canvas, 60, 44, buffer); + + // Press Back to snooze + canvas_set_font(canvas, FontPrimary); + canvas_draw_icon_ex(canvas, 5, 50, &I_Pin_back_arrow_10x8, 0); + canvas_draw_str_aligned(canvas, 20, 50, AlignLeft, AlignTop, "Snooze"); } static void clock_settings_alarm_input_callback(InputEvent* input_event, void* ctx) { @@ -81,8 +96,10 @@ int32_t clock_settings_alarm(void* p) { // View Model ClockSettingsAlramModel model; + model.is_snooze = false; furi_hal_rtc_get_datetime(&model.now); + furi_hal_rtc_get_alarm(&model.alarm_start); model.icon = icon_animation_alloc(&A_Alarm_47x39); // Alloc message queue @@ -95,6 +112,7 @@ int32_t clock_settings_alarm(void* p) { // Register view port in GUI Gui* gui = furi_record_open(RECORD_GUI); + gui_set_lockdown_inhibit(gui, true); gui_add_view_port(gui, view_port, GuiLayerFullscreen); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); @@ -110,12 +128,43 @@ int32_t clock_settings_alarm(void* p) { while(running) { if(furi_message_queue_get(event_queue, &event, 2000) == FuriStatusOk) { if(event.type == InputTypePress) { - running = false; + // Snooze + if(event.key == InputKeyBack) { + furi_hal_rtc_get_datetime(&model.snooze_until); + model.snooze_until.minute += SNOOZE_MINUTES; + model.snooze_until.hour += model.snooze_until.minute / 60; + model.snooze_until.minute %= 60; + model.snooze_until.hour %= 24; + + model.is_snooze = true; + model.alarm_start = model.snooze_until; // For correct timeout behavior + view_port_enabled_set(view_port, false); + gui_set_lockdown_inhibit(gui, false); + } else { + running = false; + } + } + } else if(model.is_snooze) { + furi_hal_rtc_get_datetime(&model.now); + if(datetime_datetime_to_timestamp(&model.now) >= + datetime_datetime_to_timestamp(&model.snooze_until)) { + view_port_enabled_set(view_port, true); + gui_set_lockdown_inhibit(gui, true); + + model.is_snooze = false; } } else { notification_message(notification, &sequence_alarm); furi_hal_rtc_get_datetime(&model.now); view_port_update(view_port); + + // Stop the alarm if it has been ringing for more than TIMEOUT_MINUTES + if((model.now.hour == model.alarm_start.hour && + model.now.minute >= model.alarm_start.minute + TIMEOUT_MINUTES) || + (model.now.hour == (model.alarm_start.hour + 1) % 24 && + model.now.minute < (model.alarm_start.minute + TIMEOUT_MINUTES) % 60)) { + running = false; + } } } @@ -125,6 +174,7 @@ int32_t clock_settings_alarm(void* p) { furi_record_close(RECORD_NOTIFICATION); view_port_enabled_set(view_port, false); + gui_set_lockdown_inhibit(gui, false); gui_remove_view_port(gui, view_port); view_port_free(view_port); furi_message_queue_free(event_queue); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c index 9b992c80c..8555bbf71 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c @@ -18,6 +18,7 @@ typedef enum { DesktopSettingsFavoriteLeftLong, DesktopSettingsFavoriteRightShort, DesktopSettingsFavoriteRightLong, + DesktopSettingsFavoriteOkLong, DesktopSettingsDummyLeft, DesktopSettingsDummyLeftLong, DesktopSettingsDummyRight, @@ -52,7 +53,7 @@ const char* const usb_inhibit_auto_lock_delay_text[USB_INHIBIT_AUTO_LOCK_DELAY_C "ON", }; -const uint32_t usb_inhibit_auto_lock_delay_value[USB_INHIBIT_AUTO_LOCK_DELAY_COUNT] = {0,1}; +const uint32_t usb_inhibit_auto_lock_delay_value[USB_INHIBIT_AUTO_LOCK_DELAY_COUNT] = {0, 1}; #define CLOCK_ENABLE_COUNT 2 const char* const clock_enable_text[CLOCK_ENABLE_COUNT] = { @@ -182,6 +183,7 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_list_add(variable_item_list, "Favorite App - Left Long", 1, NULL, NULL); variable_item_list_add(variable_item_list, "Favorite App - Right Short", 1, NULL, NULL); variable_item_list_add(variable_item_list, "Favorite App - Right Long", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Favorite App - Ok Long", 1, NULL, NULL); variable_item_list_add(variable_item_list, "DummyMode - Left", 1, NULL, NULL); variable_item_list_add(variable_item_list, "DummyMode - Left Long", 1, NULL, NULL); @@ -246,6 +248,13 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightLong); scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); break; + case DesktopSettingsFavoriteOkLong: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppOkLong); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + break; case DesktopSettingsDummyLeft: scene_manager_set_scene_state( diff --git a/applications/settings/input_settings_app/application.fam b/applications/settings/input_settings_app/application.fam new file mode 100644 index 000000000..14be52fc4 --- /dev/null +++ b/applications/settings/input_settings_app/application.fam @@ -0,0 +1,9 @@ +App( + appid="input_settings", + name="Input", + apptype=FlipperAppType.SETTINGS, + entry_point="input_settings_app", + requires=["input"], + stack_size=1 * 1024, + order=100, +) diff --git a/applications/settings/input_settings_app/input_settings_app.c b/applications/settings/input_settings_app/input_settings_app.c new file mode 100644 index 000000000..4f3e101da --- /dev/null +++ b/applications/settings/input_settings_app/input_settings_app.c @@ -0,0 +1,107 @@ +#include +#include "input_settings_app.h" + +#define TAG "InputSettingsApp" + +#define VIBRO_TOUCH_LEVEL_COUNT 10 +// vibro touch human readable levels +const char* const vibro_touch_level_text[VIBRO_TOUCH_LEVEL_COUNT] = { + "OFF", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", +}; +// vibro touch levels tick valies delay +const uint32_t vibro_touch_level_value[VIBRO_TOUCH_LEVEL_COUNT] = + {0, 13, 16, 19, 21, 24, 27, 30, 33, 36}; + +static void input_settings_vibro_touch_level_changed(VariableItem* item) { + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, vibro_touch_level_text[index]); + + //change settings to selected + InputSettingsApp* app = variable_item_get_context(item); + app->settings->vibro_touch_level = vibro_touch_level_value[index]; + + // use RECORD for acces to input service instance and set settings + InputSettings* service_settings = furi_record_open(RECORD_INPUT_SETTINGS); + service_settings->vibro_touch_level = vibro_touch_level_value[index]; + furi_record_close(RECORD_INPUT_SETTINGS); +} + +static uint32_t input_settings_app_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +InputSettingsApp* input_settings_app_alloc(void) { + InputSettingsApp* app = malloc(sizeof(InputSettingsApp)); + app->gui = furi_record_open(RECORD_GUI); + + app->settings = malloc(sizeof(InputSettings)); + input_settings_load(app->settings); + + app->variable_item_list = variable_item_list_alloc(); + View* view = variable_item_list_get_view(app->variable_item_list); + view_set_previous_callback(view, input_settings_app_exit); + + VariableItem* item; + uint8_t value_index; + + item = variable_item_list_add( + app->variable_item_list, + "Buttons Vibro", + VIBRO_TOUCH_LEVEL_COUNT, + input_settings_vibro_touch_level_changed, + app); + + value_index = value_index_uint32( + app->settings->vibro_touch_level, vibro_touch_level_value, VIBRO_TOUCH_LEVEL_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, vibro_touch_level_text[value_index]); + + // create and setup view and view dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_add_view(app->view_dispatcher, InputSettingsViewVariableItemList, view); + view_dispatcher_switch_to_view(app->view_dispatcher, InputSettingsViewVariableItemList); + + return app; +} + +void input_settings_app_free(InputSettingsApp* app) { + furi_assert(app); + + // Variable item list + view_dispatcher_remove_view(app->view_dispatcher, InputSettingsViewVariableItemList); + variable_item_list_free(app->variable_item_list); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + + // Records + furi_record_close(RECORD_GUI); + free(app->settings); + free(app); +} + +// Enter point +int32_t input_settings_app(void* p) { + UNUSED(p); + InputSettingsApp* app = input_settings_app_alloc(); + + view_dispatcher_run(app->view_dispatcher); + + //save current settings; + input_settings_save(app->settings); + + input_settings_app_free(app); + return 0; +} diff --git a/applications/settings/input_settings_app/input_settings_app.h b/applications/settings/input_settings_app/input_settings_app.h new file mode 100644 index 000000000..c96ca49e9 --- /dev/null +++ b/applications/settings/input_settings_app/input_settings_app.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// input_settings_app stucture +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + VariableItemList* variable_item_list; + InputSettings* settings; +} InputSettingsApp; + +// list of menu views for view dispatcher +typedef enum { + InputSettingsViewVariableItemList, +} InputSettingsView; diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index 2462b32bd..be7af4c42 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -1,16 +1,20 @@ #include +#include #include #include #include #include +#include +#include -#define MAX_NOTIFICATION_SETTINGS 4 +#define MAX_NOTIFICATION_SETTINGS 5 typedef struct { NotificationApp* notification; Gui* gui; ViewDispatcher* view_dispatcher; VariableItemList* variable_item_list; + VariableItemList* variable_item_list_rgb; } NotificationAppSettings; static const NotificationSequence sequence_note_c = { @@ -104,6 +108,176 @@ const char* const vibro_text[VIBRO_COUNT] = { }; const bool vibro_value[VIBRO_COUNT] = {false, true}; +// --- RGB BACKLIGHT --- + +#define RGB_BACKLIGHT_INSTALLED_COUNT 2 +const char* const rgb_backlight_installed_text[RGB_BACKLIGHT_INSTALLED_COUNT] = { + "OFF", + "ON", +}; +const bool rgb_backlight_installed_value[RGB_BACKLIGHT_INSTALLED_COUNT] = {false, true}; + +#define RGB_BACKLIGHT_RAINBOW_MODE_COUNT 3 +const char* const rgb_backlight_rainbow_mode_text[RGB_BACKLIGHT_RAINBOW_MODE_COUNT] = { + "OFF", + "Rainbow", + "Wave", +}; +const uint32_t rgb_backlight_rainbow_mode_value[RGB_BACKLIGHT_RAINBOW_MODE_COUNT] = {0, 1, 2}; + +#define RGB_BACKLIGHT_RAINBOW_SPEED_COUNT 10 +const char* const rgb_backlight_rainbow_speed_text[RGB_BACKLIGHT_RAINBOW_SPEED_COUNT] = { + "0.1s", + "0.2s", + "0.3s", + "0.4s", + "0.5s", + "0.6s", + "0.7", + "0.8", + "0.9", + "1s", +}; + +const uint32_t rgb_backlight_rainbow_speed_value[RGB_BACKLIGHT_RAINBOW_SPEED_COUNT] = { + 100, + 200, + 300, + 400, + 500, + 600, + 700, + 800, + 900, + 1000, +}; + +#define RGB_BACKLIGHT_RAINBOW_STEP_COUNT 3 +const char* const rgb_backlight_rainbow_step_text[RGB_BACKLIGHT_RAINBOW_STEP_COUNT] = { + "1", + "2", + "3", +}; +const uint32_t rgb_backlight_rainbow_step_value[RGB_BACKLIGHT_RAINBOW_STEP_COUNT] = { + 1, + 2, + 3, +}; + +#define RGB_BACKLIGHT_RAINBOW_WIDE_COUNT 3 +const char* const rgb_backlight_rainbow_wide_text[RGB_BACKLIGHT_RAINBOW_WIDE_COUNT] = { + "1", + "2", + "3", +}; +const uint32_t rgb_backlight_rainbow_wide_value[RGB_BACKLIGHT_RAINBOW_WIDE_COUNT] = { + 30, + 40, + 50, +}; + +typedef enum { + MainViewId, + RGBViewId, +} ViewId; + +// --- RGB BACKLIGHT END --- + +// --- NIGHT SHIFT --- +#define NIGHT_SHIFT_COUNT 7 +const char* const night_shift_text[NIGHT_SHIFT_COUNT] = + {"OFF", "-10%", "-20%", "-30%", "-40%", "-50%", "-60%" + +}; +const float night_shift_value[NIGHT_SHIFT_COUNT] = { + 1.0f, + 0.9f, + 0.8f, + 0.7f, + 0.6f, + 0.5f, + 0.4f, +}; + +#define NIGHT_SHIFT_START_COUNT 14 +const char* const night_shift_start_text[NIGHT_SHIFT_START_COUNT] = { + "17:00", + "17:30", + "18:00", + "18:30", + "19:00", + "19:30", + "20:00", + "20:30", + "21:00", + "21:30", + "22:00", + "22:30", + "23:00", + "23:30", +}; +// values in minutes like 23:30 = 23*60+30=1410 +const uint32_t night_shift_start_value[NIGHT_SHIFT_START_COUNT] = { + 1020, + 1050, + 1080, + 1110, + 1140, + 1170, + 1200, + 1230, + 1260, + 1290, + 1320, + 1350, + 1380, + 1410, +}; + +#define NIGHT_SHIFT_END_COUNT 14 +const char* const night_shift_end_text[NIGHT_SHIFT_END_COUNT] = { + "05:00", + "05:30", + "06:00", + "06:30", + "07:00", + "07:30", + "08:00", + "08:30", + "09:00", + "09:30", + "10:00", + "10:30", + "11:00", + "11:30", +}; +// values in minutes like 6:30 = 6*60+30=390 +const uint32_t night_shift_end_value[NIGHT_SHIFT_END_COUNT] = { + 300, + 330, + 360, + 390, + 410, + 440, + 470, + 500, + 530, + 560, + 590, + 620, + 650, + 680, +}; + +// --- NIGHT SHIFT END --- + +#define LCD_INVERSION_COUNT 2 +const char* const lcd_inversion_text[LCD_INVERSION_COUNT] = { + "OFF", + "ON", +}; +const bool lcd_inversion_value[LCD_INVERSION_COUNT] = {false, true}; + static void contrast_changed(VariableItem* item) { NotificationAppSettings* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -119,6 +293,7 @@ static void backlight_changed(VariableItem* item) { variable_item_set_current_value_text(item, backlight_text[index]); app->notification->settings.display_brightness = backlight_value[index]; + notification_message(app->notification, &sequence_display_backlight_on); } @@ -168,6 +343,260 @@ static void vibro_changed(VariableItem* item) { notification_message(app->notification, &sequence_single_vibro); } +static void lcd_inversion_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, lcd_inversion_text[index]); + app->notification->settings.lcd_inversion = lcd_inversion_value[index]; + + Gui* gui = furi_record_open(RECORD_GUI); + u8x8_d_st756x_set_inversion(&gui->canvas->fb.u8x8, lcd_inversion_value[index]); + furi_record_close(RECORD_GUI); + + notification_message_save_settings(app->notification); +} + +//--- RGB BACKLIGHT --- + +static void rgb_backlight_installed_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, rgb_backlight_installed_text[index]); + app->notification->settings.rgb.rgb_backlight_installed = rgb_backlight_installed_value[index]; + set_rgb_backlight_installed_variable(rgb_backlight_installed_value[index]); + + // In case of user playing with rgb_backlight_installed swith: + // if user swith_off rgb_backlight_installed (but may be he have mod installed) + // then force set default orange color and stop rainbow timer + if(index == 0) { + rgb_backlight_set_led_static_color(2, 0); + rgb_backlight_set_led_static_color(1, 0); + rgb_backlight_set_led_static_color(0, 0); + SK6805_update(); + rainbow_timer_stop(app->notification); + // start rainbow (if its Enabled) or set saved static colors if user swith_on rgb_backlight_installed switch + } else { + if(app->notification->settings.rgb.rainbow_mode > 0) { + rainbow_timer_starter(app->notification); + } else { + rgb_backlight_set_led_static_color( + 2, app->notification->settings.rgb.led_2_color_index); + rgb_backlight_set_led_static_color( + 1, app->notification->settings.rgb.led_1_color_index); + rgb_backlight_set_led_static_color( + 0, app->notification->settings.rgb.led_0_color_index); + rgb_backlight_update( + app->notification->settings.display_brightness * + app->notification->current_night_shift); + } + } + + // Lock/Unlock all rgb settings depent from rgb_backlight_installed switch + for(int i = 1; i < 9; i++) { + VariableItem* t_item = variable_item_list_get(app->variable_item_list_rgb, i); + if(index == 0) { + variable_item_set_locked(t_item, true, "RGB\nOFF!"); + } else { + variable_item_set_locked(t_item, false, "RGB\nOFF!"); + } + } + notification_message_save_settings(app->notification); +} + +static void led_2_color_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, rgb_backlight_get_color_text(index)); + app->notification->settings.rgb.led_2_color_index = index; + + // dont update screen color if rainbow timer working + if(!furi_timer_is_running(app->notification->rainbow_timer)) { + rgb_backlight_set_led_static_color(2, index); + rgb_backlight_update( + app->notification->settings.display_brightness * + app->notification->current_night_shift); + } + + notification_message_save_settings(app->notification); +} + +static void led_1_color_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, rgb_backlight_get_color_text(index)); + app->notification->settings.rgb.led_1_color_index = index; + + // dont update screen color if rainbow timer working + if(!furi_timer_is_running(app->notification->rainbow_timer)) { + rgb_backlight_set_led_static_color(1, index); + rgb_backlight_update( + app->notification->settings.display_brightness * + app->notification->current_night_shift); + } + + notification_message_save_settings(app->notification); +} + +static void led_0_color_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, rgb_backlight_get_color_text(index)); + app->notification->settings.rgb.led_0_color_index = index; + + // dont update screen color if rainbow timer working + if(!furi_timer_is_running(app->notification->rainbow_timer)) { + rgb_backlight_set_led_static_color(0, index); + rgb_backlight_update( + app->notification->settings.display_brightness * + app->notification->current_night_shift); + } + + notification_message_save_settings(app->notification); +} + +static void rgb_backlight_rainbow_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, rgb_backlight_rainbow_mode_text[index]); + app->notification->settings.rgb.rainbow_mode = rgb_backlight_rainbow_mode_value[index]; + + // restore saved rgb backlight settings if we switch_off effects + if(index == 0) { + rgb_backlight_set_led_static_color(2, app->notification->settings.rgb.led_2_color_index); + rgb_backlight_set_led_static_color(1, app->notification->settings.rgb.led_1_color_index); + rgb_backlight_set_led_static_color(0, app->notification->settings.rgb.led_0_color_index); + rgb_backlight_update( + app->notification->settings.display_brightness * + app->notification->current_night_shift); + rainbow_timer_stop(app->notification); + } else { + rainbow_timer_starter(app->notification); + } + + notification_message_save_settings(app->notification); +} + +static void rgb_backlight_rainbow_speed_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, rgb_backlight_rainbow_speed_text[index]); + app->notification->settings.rgb.rainbow_speed_ms = rgb_backlight_rainbow_speed_value[index]; + + // save settings and restart timer with new speed value + rainbow_timer_starter(app->notification); + notification_message_save_settings(app->notification); +} + +static void rgb_backlight_rainbow_step_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, rgb_backlight_rainbow_step_text[index]); + app->notification->settings.rgb.rainbow_step = rgb_backlight_rainbow_step_value[index]; + + notification_message_save_settings(app->notification); +} + +static void rgb_backlight_rainbow_saturation_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + + // saturation must be 1..255, so we do (0..254)+1 + uint8_t index = variable_item_get_current_value_index(item) + 1; + char valtext[4] = {}; + snprintf(valtext, sizeof(valtext), "%d", index); + variable_item_set_current_value_text(item, valtext); + app->notification->settings.rgb.rainbow_saturation = index; + + notification_message_save_settings(app->notification); +} + +static void rgb_backlight_rainbow_wide_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, rgb_backlight_rainbow_wide_text[index]); + app->notification->settings.rgb.rainbow_wide = rgb_backlight_rainbow_wide_value[index]; + + notification_message_save_settings(app->notification); +} + +// open settings.rgb_view if user press OK on last (index=10) menu string +void variable_item_list_enter_callback(void* context, uint32_t index) { + UNUSED(context); + NotificationAppSettings* app = context; + + if(index == 10) { + view_dispatcher_switch_to_view(app->view_dispatcher, RGBViewId); + } +} + +// switch to main view on exit from settings.rgb_view +static uint32_t notification_app_rgb_settings_exit(void* context) { + UNUSED(context); + return MainViewId; +} +//--- RGB BACKLIGHT END --- + +// --- NIGHT SHIFT --- + +static void night_shift_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, night_shift_text[index]); + app->notification->settings.night_shift = night_shift_value[index]; + app->notification->current_night_shift = night_shift_value[index]; + app->notification->current_night_shift = night_shift_value[index]; + + // force demo night_shift brightness to rgb backlight and stock backlight + notification_message(app->notification, &sequence_display_backlight_on); + + for(int i = 4; i < 6; i++) { + VariableItem* t_item = variable_item_list_get(app->variable_item_list, i); + if(index == 0) { + variable_item_set_locked(t_item, true, "Night Shift\nOFF!"); + } else { + variable_item_set_locked(t_item, false, "Night Shift\nOFF!"); + } + } + + if(night_shift_value[index] != 1) { + night_shift_timer_start(app->notification); + } else { + night_shift_timer_stop(app->notification); + } + + notification_message_save_settings(app->notification); +} + +static void night_shift_start_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, night_shift_start_text[index]); + app->notification->settings.night_shift_start = night_shift_start_value[index]; + + notification_message_save_settings(app->notification); +} + +static void night_shift_end_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, night_shift_end_text[index]); + app->notification->settings.night_shift_end = night_shift_end_value[index]; + + notification_message_save_settings(app->notification); +} + +// --- NIGHT SHIFT END --- + static uint32_t notification_app_settings_exit(void* context) { UNUSED(context); return VIEW_NONE; @@ -180,11 +609,18 @@ static NotificationAppSettings* alloc_settings(void) { app->variable_item_list = variable_item_list_alloc(); View* view = variable_item_list_get_view(app->variable_item_list); - view_set_previous_callback(view, notification_app_settings_exit); VariableItem* item; uint8_t value_index; + //set callback for exit from main view + view_set_previous_callback(view, notification_app_settings_exit); + + //--- RGB BACKLIGHT --- + // set callback for OK pressed in notification settings menu + variable_item_list_set_enter_callback( + app->variable_item_list, variable_item_list_enter_callback, app); + item = variable_item_list_add( app->variable_item_list, "LCD Contrast", CONTRAST_COUNT, contrast_changed, app); value_index = @@ -206,6 +642,40 @@ static NotificationAppSettings* alloc_settings(void) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, delay_text[value_index]); + // --- NIGHT SHIFT --- + item = variable_item_list_add( + app->variable_item_list, "Night Shift", NIGHT_SHIFT_COUNT, night_shift_changed, app); + value_index = value_index_float( + app->notification->settings.night_shift, night_shift_value, NIGHT_SHIFT_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, night_shift_text[value_index]); + + item = variable_item_list_add( + app->variable_item_list, + " . Start", + NIGHT_SHIFT_START_COUNT, + night_shift_start_changed, + app); + value_index = value_index_uint32( + app->notification->settings.night_shift_start, + night_shift_start_value, + NIGHT_SHIFT_START_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, night_shift_start_text[value_index]); + variable_item_set_locked( + item, (app->notification->settings.night_shift == 1), "Night Shift \nOFF!"); + + item = variable_item_list_add( + app->variable_item_list, " . End", NIGHT_SHIFT_END_COUNT, night_shift_end_changed, app); + value_index = value_index_uint32( + app->notification->settings.night_shift_end, night_shift_end_value, NIGHT_SHIFT_END_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, night_shift_end_text[value_index]); + variable_item_set_locked( + item, (app->notification->settings.night_shift == 1), "Night Shift \nOFF!"); + + // --- NIGHT SHIFT END--- + item = variable_item_list_add( app->variable_item_list, "LED Brightness", BACKLIGHT_COUNT, led_changed, app); value_index = value_index_float( @@ -241,17 +711,166 @@ static NotificationAppSettings* alloc_settings(void) { variable_item_set_current_value_text(item, vibro_text[value_index]); } + item = variable_item_list_add( + app->variable_item_list, "LCD Inversion", LCD_INVERSION_COUNT, lcd_inversion_changed, app); + value_index = value_index_bool( + app->notification->settings.lcd_inversion, lcd_inversion_value, LCD_INVERSION_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, lcd_inversion_text[value_index]); + + //--- RGB BACKLIGHT --- + item = variable_item_list_add(app->variable_item_list, "RGB Mod Settings", 0, NULL, app); + //--- RGB BACKLIGHT END --- + + app->variable_item_list_rgb = variable_item_list_alloc(); + View* view_rgb = variable_item_list_get_view(app->variable_item_list_rgb); + + // set callback for exit from rgb settings menu + view_set_previous_callback(view_rgb, notification_app_rgb_settings_exit); + + item = variable_item_list_add( + app->variable_item_list_rgb, + "RGB backlight installed", + RGB_BACKLIGHT_INSTALLED_COUNT, + rgb_backlight_installed_changed, + app); + value_index = value_index_bool( + app->notification->settings.rgb.rgb_backlight_installed, + rgb_backlight_installed_value, + RGB_BACKLIGHT_INSTALLED_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rgb_backlight_installed_text[value_index]); + + // We (humans) are numbering LEDs from left to right as 1..3, but hardware have another order from right to left 2..0 + // led_1 color + item = variable_item_list_add( + app->variable_item_list_rgb, + "LED 1 Color", + rgb_backlight_get_color_count(), + led_2_color_changed, + app); + value_index = app->notification->settings.rgb.led_2_color_index; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rgb_backlight_get_color_text(value_index)); + variable_item_set_locked( + item, (app->notification->settings.rgb.rgb_backlight_installed == 0), "RGB MOD \nOFF!"); + + // led_2 color + item = variable_item_list_add( + app->variable_item_list_rgb, + "LED 2 Color", + rgb_backlight_get_color_count(), + led_1_color_changed, + app); + value_index = app->notification->settings.rgb.led_1_color_index; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rgb_backlight_get_color_text(value_index)); + variable_item_set_locked( + item, (app->notification->settings.rgb.rgb_backlight_installed == 0), "RGB MOD \nOFF!"); + + // led 3 color + item = variable_item_list_add( + app->variable_item_list_rgb, + "LED 3 Color", + rgb_backlight_get_color_count(), + led_0_color_changed, + app); + value_index = app->notification->settings.rgb.led_0_color_index; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rgb_backlight_get_color_text(value_index)); + variable_item_set_locked( + item, (app->notification->settings.rgb.rgb_backlight_installed == 0), "RGB MOD \nOFF!"); + + // Efects + item = variable_item_list_add( + app->variable_item_list_rgb, + "Effects", + RGB_BACKLIGHT_RAINBOW_MODE_COUNT, + rgb_backlight_rainbow_changed, + app); + value_index = value_index_uint32( + app->notification->settings.rgb.rainbow_mode, + rgb_backlight_rainbow_mode_value, + RGB_BACKLIGHT_RAINBOW_MODE_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rgb_backlight_rainbow_mode_text[value_index]); + variable_item_set_locked( + item, (app->notification->settings.rgb.rgb_backlight_installed == 0), "RGB MOD \nOFF!"); + + item = variable_item_list_add( + app->variable_item_list_rgb, + " . Speed", + RGB_BACKLIGHT_RAINBOW_SPEED_COUNT, + rgb_backlight_rainbow_speed_changed, + app); + value_index = value_index_uint32( + app->notification->settings.rgb.rainbow_speed_ms, + rgb_backlight_rainbow_speed_value, + RGB_BACKLIGHT_RAINBOW_SPEED_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rgb_backlight_rainbow_speed_text[value_index]); + variable_item_set_locked( + item, (app->notification->settings.rgb.rgb_backlight_installed == 0), "RGB MOD \nOFF!"); + + item = variable_item_list_add( + app->variable_item_list_rgb, + " . Color step", + RGB_BACKLIGHT_RAINBOW_STEP_COUNT, + rgb_backlight_rainbow_step_changed, + app); + value_index = value_index_uint32( + app->notification->settings.rgb.rainbow_step, + rgb_backlight_rainbow_step_value, + RGB_BACKLIGHT_RAINBOW_STEP_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rgb_backlight_rainbow_step_text[value_index]); + variable_item_set_locked( + item, (app->notification->settings.rgb.rgb_backlight_installed == 0), "RGB MOD \nOFF!"); + + item = variable_item_list_add( + app->variable_item_list_rgb, + " . Saturation", + 255, + rgb_backlight_rainbow_saturation_changed, + app); + value_index = app->notification->settings.rgb.rainbow_saturation; + variable_item_set_current_value_index(item, value_index); + char valtext[4] = {}; + snprintf(valtext, sizeof(valtext), "%d", value_index); + variable_item_set_current_value_text(item, valtext); + variable_item_set_locked( + item, (app->notification->settings.rgb.rgb_backlight_installed == 0), "RGB MOD \nOFF!"); + + item = variable_item_list_add( + app->variable_item_list_rgb, + " . Wave wide", + RGB_BACKLIGHT_RAINBOW_WIDE_COUNT, + rgb_backlight_rainbow_wide_changed, + app); + value_index = value_index_uint32( + app->notification->settings.rgb.rainbow_wide, + rgb_backlight_rainbow_wide_value, + RGB_BACKLIGHT_RAINBOW_WIDE_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rgb_backlight_rainbow_wide_text[value_index]); + variable_item_set_locked( + item, (app->notification->settings.rgb.rgb_backlight_installed == 0), "RGB MOD \nOFF!"); + + //--- RGB BACKLIGHT END --- + app->view_dispatcher = view_dispatcher_alloc(); view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - view_dispatcher_add_view(app->view_dispatcher, 0, view); - view_dispatcher_switch_to_view(app->view_dispatcher, 0); - + view_dispatcher_add_view(app->view_dispatcher, MainViewId, view); + view_dispatcher_add_view(app->view_dispatcher, RGBViewId, view_rgb); + view_dispatcher_switch_to_view(app->view_dispatcher, MainViewId); return app; } static void free_settings(NotificationAppSettings* app) { - view_dispatcher_remove_view(app->view_dispatcher, 0); + view_dispatcher_remove_view(app->view_dispatcher, MainViewId); + view_dispatcher_remove_view(app->view_dispatcher, RGBViewId); variable_item_list_free(app->variable_item_list); + variable_item_list_free(app->variable_item_list_rgb); view_dispatcher_free(app->view_dispatcher); furi_record_close(RECORD_GUI); @@ -264,6 +883,12 @@ int32_t notification_settings_app(void* p) { NotificationAppSettings* app = alloc_settings(); view_dispatcher_run(app->view_dispatcher); notification_message_save_settings(app->notification); + + // Automaticaly switch_off debug_mode when user exit from settings with enabled rgb_backlight_installed + // if(app->notification->settings.rgb_backlight_installed) { + // furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); + // } + free_settings(app); return 0; } diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_start.c b/applications/settings/power_settings_app/scenes/power_settings_scene_start.c index 3fc3a8a30..42c53ed02 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_start.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_start.c @@ -15,6 +15,21 @@ const char* const auto_poweroff_delay_text[AUTO_POWEROFF_DELAY_COUNT] = const uint32_t auto_poweroff_delay_value[AUTO_POWEROFF_DELAY_COUNT] = {0, 300000, 600000, 900000, 1800000, 2700000, 3600000, 5400000}; +#define CHARGE_SUPRESS_PERCENT_COUNT 6 +const char* const charge_supress_percent_text[CHARGE_SUPRESS_PERCENT_COUNT] = + {"OFF", "90%", "85%", "80%", "75%", "70%"}; + +const uint32_t charge_supress_percent_value[CHARGE_SUPRESS_PERCENT_COUNT] = {0, 90, 85, 80, 75, 70}; + +// change variable_item_list visible text and charge_supress_percent_settings when user change item in variable_item_list +static void power_settings_scene_start_charge_supress_percent_changed(VariableItem* item) { + PowerSettingsApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, charge_supress_percent_text[index]); + app->settings.charge_supress_percent = charge_supress_percent_value[index]; +} + // change variable_item_list visible text and app_poweroff_delay_time_settings when user change item in variable_item_list static void power_settings_scene_start_auto_poweroff_delay_changed(VariableItem* item) { PowerSettingsApp* app = variable_item_get_context(item); @@ -24,9 +39,8 @@ static void power_settings_scene_start_auto_poweroff_delay_changed(VariableItem* app->settings.auto_poweroff_delay_ms = auto_poweroff_delay_value[index]; } -static void power_settings_scene_start_submenu_callback( - void* context, - uint32_t index) { //show selected menu screen +static void power_settings_scene_start_submenu_callback(void* context, uint32_t index) { + //show selected menu screen by index furi_assert(context); PowerSettingsApp* app = context; view_dispatcher_send_custom_event(app->view_dispatcher, index); @@ -42,11 +56,12 @@ void power_settings_scene_start_on_enter(void* context) { VariableItem* item; uint8_t value_index; + item = variable_item_list_add( variable_item_list, "Auto PowerOff", AUTO_POWEROFF_DELAY_COUNT, - power_settings_scene_start_auto_poweroff_delay_changed, //function for change visible item list value and app settings + power_settings_scene_start_auto_poweroff_delay_changed, app); value_index = value_index_uint32( @@ -56,14 +71,25 @@ void power_settings_scene_start_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, auto_poweroff_delay_text[value_index]); + item = variable_item_list_add( + variable_item_list, + "Limit Charge", + CHARGE_SUPRESS_PERCENT_COUNT, + power_settings_scene_start_charge_supress_percent_changed, + app); + + value_index = value_index_uint32( + app->settings.charge_supress_percent, + charge_supress_percent_value, + CHARGE_SUPRESS_PERCENT_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, charge_supress_percent_text[value_index]); + variable_item_list_set_selected_item( variable_item_list, scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneStart)); - variable_item_list_set_enter_callback( //callback to show next mennu screen - variable_item_list, - power_settings_scene_start_submenu_callback, - app); - + variable_item_list_set_enter_callback( + variable_item_list, power_settings_scene_start_submenu_callback, app); view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewVariableItemList); } @@ -88,5 +114,5 @@ bool power_settings_scene_start_on_event(void* context, SceneManagerEvent event) void power_settings_scene_start_on_exit(void* context) { PowerSettingsApp* app = context; variable_item_list_reset(app->variable_item_list); - power_settings_save(&app->settings); //actual need save every time when use ? + power_settings_save(&app->settings); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c index e351a2ef7..f03474ac1 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c @@ -1,131 +1,20 @@ #include "../storage_settings.h" -enum StorageSettingsStartSubmenuIndex { - StorageSettingsStartSubmenuIndexInternalInfo, - StorageSettingsStartSubmenuIndexSDInfo, - StorageSettingsStartSubmenuIndexUnmount, - StorageSettingsStartSubmenuIndexFormat, - StorageSettingsStartSubmenuIndexBenchy, - StorageSettingsStartSubmenuIndexFactoryReset -}; - -static void storage_settings_scene_start_submenu_callback(void* context, uint32_t index) { - StorageSettings* app = context; - - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - void storage_settings_scene_start_on_enter(void* context) { StorageSettings* app = context; - Submenu* submenu = app->submenu; - - submenu_add_item( - submenu, - "About Internal Storage", - StorageSettingsStartSubmenuIndexInternalInfo, - storage_settings_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "About SD Card", - StorageSettingsStartSubmenuIndexSDInfo, - storage_settings_scene_start_submenu_callback, - app); FS_Error sd_status = storage_sd_status(app->fs_api); - if(sd_status != FSE_OK) { - submenu_add_item( - submenu, - "Mount SD Card", - StorageSettingsStartSubmenuIndexUnmount, - storage_settings_scene_start_submenu_callback, - app); - } else { - submenu_add_item( - submenu, - "Unmount SD Card", - StorageSettingsStartSubmenuIndexUnmount, - storage_settings_scene_start_submenu_callback, - app); - } - - submenu_add_item( - submenu, - "Format SD Card", - StorageSettingsStartSubmenuIndexFormat, - storage_settings_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "Benchmark SD Card", - StorageSettingsStartSubmenuIndexBenchy, - storage_settings_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "Factory Reset", - StorageSettingsStartSubmenuIndexFactoryReset, - storage_settings_scene_start_submenu_callback, - app); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, StorageSettingsStart)); - - view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewSubmenu); + app->helper_descriptor->options[STORAGE_SETTINGS_MOUNT_INDEX].name = + (sd_status != FSE_OK) ? "Mount SD Card" : "Unmount SD Card"; + submenu_settings_helpers_scene_enter(app->settings_helper); } bool storage_settings_scene_start_on_event(void* context, SceneManagerEvent event) { StorageSettings* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - switch(event.event) { - case StorageSettingsStartSubmenuIndexSDInfo: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexSDInfo); - scene_manager_next_scene(app->scene_manager, StorageSettingsSDInfo); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexInternalInfo: - scene_manager_set_scene_state( - app->scene_manager, - StorageSettingsStart, - StorageSettingsStartSubmenuIndexInternalInfo); - scene_manager_next_scene(app->scene_manager, StorageSettingsInternalInfo); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexUnmount: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexUnmount); - scene_manager_next_scene(app->scene_manager, StorageSettingsUnmountConfirm); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexFormat: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexFormat); - scene_manager_next_scene(app->scene_manager, StorageSettingsFormatConfirm); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexBenchy: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexBenchy); - scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmarkConfirm); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexFactoryReset: - scene_manager_set_scene_state( - app->scene_manager, - StorageSettingsStart, - StorageSettingsStartSubmenuIndexFactoryReset); - scene_manager_next_scene(app->scene_manager, StorageSettingsFactoryReset); - consumed = true; - break; - } - } - return consumed; + return submenu_settings_helpers_scene_event(app->settings_helper, event); } void storage_settings_scene_start_on_exit(void* context) { StorageSettings* app = context; - submenu_reset(app->submenu); + submenu_settings_helpers_scene_exit(app->settings_helper); } diff --git a/applications/settings/storage_settings/storage_settings.c b/applications/settings/storage_settings/storage_settings.c index 354632890..07656431c 100644 --- a/applications/settings/storage_settings/storage_settings.c +++ b/applications/settings/storage_settings/storage_settings.c @@ -1,5 +1,19 @@ #include "storage_settings.h" +const SubmenuSettingsHelperDescriptor descriptor_template = { + .app_name = "Storage", + .options_cnt = 6, + .options = + { + {.name = "About Internal Storage", .scene_id = StorageSettingsInternalInfo}, + {.name = "About SD Card", .scene_id = StorageSettingsSDInfo}, + {.name = "Unmount SD Card", .scene_id = StorageSettingsUnmountConfirm}, + {.name = "Format SD Card", .scene_id = StorageSettingsFormatConfirm}, + {.name = "Benchmark SD Card", .scene_id = StorageSettingsBenchmarkConfirm}, + {.name = "Factory Reset", .scene_id = StorageSettingsFactoryReset}, + }, +}; + static bool storage_settings_custom_event_callback(void* context, uint32_t event) { furi_assert(context); StorageSettings* app = context; @@ -40,12 +54,27 @@ static StorageSettings* storage_settings_alloc(void) { view_dispatcher_add_view( app->view_dispatcher, StorageSettingsViewDialogEx, dialog_ex_get_view(app->dialog_ex)); - scene_manager_next_scene(app->scene_manager, StorageSettingsStart); + size_t descriptor_size = + sizeof(SubmenuSettingsHelperDescriptor) + + (descriptor_template.options_cnt * sizeof(SubmenuSettingsHelperOption)); + app->helper_descriptor = malloc(descriptor_size); + memcpy(app->helper_descriptor, &descriptor_template, descriptor_size); + app->settings_helper = submenu_settings_helpers_alloc(app->helper_descriptor); + submenu_settings_helpers_assign_objects( + app->settings_helper, + app->view_dispatcher, + app->scene_manager, + app->submenu, + StorageSettingsViewSubmenu, + StorageSettingsStart); return app; } static void storage_settings_free(StorageSettings* app) { + submenu_settings_helpers_free(app->settings_helper); + free(app->helper_descriptor); + view_dispatcher_remove_view(app->view_dispatcher, StorageSettingsViewSubmenu); submenu_free(app->submenu); @@ -68,6 +97,10 @@ int32_t storage_settings_app(void* p) { UNUSED(p); StorageSettings* app = storage_settings_alloc(); + if(!submenu_settings_helpers_app_start(app->settings_helper, p)) { + scene_manager_next_scene(app->scene_manager, StorageSettingsStart); + } + view_dispatcher_run(app->view_dispatcher); storage_settings_free(app); diff --git a/applications/settings/storage_settings/storage_settings.h b/applications/settings/storage_settings/storage_settings.h index fd841623e..5f4c6404e 100644 --- a/applications/settings/storage_settings/storage_settings.h +++ b/applications/settings/storage_settings/storage_settings.h @@ -16,6 +16,10 @@ #include "scenes/storage_settings_scene.h" +#include + +#define STORAGE_SETTINGS_MOUNT_INDEX 2 + #ifdef __cplusplus extern "C" { #endif @@ -36,6 +40,10 @@ typedef struct { // text FuriString* text_string; + + // helpers + SubmenuSettingsHelperDescriptor* helper_descriptor; + SubmenuSettingsHelper* settings_helper; } StorageSettings; typedef enum { diff --git a/applications/system/application.fam b/applications/system/application.fam index 9a7ae40b1..e17bf3b7d 100644 --- a/applications/system/application.fam +++ b/applications/system/application.fam @@ -5,6 +5,7 @@ App( provides=[ "updater_app", "js_app", + "findmy_startup", # "archive", ], ) diff --git a/applications/system/find_my_flipper/README.md b/applications/system/find_my_flipper/README.md new file mode 100644 index 000000000..5035d3179 --- /dev/null +++ b/applications/system/find_my_flipper/README.md @@ -0,0 +1,113 @@ +# FindMy Flipper - FindMy SmartTag Emulator + +This app extends the functionality of the FlipperZero's bluetooth capabilities, enabling it to act as an Apple AirTag or Samsung SmartTag, or even both simultaneously. It utilizes the FlipperZero's BLE beacon to broadcast a SmartTag signal to be picked up by the FindMy Network. I made this to serve as a versatile tool for tracking purposes, offering the ability to clone existing tags, generate OpenHaystack key pairs for integration with Apple's FindMy network, and tune the device's beacon broadcast settings. + +## Features + +1. Tag Emulation: Clone your existing Apple AirTag or Samsung SmartTag to the FlipperZero, or generate a key pair for use with the FindMy network without owning an actual AirTag. +2. Customization: Users can adjust the interval between beacon broadcasts and modify the transmit power to suit their needs, optimizing for both visibility and battery life. +3. Efficient Background Operation: The app is optimized to run in the background, ensuring that your FlipperZero can still be tracked with minimal battery usage and without stopping normal use. + +## Usage Guide + +### Step 1: Installation +- **Option A:** Use the released/precompiled firmware appropriate (FAP) for your device. +- **Option B:** Build the firmware yourself using `fbt/ufbt`. +- Both Installation options require you to be running a dev build of firmware. When release gets access to the extra BLE beacon this will change, thank you! +- All firmware should now work with main branch, including icons + +### Step 2: Obtaining SmartTag Data + +#### Option A: Cloning Existing Tag (Preferred and allows you to track without additional setup) +1. **Pair a Tag:** First, pair an AirTag or Samsung SmartTag with your device. +2. **Enter 'Lost' Mode:** Keep the tag away from the device it's registered to for approximately 15 minutes. +3. **Download nrfConnect:** Install nrfConnect from the Google Play Store. (Apple version doesn't reveal the needed Raw data, looking for a workaround) +4. **Filter and Scan:** + - Open the app, click on filters, and exclude all except for the brand of your tag (Apple/Samsung). + - Adjust the RSSI to the lowest setting (-40 dBm). + - Initiate a scan. Wait for your SmartTag to appear as a "FindMy" device. +5. **Capture Data:** Click **Raw** or **View Raw** to capture your **payload** and note your tag's **MAC Address**. Immediately remove the tag's battery to prevent key/MAC rotation. +6. **Enter Data in FlipperZero App:** Input the captured **payload** and **MAC Address** into the FlipperZero app. + +#### Option B: Open Haystack Method +1. **Generate a Tag:** Download the `generate_keys.py` file and execute it in your terminal. (You will need cryptography ```python3 -m pip install cryptography```) +2. **Follow Prompts:** During execution, you'll be prompted for inputs. By the end, you'll obtain a **Private Key**, **Public Key**, **Payload**, and **MAC Address**. + - **Private Key** is necessary to receive location reports from Apple. + - **MAC Address** should be registered in the FlipperZero app: + 1. Open the app and navigate to the config menu. + 2. Choose "register tag" and enter the MAC Address when prompted. + 3. A payload dialog will appear next. Enter your **Payload** here. + 4. Click save. +3. **Configuration Completion:** With this setup, your device is ready for Open Haystack. Proceed with the specific steps for Open Haystack or MaclessHaystack based on your setup. + - Don't Own a Mac: https://github.com/dchristl/macless-haystack or https://github.com/Chapoly1305/FindMy + - Own a Mac: https://github.com/seemoo-lab/openhaystack + +To use OpenHayStack for tracking, you must use MacOS lower than version 14 (Mail Plug-in Incompetiablity of MacOS 14+ seemoo-lab/openhaystack#224). If you do own a device, I believe a convertor script can be provided without much of effort. If you do not own a Mac device or the system has been upgraded to 14 and beyond. The alternative solution includes, + + https://github.com/dchristl/macless-haystack (recommended in README) + https://github.com/Chapoly1305/FindMy (a project uses python and docker to provide location lookup as a backend service) + +## Setting Up on Mac with OpenHayStack (OHS) App -- If you own a Mac instructions + +Follow these steps to get everything working on a Mac using the latest version of the OpenHayStack app. +Thanks to Wr3nch for the help + +### Step 1: Create a New Device +- Start by creating a new device in the OpenHayStack app, but **do not deploy** it immediately after creation. + +### Step 2: Export Configuration +- Choose to **EXPORT** the configuration by selecting "all accessories as file." To simplify, ensure you only have one entry in the list before exporting. +- It is crucial that the export format is in JSON. + +### Step 3: Modify the JSON File +Open the exported JSON file in a text editor and make the following changes: +- **Left OHS, Right keys from my ```generate_keys.py``` script:** + - `symmetricKey` should be set to the `Hashed adv key`. + - `privateKey` should be replaced with your `Private Key`. + - `oldestRelevantSymmetricKey` should also use the `Hashed adv key`. +- Additionally, update the following attributes to `true`: + - `"isDeployed": true` + - `"isActive": true` + +### Step 4: Re-import the Configuration +- After saving your changes to the JSON file, re-import it back into OpenHayStack. + +### Step 5: Adjust Settings in OHS App +- In the OpenHayStack Mac App, navigate to the top bar and change the time setting from `1 Day` to `30min`. +- Give it some time to process and apply the new settings. + +By following these steps, you should have your device set up and ready to go with OpenHayStack on a Mac. +**** + +### Step 3: Configuration on the FlipperZero +- Upon launching the app, open the config menu and either click ```Import Tag From File``` or ```Register Tag Manually```. Put your generated .keys file onto the FlipperZero SD card inside the AppsData/FindMyFlipper folder to import from file. Or you can manually enter the tag information. When using the cloning method, you can export a .txt file from nrfConnect (click save button) amd place that in the same folder in order to import. + +### Step 4: Tracking +- Once the app is configured, your FlipperZero can be tracked using the relevant platform's tracking service (FindMy app for Apple devices, SmartThings for Samsung devices, and respective web browsers). If using generated keys and OpenHaystack then you can track on the OHS app or via the Macless Haystack setup. Links to both are above + + +Customization + +- Beacon Interval: Adjust how frequently your FlipperZero broadcasts its presence. +- Transmit Power: Increase or decrease the signal strength to balance between tracking range and battery life. + +Background Use + +The app is designed to have a negligible impact on battery life, even when running in the background. This allows for continuous tracking without the need for frequent recharging. + +Compatibility + +- Apple devices for AirTag tracking via the FindMy network. +- Any device that supports Samsung SmartTag tracking, including web browsers (previously FindMyMobile). + +Thanks + +- Huge thanks to all the people that contributed to the OpenHaystack project, supporting projects, and guides on the subject. This wouldn't be a thing without any of you! Special thanks to WillyJL for helping get the app input working and overall overhaul of the apps functions! + +Legal and Privacy + +This app is intended for personal and educational use. Users are responsible for complying with local privacy laws and regulations regarding tracking devices. The cloning and emulation of tracking tags should be done responsibly and with respect to the ownership of the original devices. + +Disclaimer + +This project is not affiliated with Apple Inc. or Samsung. All product names, logos, and brands are property of their respective owners. Use this app responsibly and ethically. diff --git a/applications/system/find_my_flipper/application.fam b/applications/system/find_my_flipper/application.fam new file mode 100644 index 000000000..c9b870282 --- /dev/null +++ b/applications/system/find_my_flipper/application.fam @@ -0,0 +1,24 @@ +App( + appid="findmy", + name="FindMy Flipper", + apptype=FlipperAppType.EXTERNAL, + entry_point="findmy_main", + requires=["gui"], + stack_size=2 * 1024, + fap_icon="location_icon.png", + fap_icon_assets="icons", + fap_category="Bluetooth", + fap_author="@MatthewKuKanich", + fap_weburl="https://github.com/MatthewKuKanich/FindMyFlipper", + fap_version="3.5", + fap_description="BLE FindMy Location Beacon", +) + +App( + appid="findmy_startup", + targets=["f7"], + apptype=FlipperAppType.STARTUP, + entry_point="findmy_startup", + sources=["findmy_startup.c", "findmy_state.c"], + order=1210, +) diff --git a/applications/system/find_my_flipper/findmy.c b/applications/system/find_my_flipper/findmy.c new file mode 100644 index 000000000..5afca1a19 --- /dev/null +++ b/applications/system/find_my_flipper/findmy.c @@ -0,0 +1,141 @@ +#include "findmy_i.h" + +static bool findmy_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + FindMy* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool findmy_back_event_callback(void* context) { + furi_assert(context); + FindMy* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static FindMy* findmy_app_alloc() { + FindMy* app = malloc(sizeof(FindMy)); + + app->gui = furi_record_open(RECORD_GUI); + app->storage = furi_record_open(RECORD_STORAGE); + app->dialogs = furi_record_open(RECORD_DIALOGS); + + app->view_dispatcher = view_dispatcher_alloc(); + + app->scene_manager = scene_manager_alloc(&findmy_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback(app->view_dispatcher, findmy_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, findmy_back_event_callback); + + app->findmy_main = findmy_main_alloc(app); + view_dispatcher_add_view( + app->view_dispatcher, FindMyViewMain, findmy_main_get_view(app->findmy_main)); + + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, FindMyViewByteInput, byte_input_get_view(app->byte_input)); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + FindMyViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, FindMyViewPopup, popup_get_view(app->popup)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + findmy_state_load(&app->state); + findmy_state_apply(&app->state); + + findmy_main_update_active(app->findmy_main, app->state.beacon_active); + findmy_main_update_interval(app->findmy_main, app->state.broadcast_interval); + findmy_main_toggle_mac(app->findmy_main, app->state.show_mac); + findmy_main_update_mac(app->findmy_main, app->state.mac); + findmy_main_update_type(app->findmy_main, app->state.tag_type); + + return app; +} + +static void findmy_app_free(FindMy* app) { + furi_assert(app); + + view_dispatcher_remove_view(app->view_dispatcher, FindMyViewPopup); + popup_free(app->popup); + + view_dispatcher_remove_view(app->view_dispatcher, FindMyViewVarItemList); + variable_item_list_free(app->var_item_list); + + view_dispatcher_remove_view(app->view_dispatcher, FindMyViewByteInput); + byte_input_free(app->byte_input); + + view_dispatcher_remove_view(app->view_dispatcher, FindMyViewMain); + findmy_main_free(app->findmy_main); + + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t findmy_main(void* p) { + UNUSED(p); + FindMy* app = findmy_app_alloc(); + + scene_manager_next_scene(app->scene_manager, FindMySceneMain); + + view_dispatcher_run(app->view_dispatcher); + + findmy_app_free(app); + return 0; +} + +void findmy_change_broadcast_interval(FindMy* app, uint8_t value) { + if(value > 10 || value < 1) { + return; + } + app->state.broadcast_interval = value; + findmy_main_update_interval(app->findmy_main, app->state.broadcast_interval); + findmy_state_save_and_apply(&app->state); +} + +void findmy_change_transmit_power(FindMy* app, uint8_t value) { + if(value > 6) { + return; + } + app->state.transmit_power = value; + findmy_state_save_and_apply(&app->state); +} + +void findmy_toggle_show_mac(FindMy* app, bool show_mac) { + app->state.show_mac = show_mac; + findmy_main_toggle_mac(app->findmy_main, app->state.show_mac); + findmy_state_save_and_apply(&app->state); +} + +void findmy_toggle_beacon(FindMy* app) { + app->state.beacon_active = !app->state.beacon_active; + findmy_state_save_and_apply(&app->state); + findmy_main_update_active(app->findmy_main, app->state.beacon_active); +} + +void findmy_set_tag_type(FindMy* app, FindMyType type) { + app->state.tag_type = type; + findmy_state_save_and_apply(&app->state); + findmy_main_update_type(app->findmy_main, type); +} + +void findmy_reverse_mac_addr(uint8_t mac_addr[GAP_MAC_ADDR_SIZE]) { + uint8_t tmp; + for(size_t i = 0; i < GAP_MAC_ADDR_SIZE / 2; i++) { + tmp = mac_addr[i]; + mac_addr[i] = mac_addr[GAP_MAC_ADDR_SIZE - 1 - i]; + mac_addr[GAP_MAC_ADDR_SIZE - 1 - i] = tmp; + } +} diff --git a/applications/system/find_my_flipper/findmy.h b/applications/system/find_my_flipper/findmy.h new file mode 100644 index 000000000..344882ef1 --- /dev/null +++ b/applications/system/find_my_flipper/findmy.h @@ -0,0 +1,3 @@ +#pragma once + +typedef struct FindMy FindMy; diff --git a/applications/system/find_my_flipper/findmy_i.h b/applications/system/find_my_flipper/findmy_i.h new file mode 100644 index 000000000..fee227924 --- /dev/null +++ b/applications/system/find_my_flipper/findmy_i.h @@ -0,0 +1,54 @@ +#pragma once + +#include "findmy.h" +#include "findmy_state.h" +#include +#include +#include "findmy_icons.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "views/findmy_main.h" +#include +#include +#include +#include "scenes/findmy_scene.h" +#include "helpers/base64.h" + +void findmy_reverse_mac_addr(uint8_t mac_addr[GAP_MAC_ADDR_SIZE]); + +struct FindMy { + Gui* gui; + Storage* storage; + DialogsApp* dialogs; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + + FindMyMain* findmy_main; + ByteInput* byte_input; + VariableItemList* var_item_list; + Popup* popup; + + uint8_t mac_buf[EXTRA_BEACON_MAC_ADDR_SIZE]; + uint8_t packet_buf[EXTRA_BEACON_MAX_DATA_SIZE]; + + FindMyState state; +}; + +typedef enum { + FindMyViewMain, + FindMyViewByteInput, + FindMyViewVarItemList, + FindMyViewPopup, +} FindMyView; + +void findmy_change_broadcast_interval(FindMy* app, uint8_t value); +void findmy_change_transmit_power(FindMy* app, uint8_t value); +void findmy_toggle_show_mac(FindMy* app, bool show_mac); +void findmy_set_tag_type(FindMy* app, FindMyType type); +void findmy_toggle_beacon(FindMy* app); diff --git a/applications/system/find_my_flipper/findmy_startup.c b/applications/system/find_my_flipper/findmy_startup.c new file mode 100644 index 000000000..b7af97e23 --- /dev/null +++ b/applications/system/find_my_flipper/findmy_startup.c @@ -0,0 +1,51 @@ +#include "findmy_state.h" +#include +#include +#include +#include + +#define TAG "FindMyStartup" + +static int32_t findmy_startup_apply(void* context) { + UNUSED(context); + FURI_LOG_D(TAG, "Loading state"); + + // Wait for BT init and check core2 + furi_record_open(RECORD_BT); + furi_record_close(RECORD_BT); + if(!furi_hal_bt_is_gatt_gap_supported()) return 0; + + FindMyState state; + if(findmy_state_load(&state)) { + FURI_LOG_D(TAG, "Activating beacon"); + findmy_state_apply(&state); + } else { + FURI_LOG_D(TAG, "Beacon not active, bailing"); + } + + return 0; +} + +static void findmy_startup_mount_callback(const void* message, void* context) { + UNUSED(context); + const StorageEvent* event = message; + + if(event->type == StorageEventTypeCardMount) { + run_parallel(findmy_startup_apply, NULL, 2048); + } +} + +void findmy_startup() { + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) return; + + Storage* storage = furi_record_open(RECORD_STORAGE); + furi_pubsub_subscribe(storage_get_pubsub(storage), findmy_startup_mount_callback, NULL); + + if(storage_sd_status(storage) != FSE_OK) { + FURI_LOG_D(TAG, "SD Card not ready, skipping startup hook"); + } else { + findmy_startup_apply(NULL); + } + + furi_record_close(RECORD_STORAGE); +} diff --git a/applications/system/find_my_flipper/findmy_state.c b/applications/system/find_my_flipper/findmy_state.c new file mode 100644 index 000000000..047eb0b83 --- /dev/null +++ b/applications/system/find_my_flipper/findmy_state.c @@ -0,0 +1,192 @@ +#include "findmy_state.h" + +#include +#include +#include +#include +#include + +bool findmy_state_load(FindMyState* out_state) { + FindMyState state; + + // Try to load from file + bool loaded_from_file = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_file_exists(storage, FINDMY_STATE_PATH)) { + FlipperFormat* file = flipper_format_file_alloc(storage); + FuriString* str = furi_string_alloc(); + uint32_t tmp; + do { + if(!flipper_format_file_open_existing(file, FINDMY_STATE_PATH)) break; + if(!flipper_format_read_header(file, str, &tmp)) break; + if(furi_string_cmp_str(str, FINDMY_STATE_HEADER)) break; + if(tmp != FINDMY_STATE_VER) break; + + if(!flipper_format_read_bool(file, "beacon_active", &state.beacon_active, 1)) break; + + if(!flipper_format_read_uint32(file, "broadcast_interval", &tmp, 1)) break; + state.broadcast_interval = tmp; + + if(!flipper_format_read_uint32(file, "transmit_power", &tmp, 1)) break; + state.transmit_power = tmp; + + if(!flipper_format_read_uint32(file, "tag_type", &tmp, 1)) { + tmp = FindMyTypeApple; + flipper_format_rewind(file); + } + state.tag_type = tmp; + + if(!flipper_format_read_bool(file, "show_mac", &state.show_mac, 1)) { + // Support migrating from old config + state.show_mac = false; + flipper_format_rewind(file); + } + + if(!flipper_format_read_hex(file, "mac", state.mac, sizeof(state.mac))) break; + + if(!flipper_format_read_hex( + file, "data", state.data, findmy_state_data_size(state.tag_type))) + break; + + loaded_from_file = true; + } while(0); + furi_string_free(str); + flipper_format_free(file); + } + furi_record_close(RECORD_STORAGE); + + // Otherwise set default values + if(!loaded_from_file) { + state.beacon_active = false; + state.broadcast_interval = 5; + state.transmit_power = 6; + state.show_mac = false; + state.tag_type = FindMyTypeApple; + + // Set default mac + uint8_t default_mac[EXTRA_BEACON_MAC_ADDR_SIZE] = {0x66, 0x55, 0x44, 0x33, 0x22, 0x11}; + memcpy(state.mac, default_mac, sizeof(state.mac)); + + // Set default empty AirTag data + uint8_t* data = state.data; + *data++ = 0x1E; // Length + *data++ = 0xFF; // Manufacturer Specific Data + *data++ = 0x4C; // Company ID (Apple, Inc.) + *data++ = 0x00; // ... + *data++ = 0x12; // Type (FindMy) + *data++ = 0x19; // Length + *data++ = 0x00; // Battery Status set to Full + // Placeholder Empty Public Key without the MAC address + for(size_t i = 0; i < 22; ++i) { + *data++ = 0x00; + } + *data++ = 0x00; // First 2 bits are the version + *data++ = 0x00; // Hint (0x00) + } + + // Copy to caller state before popping stack + memcpy(out_state, &state, sizeof(state)); + + // Return if active, can be used to start after loading in an if statement + return state.beacon_active; +} + +static void findmy_state_update_payload_battery(FindMyState* state) { + // Update the battery level in the payload + if(state->tag_type == FindMyTypeApple) { + uint32_t battery_capacity = furi_hal_power_get_battery_full_capacity(); + uint32_t battery_remaining = furi_hal_power_get_battery_remaining_capacity(); + uint8_t battery_percent = (battery_remaining * 100) / battery_capacity; + uint8_t battery_level; + + if(battery_percent > 80) { + battery_level = BATTERY_FULL; + } else if(battery_percent > 50) { + battery_level = BATTERY_MEDIUM; + } else if(battery_percent > 20) { + battery_level = BATTERY_LOW; + } else { + battery_level = BATTERY_CRITICAL; + } + state->data[6] = battery_level; + } +} + +void findmy_state_apply(FindMyState* state) { + // This function applies configured state to the beacon (loaded values) + + // Stop beacon before configuring + if(furi_hal_bt_extra_beacon_is_active()) { + furi_check(furi_hal_bt_extra_beacon_stop()); + } + + // Make config struct from configured parameters and set it + GapExtraBeaconConfig config = { + .min_adv_interval_ms = state->broadcast_interval * 1000, // Converting s to ms + .max_adv_interval_ms = (state->broadcast_interval * 1000) + 150, + .adv_channel_map = GapAdvChannelMapAll, + .adv_power_level = GapAdvPowerLevel_0dBm + state->transmit_power, + .address_type = GapAddressTypePublic, + }; + memcpy(config.address, state->mac, sizeof(config.address)); + furi_check(furi_hal_bt_extra_beacon_set_config(&config)); + + // Update data payload with battery level and set it + findmy_state_update_payload_battery(state); + furi_check( + furi_hal_bt_extra_beacon_set_data(state->data, findmy_state_data_size(state->tag_type))); + + // Start beacon if configured + if(state->beacon_active) { + furi_check(furi_hal_bt_extra_beacon_start()); + } +} + +static void findmy_state_save(FindMyState* state) { + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_mkdir(storage, FINDMY_STATE_DIR); + FlipperFormat* file = flipper_format_file_alloc(storage); + + do { + uint32_t tmp; + if(!flipper_format_file_open_always(file, FINDMY_STATE_PATH)) break; + if(!flipper_format_write_header_cstr(file, FINDMY_STATE_HEADER, FINDMY_STATE_VER)) break; + + if(!flipper_format_write_bool(file, "beacon_active", &state->beacon_active, 1)) break; + + tmp = state->broadcast_interval; + if(!flipper_format_write_uint32(file, "broadcast_interval", &tmp, 1)) break; + + tmp = state->transmit_power; + if(!flipper_format_write_uint32(file, "transmit_power", &tmp, 1)) break; + + tmp = state->tag_type; + if(!flipper_format_write_uint32(file, "tag_type", &tmp, 1)) break; + + if(!flipper_format_write_bool(file, "show_mac", &state->show_mac, 1)) break; + + if(!flipper_format_write_hex(file, "mac", state->mac, sizeof(state->mac))) break; + if(!flipper_format_write_hex( + file, "data", state->data, findmy_state_data_size(state->tag_type))) + break; + } while(0); + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); +} + +void findmy_state_save_and_apply(FindMyState* state) { + findmy_state_apply(state); + findmy_state_save(state); +} + +uint8_t findmy_state_data_size(FindMyType type) { + switch(type) { + case FindMyTypeApple: + case FindMyTypeSamsung: + return 31; + case FindMyTypeTile: + return 21; + default: + return 0; + } +} diff --git a/applications/system/find_my_flipper/findmy_state.h b/applications/system/find_my_flipper/findmy_state.h new file mode 100644 index 000000000..f0be35430 --- /dev/null +++ b/applications/system/find_my_flipper/findmy_state.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#define FINDMY_STATE_HEADER "FindMy Flipper State" +#define FINDMY_STATE_VER 1 +#define FINDMY_STATE_DIR EXT_PATH("apps_data/findmy") +#define FINDMY_STATE_PATH FINDMY_STATE_DIR "/findmy_state.txt" + +#define BATTERY_FULL 0x00 +#define BATTERY_MEDIUM 0x50 +#define BATTERY_LOW 0xA0 +#define BATTERY_CRITICAL 0xF0 + +typedef enum { + FindMyTypeApple, + FindMyTypeSamsung, + FindMyTypeTile, +} FindMyType; + +typedef struct { + bool beacon_active; + uint8_t broadcast_interval; + uint8_t transmit_power; + bool show_mac; + uint8_t mac[EXTRA_BEACON_MAC_ADDR_SIZE]; + uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]; + FindMyType tag_type; +} FindMyState; + +bool findmy_state_load(FindMyState* out_state); + +void findmy_state_apply(FindMyState* state); + +void findmy_state_save_and_apply(FindMyState* state); + +uint8_t findmy_state_data_size(FindMyType type); diff --git a/applications/system/find_my_flipper/helpers/base64.c b/applications/system/find_my_flipper/helpers/base64.c new file mode 100644 index 000000000..9c6a32bfa --- /dev/null +++ b/applications/system/find_my_flipper/helpers/base64.c @@ -0,0 +1,142 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005-2011, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ +// https://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c + +#include "base64.h" + +#define os_malloc malloc +#define os_free free +#define os_memset memset + +static const unsigned char base64_table[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * base64_encode - Base64 encode + * @src: Data to be encoded + * @len: Length of the data to be encoded + * @out_len: Pointer to output length variable, or %NULL if not used + * Returns: Allocated buffer of out_len bytes of encoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. Returned buffer is + * nul terminated to make it easier to use as a C string. The nul terminator is + * not included in out_len. + */ +unsigned char* base64_encode(const unsigned char* src, size_t len, size_t* out_len) { + unsigned char *out, *pos; + const unsigned char *end, *in; + size_t olen; + int line_len; + + olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */ + olen += olen / 72; /* line feeds */ + olen++; /* nul termination */ + if(olen < len) return NULL; /* integer overflow */ + out = os_malloc(olen); + if(out == NULL) return NULL; + + end = src + len; + in = src; + pos = out; + line_len = 0; + while(end - in >= 3) { + *pos++ = base64_table[in[0] >> 2]; + *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = base64_table[in[2] & 0x3f]; + in += 3; + line_len += 4; + if(line_len >= 72) { + *pos++ = '\n'; + line_len = 0; + } + } + + if(end - in) { + *pos++ = base64_table[in[0] >> 2]; + if(end - in == 1) { + *pos++ = base64_table[(in[0] & 0x03) << 4]; + *pos++ = '='; + } else { + *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64_table[(in[1] & 0x0f) << 2]; + } + *pos++ = '='; + line_len += 4; + } + + if(line_len) *pos++ = '\n'; + + *pos = '\0'; + if(out_len) *out_len = pos - out; + return out; +} + +/** + * base64_decode - Base64 decode + * @src: Data to be decoded + * @len: Length of the data to be decoded + * @out_len: Pointer to output length variable + * Returns: Allocated buffer of out_len bytes of decoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. + */ +unsigned char* base64_decode(const unsigned char* src, size_t len, size_t* out_len) { + unsigned char dtable[256], *out, *pos, block[4], tmp; + size_t i, count, olen; + int pad = 0; + + os_memset(dtable, 0x80, 256); + for(i = 0; i < sizeof(base64_table) - 1; i++) + dtable[base64_table[i]] = (unsigned char)i; + dtable['='] = 0; + + count = 0; + for(i = 0; i < len; i++) { + if(dtable[src[i]] != 0x80) count++; + } + + if(count == 0 || count % 4) return NULL; + + olen = count / 4 * 3; + pos = out = os_malloc(olen); + if(out == NULL) return NULL; + + count = 0; + for(i = 0; i < len; i++) { + tmp = dtable[src[i]]; + if(tmp == 0x80) continue; + + if(src[i] == '=') pad++; + block[count] = tmp; + count++; + if(count == 4) { + *pos++ = (block[0] << 2) | (block[1] >> 4); + *pos++ = (block[1] << 4) | (block[2] >> 2); + *pos++ = (block[2] << 6) | block[3]; + count = 0; + if(pad) { + if(pad == 1) + pos--; + else if(pad == 2) + pos -= 2; + else { + /* Invalid padding */ + os_free(out); + return NULL; + } + break; + } + } + } + + *out_len = pos - out; + return out; +} diff --git a/applications/system/find_my_flipper/helpers/base64.h b/applications/system/find_my_flipper/helpers/base64.h new file mode 100644 index 000000000..d20660c5b --- /dev/null +++ b/applications/system/find_my_flipper/helpers/base64.h @@ -0,0 +1,21 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ +// https://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.h + +#ifndef BASE64_H +#define BASE64_H + +#include +#include +#include +#include + +unsigned char* base64_encode(const unsigned char* src, size_t len, size_t* out_len); +unsigned char* base64_decode(const unsigned char* src, size_t len, size_t* out_len); + +#endif /* BASE64_H */ diff --git a/applications/system/find_my_flipper/icons/DolphinDone_80x58.png b/applications/system/find_my_flipper/icons/DolphinDone_80x58.png new file mode 100644 index 000000000..594d62d52 Binary files /dev/null and b/applications/system/find_my_flipper/icons/DolphinDone_80x58.png differ diff --git a/applications/system/find_my_flipper/icons/Lock_7x8.png b/applications/system/find_my_flipper/icons/Lock_7x8.png new file mode 100644 index 000000000..f7c9ca2c7 Binary files /dev/null and b/applications/system/find_my_flipper/icons/Lock_7x8.png differ diff --git a/applications/system/find_my_flipper/icons/Ok_btn_9x9.png b/applications/system/find_my_flipper/icons/Ok_btn_9x9.png new file mode 100644 index 000000000..9a1539da2 Binary files /dev/null and b/applications/system/find_my_flipper/icons/Ok_btn_9x9.png differ diff --git a/applications/system/find_my_flipper/icons/WarningDolphinFlip_45x42.png b/applications/system/find_my_flipper/icons/WarningDolphinFlip_45x42.png new file mode 100644 index 000000000..2ba54afce Binary files /dev/null and b/applications/system/find_my_flipper/icons/WarningDolphinFlip_45x42.png differ diff --git a/applications/system/find_my_flipper/icons/text_10px.png b/applications/system/find_my_flipper/icons/text_10px.png new file mode 100644 index 000000000..cd38770f4 Binary files /dev/null and b/applications/system/find_my_flipper/icons/text_10px.png differ diff --git a/applications/system/find_my_flipper/location_icon.png b/applications/system/find_my_flipper/location_icon.png new file mode 100644 index 000000000..49e687c23 Binary files /dev/null and b/applications/system/find_my_flipper/location_icon.png differ diff --git a/applications/system/find_my_flipper/scenes/findmy_scene.c b/applications/system/find_my_flipper/scenes/findmy_scene.c new file mode 100644 index 000000000..f854566e5 --- /dev/null +++ b/applications/system/find_my_flipper/scenes/findmy_scene.c @@ -0,0 +1,31 @@ +#include "findmy_scene.h" +#include "findmy.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const findmy_on_enter_handlers[])(void*) = { +#include "findmy_scenes.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const findmy_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "findmy_scenes.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const findmy_on_exit_handlers[])(void* context) = { +#include "findmy_scenes.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers findmy_scene_handlers = { + .on_enter_handlers = findmy_on_enter_handlers, + .on_event_handlers = findmy_on_event_handlers, + .on_exit_handlers = findmy_on_exit_handlers, + .scene_num = FindMySceneNum, +}; diff --git a/applications/system/find_my_flipper/scenes/findmy_scene.h b/applications/system/find_my_flipper/scenes/findmy_scene.h new file mode 100644 index 000000000..14ab9d6bf --- /dev/null +++ b/applications/system/find_my_flipper/scenes/findmy_scene.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include "findmy.h" + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) FindMyScene##id, +typedef enum { +#include "findmy_scenes.h" + FindMySceneNum, +} FindMyScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers findmy_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "findmy_scenes.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "findmy_scenes.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "findmy_scenes.h" +#undef ADD_SCENE diff --git a/applications/system/find_my_flipper/scenes/findmy_scene_config.c b/applications/system/find_my_flipper/scenes/findmy_scene_config.c new file mode 100644 index 000000000..caad8f13e --- /dev/null +++ b/applications/system/find_my_flipper/scenes/findmy_scene_config.c @@ -0,0 +1,117 @@ +#include "../findmy_i.h" + +enum VarItemListIndex { + VarItemListIndexBroadcastInterval, + VarItemListIndexTransmitPower, + VarItemListIndexRegisterTag, + VarItemListIndexShowMac, + VarItemListIndexAbout, +}; + +void findmy_scene_config_broadcast_interval_changed(VariableItem* item) { + FindMy* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + findmy_change_broadcast_interval(app, index + 1); + char str[5]; + snprintf(str, sizeof(str), "%ds", app->state.broadcast_interval); + variable_item_set_current_value_text(item, str); + variable_item_set_current_value_index(item, app->state.broadcast_interval - 1); +} + +void findmy_scene_config_transmit_power_changed(VariableItem* item) { + FindMy* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + findmy_change_transmit_power(app, index); + char str[7]; + snprintf(str, sizeof(str), "%ddBm", app->state.transmit_power); + variable_item_set_current_value_text(item, str); + variable_item_set_current_value_index(item, app->state.transmit_power); +} + +void findmy_scene_config_show_mac(VariableItem* item) { + FindMy* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + findmy_toggle_show_mac(app, index); + variable_item_set_current_value_text(item, app->state.show_mac ? "Yes" : "No"); + variable_item_set_current_value_index(item, app->state.show_mac); +} + +void findmy_scene_config_callback(void* context, uint32_t index) { + furi_assert(context); + FindMy* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void findmy_scene_config_on_enter(void* context) { + FindMy* app = context; + VariableItemList* var_item_list = app->var_item_list; + VariableItem* item; + + item = variable_item_list_add( + var_item_list, + "Broadcast Interval", + 10, + findmy_scene_config_broadcast_interval_changed, + app); + // Broadcast Interval is 1-10, so use 0-9 and offset indexes by 1 + variable_item_set_current_value_index(item, app->state.broadcast_interval - 1); + char interval_str[5]; + snprintf(interval_str, sizeof(interval_str), "%ds", app->state.broadcast_interval); + variable_item_set_current_value_text(item, interval_str); + + item = variable_item_list_add( + var_item_list, "Transmit Power", 7, findmy_scene_config_transmit_power_changed, app); + variable_item_set_current_value_index(item, app->state.transmit_power); + char power_str[7]; + snprintf(power_str, sizeof(power_str), "%ddBm", app->state.transmit_power); + variable_item_set_current_value_text(item, power_str); + + item = variable_item_list_add(var_item_list, "Register Tag", 0, NULL, NULL); + + item = variable_item_list_add(var_item_list, "Show MAC", 2, findmy_scene_config_show_mac, app); + variable_item_set_current_value_index(item, app->state.show_mac); + variable_item_set_current_value_text(item, app->state.show_mac ? "Yes" : "No"); + + item = variable_item_list_add( + var_item_list, + "Matthew KuKanich, Thanks to Chapoly1305, WillyJL, OpenHaystack, Testers", + 1, + NULL, + NULL); + variable_item_set_current_value_text(item, "Credits"); + + variable_item_list_set_enter_callback(var_item_list, findmy_scene_config_callback, app); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, FindMySceneConfig)); + + view_dispatcher_switch_to_view(app->view_dispatcher, FindMyViewVarItemList); +} + +bool findmy_scene_config_on_event(void* context, SceneManagerEvent event) { + FindMy* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, FindMySceneConfig, event.event); + consumed = true; + switch(event.event) { + case VarItemListIndexRegisterTag: + scene_manager_next_scene(app->scene_manager, FindMySceneConfigTagtype); + break; + case VarItemListIndexAbout: + break; + default: + break; + } + } + + return consumed; +} + +void findmy_scene_config_on_exit(void* context) { + FindMy* app = context; + VariableItemList* var_item_list = app->var_item_list; + + variable_item_list_reset(var_item_list); +} diff --git a/applications/system/find_my_flipper/scenes/findmy_scene_config_import.c b/applications/system/find_my_flipper/scenes/findmy_scene_config_import.c new file mode 100644 index 000000000..231741320 --- /dev/null +++ b/applications/system/find_my_flipper/scenes/findmy_scene_config_import.c @@ -0,0 +1,231 @@ +#include "../findmy_i.h" + +enum VarItemListIndex { + VarItemListIndexNrfConnect, + VarItemListIndexOpenHaystack, + VarItemListIndexRegisterTagManually, +}; + +static const char* parse_nrf_connect(FindMy* app, const char* path) { + const char* error = NULL; + + Stream* stream = file_stream_alloc(app->storage); + FuriString* line = furi_string_alloc(); + do { + // XX-XX-XX-XX-XX-XX_YYYY-MM-DD HH_MM_SS.txt + error = "Filename must\nhave MAC\naddress"; + uint8_t mac[EXTRA_BEACON_MAC_ADDR_SIZE]; + path_extract_filename_no_ext(path, line); + if(furi_string_size(line) < sizeof(mac) * 3 - 1) break; + error = NULL; + for(size_t i = 0; i < sizeof(mac); i++) { + char a = furi_string_get_char(line, i * 3); + char b = furi_string_get_char(line, i * 3 + 1); + if((a < 'A' && a > 'F') || (a < '0' && a > '9') || (b < 'A' && b > 'F') || + (b < '0' && b > '9') || !hex_char_to_uint8(a, b, &mac[i])) { + error = "Filename must\nhave MAC\naddress"; + break; + } + } + if(error) break; + findmy_reverse_mac_addr(mac); + + error = "Can't open file"; + if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) break; + + // YYYY-MM-DD HH:MM:SS.ms, XX dBm, 0xXXXXX + error = "Wrong file format"; + if(!stream_read_line(stream, line)) break; + const char* marker = " dBm, 0x"; + size_t pos = furi_string_search(line, marker); + if(pos == FURI_STRING_FAILURE) break; + furi_string_right(line, pos + strlen(marker)); + furi_string_trim(line); + + error = "Wrong payload size"; + size_t line_size = furi_string_size(line); + uint8_t data_size = findmy_state_data_size(app->state.tag_type); + FURI_LOG_I("ImportPayload", "Line Size: %d", line_size); + FURI_LOG_I("ImportPayload", "Data Size: %d", data_size * 2); + if(line_size != data_size * 2) break; + // Initialize full data to 0's, then fill only first data_size bytes + uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE] = {0}; + error = NULL; + for(size_t i = 0; i < data_size; i++) { + char a = furi_string_get_char(line, i * 2); + char b = furi_string_get_char(line, i * 2 + 1); + if((a < 'A' && a > 'F') || (a < '0' && a > '9') || (b < 'A' && b > 'F') || + (b < '0' && b > '9') || !hex_char_to_uint8(a, b, &data[i])) { + error = "Invalid payload"; + break; + } + } + if(error) break; + + memcpy(app->state.mac, mac, sizeof(app->state.mac)); + memcpy(app->state.data, data, sizeof(app->state.data)); + findmy_state_save_and_apply(&app->state); + + error = NULL; + + } while(false); + furi_string_free(line); + file_stream_close(stream); + stream_free(stream); + + return error; +} + +static const char* parse_open_haystack(FindMy* app, const char* path) { + const char* error = NULL; + + Stream* stream = file_stream_alloc(app->storage); + FuriString* line = furi_string_alloc(); + do { + error = "Can't open file"; + if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) break; + + error = "Wrong file format"; + while(stream_read_line(stream, line)) { + if(furi_string_start_with(line, "Public key: ") || + furi_string_start_with(line, "Advertisement key: ")) { + error = NULL; + break; + } + } + if(error) break; + + furi_string_right(line, furi_string_search_char(line, ':') + 2); + furi_string_trim(line); + + error = "Base64 failed"; + size_t decoded_len; + uint8_t* public_key = base64_decode( + (uint8_t*)furi_string_get_cstr(line), furi_string_size(line), &decoded_len); + if(decoded_len != 28) { + free(public_key); + break; + } + + memcpy(app->state.mac, public_key, sizeof(app->state.mac)); + app->state.mac[0] |= 0b11000000; + findmy_reverse_mac_addr(app->state.mac); + + uint8_t advertisement_template[EXTRA_BEACON_MAX_DATA_SIZE] = { + 0x1e, // length (30) + 0xff, // manufacturer specific data + 0x4c, 0x00, // company ID (Apple) + 0x12, 0x19, // offline finding type and length + 0x00, //state + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, // first two bits of key[0] + 0x00, // hint + }; + memcpy(app->state.data, advertisement_template, sizeof(app->state.data)); + memcpy(&app->state.data[7], &public_key[6], decoded_len - 6); + app->state.data[29] = public_key[0] >> 6; + findmy_state_save_and_apply(&app->state); + + free(public_key); + error = NULL; + + } while(false); + furi_string_free(line); + file_stream_close(stream); + stream_free(stream); + + return error; +} + +void findmy_scene_config_import_callback(void* context, uint32_t index) { + furi_assert(context); + FindMy* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void findmy_scene_config_import_on_enter(void* context) { + FindMy* app = context; + VariableItemList* var_item_list = app->var_item_list; + VariableItem* item; + + //variable_item_list_set_header(var_item_list, "Choose file type"); + + item = variable_item_list_add(var_item_list, "nRF Connect (.txt)", 0, NULL, NULL); + + item = variable_item_list_add(var_item_list, "OpenHaystack (.keys)", 0, NULL, NULL); + + item = variable_item_list_add(var_item_list, "Register Tag Manually", 0, NULL, NULL); + + // This scene acts more like a submenu than a var item list tbh + UNUSED(item); + + variable_item_list_set_enter_callback(var_item_list, findmy_scene_config_import_callback, app); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, FindMySceneConfigImport)); + + view_dispatcher_switch_to_view(app->view_dispatcher, FindMyViewVarItemList); +} + +bool findmy_scene_config_import_on_event(void* context, SceneManagerEvent event) { + FindMy* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, FindMySceneConfigImport, event.event); + consumed = true; + + const char* extension = NULL; + switch(event.event) { + case VarItemListIndexNrfConnect: + extension = ".txt"; + break; + case VarItemListIndexOpenHaystack: + extension = ".keys"; + break; + case VarItemListIndexRegisterTagManually: + scene_manager_next_scene(app->scene_manager, FindMySceneConfigMac); + break; + default: + break; + } + if(!extension) { + return consumed; + } + + const DialogsFileBrowserOptions browser_options = { + .extension = extension, + .icon = &I_text_10px, + .base_path = FINDMY_STATE_DIR, + }; + storage_simply_mkdir(app->storage, browser_options.base_path); + FuriString* path = furi_string_alloc_set_str(browser_options.base_path); + if(dialog_file_browser_show(app->dialogs, path, path, &browser_options)) { + // The parse functions return the error text, or NULL for success + // Used in result to show success or error message + const char* error = NULL; + switch(event.event) { + case VarItemListIndexNrfConnect: + error = parse_nrf_connect(app, furi_string_get_cstr(path)); + break; + case VarItemListIndexOpenHaystack: + error = parse_open_haystack(app, furi_string_get_cstr(path)); + break; + } + scene_manager_set_scene_state( + app->scene_manager, FindMySceneConfigImportResult, (uint32_t)error); + scene_manager_next_scene(app->scene_manager, FindMySceneConfigImportResult); + } + furi_string_free(path); + } + + return consumed; +} + +void findmy_scene_config_import_on_exit(void* context) { + FindMy* app = context; + VariableItemList* var_item_list = app->var_item_list; + + variable_item_list_reset(var_item_list); +} diff --git a/applications/system/find_my_flipper/scenes/findmy_scene_config_import_result.c b/applications/system/find_my_flipper/scenes/findmy_scene_config_import_result.c new file mode 100644 index 000000000..933b6e611 --- /dev/null +++ b/applications/system/find_my_flipper/scenes/findmy_scene_config_import_result.c @@ -0,0 +1,60 @@ +#include "../findmy_i.h" + +enum PopupEvent { + PopupEventExit, +}; + +static void findmy_scene_config_import_result_callback(void* context) { + FindMy* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, PopupEventExit); +} + +void findmy_scene_config_import_result_on_enter(void* context) { + FindMy* app = context; + Popup* popup = app->popup; + + const char* error = (const char*)scene_manager_get_scene_state( + app->scene_manager, FindMySceneConfigImportResult); + if(error) { + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); + popup_set_header(popup, "Error!", 13, 22, AlignLeft, AlignBottom); + popup_set_text(popup, error, 6, 26, AlignLeft, AlignTop); + popup_disable_timeout(popup); + } else { + popup_set_icon(popup, 36, 5, &I_DolphinDone_80x58); + popup_set_header(popup, "Imported!", 7, 14, AlignLeft, AlignBottom); + popup_enable_timeout(popup); + } + popup_set_timeout(popup, 1500); + popup_set_context(popup, app); + popup_set_callback(popup, findmy_scene_config_import_result_callback); + findmy_main_update_active(app->findmy_main, app->state.beacon_active); + findmy_main_update_mac(app->findmy_main, app->state.mac); + findmy_main_update_type(app->findmy_main, app->state.tag_type); + view_dispatcher_switch_to_view(app->view_dispatcher, FindMyViewPopup); +} + +bool findmy_scene_config_import_result_on_event(void* context, SceneManagerEvent event) { + FindMy* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case PopupEventExit: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, FindMySceneConfig); + break; + default: + break; + } + } + + return consumed; +} + +void findmy_scene_config_import_result_on_exit(void* context) { + FindMy* app = context; + popup_reset(app->popup); +} diff --git a/applications/system/find_my_flipper/scenes/findmy_scene_config_mac.c b/applications/system/find_my_flipper/scenes/findmy_scene_config_mac.c new file mode 100644 index 000000000..7e5a1d0e3 --- /dev/null +++ b/applications/system/find_my_flipper/scenes/findmy_scene_config_mac.c @@ -0,0 +1,60 @@ +#include "../findmy_i.h" + +enum ByteInputResult { + ByteInputResultOk, +}; + +static void findmy_scene_config_mac_callback(void* context) { + FindMy* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, ByteInputResultOk); +} + +void findmy_scene_config_mac_on_enter(void* context) { + FindMy* app = context; + ByteInput* byte_input = app->byte_input; + + byte_input_set_header_text(byte_input, "Enter Bluetooth MAC:"); + + memcpy(app->mac_buf, app->state.mac, sizeof(app->mac_buf)); + findmy_reverse_mac_addr(app->mac_buf); + + byte_input_set_result_callback( + byte_input, + findmy_scene_config_mac_callback, + NULL, + app, + app->mac_buf, + sizeof(app->mac_buf)); + + view_dispatcher_switch_to_view(app->view_dispatcher, FindMyViewByteInput); +} + +bool findmy_scene_config_mac_on_event(void* context, SceneManagerEvent event) { + FindMy* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case ByteInputResultOk: + findmy_reverse_mac_addr(app->mac_buf); + memcpy(&app->state.mac, app->mac_buf, sizeof(app->state.mac)); + findmy_state_save_and_apply(&app->state); + findmy_main_update_mac(app->findmy_main, app->state.mac); + scene_manager_next_scene(app->scene_manager, FindMySceneConfigPacket); + break; + default: + break; + } + } + + return consumed; +} + +void findmy_scene_config_mac_on_exit(void* context) { + FindMy* app = context; + + byte_input_set_result_callback(app->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(app->byte_input, ""); +} diff --git a/applications/system/find_my_flipper/scenes/findmy_scene_config_packet.c b/applications/system/find_my_flipper/scenes/findmy_scene_config_packet.c new file mode 100644 index 000000000..06e30856e --- /dev/null +++ b/applications/system/find_my_flipper/scenes/findmy_scene_config_packet.c @@ -0,0 +1,58 @@ +#include "../findmy_i.h" + +enum ByteInputResult { + ByteInputResultOk, +}; + +static void findmy_scene_config_packet_callback(void* context) { + FindMy* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, ByteInputResultOk); +} + +void findmy_scene_config_packet_on_enter(void* context) { + FindMy* app = context; + ByteInput* byte_input = app->byte_input; + + byte_input_set_header_text(byte_input, "Enter Bluetooth Payload:"); + + memcpy(app->packet_buf, app->state.data, findmy_state_data_size(app->state.tag_type)); + + byte_input_set_result_callback( + byte_input, + findmy_scene_config_packet_callback, + NULL, + app, + app->packet_buf, + findmy_state_data_size(app->state.tag_type)); + + view_dispatcher_switch_to_view(app->view_dispatcher, FindMyViewByteInput); +} + +bool findmy_scene_config_packet_on_event(void* context, SceneManagerEvent event) { + FindMy* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case ByteInputResultOk: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, FindMySceneConfig); + memcpy(app->state.data, app->packet_buf, findmy_state_data_size(app->state.tag_type)); + findmy_state_save_and_apply(&app->state); + break; + default: + break; + } + } + + return consumed; +} + +void findmy_scene_config_packet_on_exit(void* context) { + FindMy* app = context; + + byte_input_set_result_callback(app->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(app->byte_input, ""); +} diff --git a/applications/system/find_my_flipper/scenes/findmy_scene_config_tagtype.c b/applications/system/find_my_flipper/scenes/findmy_scene_config_tagtype.c new file mode 100644 index 000000000..51bc7f61c --- /dev/null +++ b/applications/system/find_my_flipper/scenes/findmy_scene_config_tagtype.c @@ -0,0 +1,70 @@ +#include "../findmy_i.h" + +enum VarItemListIndex { + VarItemListIndexApple, + VarItemListIndexSamsung, + VarItemListIndexTile, +}; + +void findmy_scene_config_tagtype_callback(void* context, uint32_t index) { + furi_assert(context); + FindMy* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void findmy_scene_config_tagtype_on_enter(void* context) { + FindMy* app = context; + VariableItemList* var_item_list = app->var_item_list; + VariableItem* item; + + //variable_item_list_set_header(var_item_list, "Choose tag type"); + + item = variable_item_list_add(var_item_list, "Apple AirTag", 0, NULL, NULL); + + item = variable_item_list_add(var_item_list, "Samsung SmartTag", 0, NULL, NULL); + + item = variable_item_list_add(var_item_list, "Tile SmartTag", 0, NULL, NULL); + + UNUSED(item); + + variable_item_list_set_enter_callback( + var_item_list, findmy_scene_config_tagtype_callback, app); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, FindMySceneConfigImport)); + view_dispatcher_switch_to_view(app->view_dispatcher, FindMyViewVarItemList); +} + +bool findmy_scene_config_tagtype_on_event(void* context, SceneManagerEvent event) { + FindMy* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, FindMySceneConfigTagtype, event.event); + consumed = true; + + switch(event.event) { + case VarItemListIndexApple: + findmy_set_tag_type(app, FindMyTypeApple); + break; + case VarItemListIndexSamsung: + findmy_set_tag_type(app, FindMyTypeSamsung); + break; + case VarItemListIndexTile: + findmy_set_tag_type(app, FindMyTypeTile); + break; + default: + break; + } + scene_manager_next_scene(app->scene_manager, FindMySceneConfigImport); + } + + return consumed; +} + +void findmy_scene_config_tagtype_on_exit(void* context) { + FindMy* app = context; + VariableItemList* var_item_list = app->var_item_list; + + variable_item_list_reset(var_item_list); +} diff --git a/applications/system/find_my_flipper/scenes/findmy_scene_main.c b/applications/system/find_my_flipper/scenes/findmy_scene_main.c new file mode 100644 index 000000000..a4e72bdf7 --- /dev/null +++ b/applications/system/find_my_flipper/scenes/findmy_scene_main.c @@ -0,0 +1,56 @@ +#include "../findmy_i.h" + +void findmy_scene_main_callback(FindMyMainEvent event, void* context) { + furi_assert(context); + FindMy* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void findmy_scene_main_on_enter(void* context) { + FindMy* app = context; + + findmy_main_set_callback(app->findmy_main, findmy_scene_main_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, FindMyViewMain); +} + +bool findmy_scene_main_on_event(void* context, SceneManagerEvent event) { + FindMy* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case FindMyMainEventToggle: + findmy_toggle_beacon(app); + break; + case FindMyMainEventBackground: + app->state.beacon_active = true; + findmy_state_save_and_apply(&app->state); + view_dispatcher_stop(app->view_dispatcher); + break; + case FindMyMainEventConfig: + scene_manager_next_scene(app->scene_manager, FindMySceneConfig); + break; + case FindMyMainEventIntervalUp: + findmy_change_broadcast_interval(app, app->state.broadcast_interval + 1); + break; + case FindMyMainEventIntervalDown: + findmy_change_broadcast_interval(app, app->state.broadcast_interval - 1); + break; + case FindMyMainEventQuit: + app->state.beacon_active = false; + findmy_state_save_and_apply(&app->state); + break; + default: + consumed = false; + break; + } + } + + return consumed; +} + +void findmy_scene_main_on_exit(void* context) { + FindMy* app = context; + UNUSED(app); +} diff --git a/applications/system/find_my_flipper/scenes/findmy_scenes.h b/applications/system/find_my_flipper/scenes/findmy_scenes.h new file mode 100644 index 000000000..0680e2c5f --- /dev/null +++ b/applications/system/find_my_flipper/scenes/findmy_scenes.h @@ -0,0 +1,7 @@ +ADD_SCENE(findmy, main, Main) +ADD_SCENE(findmy, config, Config) +ADD_SCENE(findmy, config_import, ConfigImport) +ADD_SCENE(findmy, config_tagtype, ConfigTagtype) +ADD_SCENE(findmy, config_import_result, ConfigImportResult) +ADD_SCENE(findmy, config_mac, ConfigMac) +ADD_SCENE(findmy, config_packet, ConfigPacket) diff --git a/applications/system/find_my_flipper/screenshots/1.png b/applications/system/find_my_flipper/screenshots/1.png new file mode 100644 index 000000000..e3511183f Binary files /dev/null and b/applications/system/find_my_flipper/screenshots/1.png differ diff --git a/applications/system/find_my_flipper/screenshots/2.png b/applications/system/find_my_flipper/screenshots/2.png new file mode 100644 index 000000000..b4b3b34a6 Binary files /dev/null and b/applications/system/find_my_flipper/screenshots/2.png differ diff --git a/applications/system/find_my_flipper/screenshots/3.png b/applications/system/find_my_flipper/screenshots/3.png new file mode 100644 index 000000000..b29124e8d Binary files /dev/null and b/applications/system/find_my_flipper/screenshots/3.png differ diff --git a/applications/system/find_my_flipper/views/findmy_main.c b/applications/system/find_my_flipper/views/findmy_main.c new file mode 100644 index 000000000..0f208b7c2 --- /dev/null +++ b/applications/system/find_my_flipper/views/findmy_main.c @@ -0,0 +1,193 @@ +#include "findmy_main.h" +#include "../findmy_i.h" + +struct FindMyMain { + View* view; + FindMyMainCallback callback; + void* context; +}; + +typedef struct { + bool active; + bool show_mac; + uint8_t interval; + uint8_t mac[6]; + FindMyType type; +} FindMyMainModel; + +static void findmy_main_draw_callback(Canvas* canvas, void* _model) { + FindMyMainModel* model = _model; + canvas_clear(canvas); + canvas_set_bitmap_mode(canvas, true); + canvas_set_font(canvas, FontPrimary); + + canvas_draw_str(canvas, 4, 11, "FindMy Flipper"); + + const char* network_text = ""; + switch(model->type) { + case FindMyTypeApple: + network_text = "Apple Network"; + break; + case FindMyTypeSamsung: + network_text = "Samsung Network"; + break; + case FindMyTypeTile: + network_text = "Tile Network"; + break; + default: + break; + } + + if(model->show_mac == false) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 4, 31, network_text); + canvas_draw_icon(canvas, 6 + canvas_string_width(canvas, network_text), 24, &I_Lock_7x8); + } else if(model->show_mac == true) { + canvas_set_font(canvas, FontSecondary); + char mac_str[23]; + snprintf( + mac_str, + sizeof(mac_str), + "MAC: %02X:%02X:%02X:%02X:%02X:%02X", + model->mac[0], + model->mac[1], + model->mac[2], + model->mac[3], + model->mac[4], + model->mac[5]); + canvas_draw_str(canvas, 4, 40, mac_str); + canvas_draw_str(canvas, 4, 30, network_text); + canvas_draw_icon(canvas, 6 + canvas_string_width(canvas, network_text), 23, &I_Lock_7x8); + } + canvas_set_font(canvas, FontSecondary); + if(model->active) { + canvas_draw_str(canvas, 4, 49, "Broadcast Active"); + canvas_draw_icon(canvas, 78, 41, &I_Ok_btn_9x9); + } else { + canvas_draw_str(canvas, 4, 49, "Broadcast Inactive"); + } + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 4, 21, "Press <- to run in background"); + canvas_set_font(canvas, FontSecondary); + char interval_str[20]; + snprintf(interval_str, sizeof(interval_str), "Ping Interval: %ds", model->interval); + canvas_draw_str(canvas, 4, 62, interval_str); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 100, 61, "Config"); + canvas_draw_line(canvas, 100, 51, 127, 51); + canvas_draw_line(canvas, 97, 53, 97, 63); + canvas_draw_line(canvas, 97, 53, 99, 51); + canvas_draw_line(canvas, 3, 52, 87, 52); +} + +static bool findmy_main_input_callback(InputEvent* event, void* context) { + furi_assert(context); + FindMyMain* findmy_main = context; + bool consumed = false; + + if(event->type == InputTypePress) { + consumed = true; + FindMyMainEvent cb_event; + + switch(event->key) { + case InputKeyBack: + cb_event = FindMyMainEventQuit; + break; + case InputKeyOk: + cb_event = FindMyMainEventToggle; + break; + case InputKeyLeft: + cb_event = FindMyMainEventBackground; + break; + case InputKeyRight: + cb_event = FindMyMainEventConfig; + break; + case InputKeyUp: + cb_event = FindMyMainEventIntervalUp; + break; + case InputKeyDown: + cb_event = FindMyMainEventIntervalDown; + break; + default: + return consumed; + } + + findmy_main->callback(cb_event, findmy_main->context); + } + + return consumed; +} + +FindMyMain* findmy_main_alloc(FindMy* app) { + FindMyMain* findmy_main = malloc(sizeof(FindMyMain)); + + findmy_main->view = view_alloc(); + view_allocate_model(findmy_main->view, ViewModelTypeLocking, sizeof(FindMyMainModel)); + with_view_model( + findmy_main->view, + FindMyMainModel * model, + { + model->active = app->state.beacon_active; + model->interval = app->state.broadcast_interval; + model->show_mac = app->state.show_mac; + memcpy(model->mac, app->state.mac, sizeof(model->mac)); + model->type = app->state.tag_type; + }, + false); + view_set_context(findmy_main->view, findmy_main); + view_set_draw_callback(findmy_main->view, findmy_main_draw_callback); + view_set_input_callback(findmy_main->view, findmy_main_input_callback); + + return findmy_main; +} + +void findmy_main_free(FindMyMain* findmy_main) { + furi_assert(findmy_main); + view_free(findmy_main->view); + free(findmy_main); +} + +View* findmy_main_get_view(FindMyMain* findmy_main) { + furi_assert(findmy_main); + return findmy_main->view; +} + +void findmy_main_set_callback(FindMyMain* findmy_main, FindMyMainCallback callback, void* context) { + furi_assert(findmy_main); + furi_assert(callback); + findmy_main->callback = callback; + findmy_main->context = context; +} + +void findmy_main_update_active(FindMyMain* findmy_main, bool active) { + furi_assert(findmy_main); + with_view_model(findmy_main->view, FindMyMainModel * model, { model->active = active; }, true); +} + +void findmy_main_toggle_mac(FindMyMain* findmy_main, bool show_mac) { + furi_assert(findmy_main); + with_view_model( + findmy_main->view, FindMyMainModel * model, { model->show_mac = show_mac; }, true); +} + +void findmy_main_update_mac(FindMyMain* findmy_main, uint8_t* mac) { + with_view_model( + findmy_main->view, + FindMyMainModel * model, + { + memcpy(model->mac, mac, sizeof(model->mac)); + findmy_reverse_mac_addr(model->mac); + }, + true); +} +void findmy_main_update_interval(FindMyMain* findmy_main, uint8_t interval) { + furi_assert(findmy_main); + with_view_model( + findmy_main->view, FindMyMainModel * model, { model->interval = interval; }, true); +} + +void findmy_main_update_type(FindMyMain* findmy_main, FindMyType type) { + furi_assert(findmy_main); + with_view_model(findmy_main->view, FindMyMainModel * model, { model->type = type; }, true); +} diff --git a/applications/system/find_my_flipper/views/findmy_main.h b/applications/system/find_my_flipper/views/findmy_main.h new file mode 100644 index 000000000..5680fa641 --- /dev/null +++ b/applications/system/find_my_flipper/views/findmy_main.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../findmy.h" +#include "../findmy_state.h" +#include + +typedef enum { + FindMyMainEventToggle, + FindMyMainEventBackground, + FindMyMainEventConfig, + FindMyMainEventIntervalUp, + FindMyMainEventIntervalDown, + FindMyMainEventQuit, +} FindMyMainEvent; + +typedef struct FindMyMain FindMyMain; +typedef void (*FindMyMainCallback)(FindMyMainEvent event, void* context); + +// Main functionality +FindMyMain* findmy_main_alloc(FindMy* app); +void findmy_main_free(FindMyMain* findmy_main); +View* findmy_main_get_view(FindMyMain* findmy_main); + +// To communicate with scene +void findmy_main_set_callback(FindMyMain* findmy_main, FindMyMainCallback callback, void* context); + +// To redraw when info changes +void findmy_main_update_active(FindMyMain* findmy_main, bool active); +void findmy_main_update_interval(FindMyMain* findmy_main, uint8_t interval); +void findmy_main_toggle_mac(FindMyMain* findmy_main, bool show_mac); +void findmy_main_update_mac(FindMyMain* findmy_main, uint8_t* mac); +void findmy_main_update_type(FindMyMain* findmy_main, FindMyType type); diff --git a/applications/system/hid_app/application.fam b/applications/system/hid_app/application.fam index f14d6b063..f41d33666 100644 --- a/applications/system/hid_app/application.fam +++ b/applications/system/hid_app/application.fam @@ -3,11 +3,11 @@ App( name="USB Keyboard & Mouse", apptype=FlipperAppType.EXTERNAL, entry_point="hid_usb_app", - stack_size=1 * 1024, + stack_size=2 * 1024, sources=["*.c", "!transport_ble.c"], cdefines=["HID_TRANSPORT_USB"], fap_description="Use Flipper as a HID remote control over USB", - fap_version="1.0", + fap_version="1.1", fap_category="USB", fap_icon="hid_usb_10px.png", fap_icon_assets="assets", @@ -20,12 +20,12 @@ App( name="Bluetooth Remote", apptype=FlipperAppType.EXTERNAL, entry_point="hid_ble_app", - stack_size=1 * 1024, + stack_size=2 * 1024, sources=["*.c", "!transport_usb.c"], cdefines=["HID_TRANSPORT_BLE"], fap_libs=["ble_profile"], fap_description="Use Flipper as a HID remote control over Bluetooth", - fap_version="1.0", + fap_version="1.1", fap_category="Bluetooth", fap_icon="hid_ble_10px.png", fap_icon_assets="assets", diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index e289b7179..491f9962b 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -124,7 +124,7 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { bool consumed = false; bool rate_changed = false; - if(event->type != InputTypeShort && event->type != InputTypeRepeat) { + if(event->type == InputTypePress || event->type == InputTypeRelease) { return false; } diff --git a/applications/system/hid_app/views/hid_tiktok.c b/applications/system/hid_app/views/hid_tiktok.c index 7e8e28722..636d21c6b 100644 --- a/applications/system/hid_app/views/hid_tiktok.c +++ b/applications/system/hid_app/views/hid_tiktok.c @@ -117,7 +117,10 @@ static void hid_tiktok_reset_cursor(HidTikTok* hid_tiktok) { furi_delay_ms(50); } // Move cursor from the corner - hid_hal_mouse_move(hid_tiktok->hid, 20, 120); + // Actions split for some mobiles to properly process mouse movements + hid_hal_mouse_move(hid_tiktok->hid, 10, 60); + furi_delay_ms(3); + hid_hal_mouse_move(hid_tiktok->hid, 0, 60); furi_delay_ms(50); } @@ -180,29 +183,30 @@ static bool hid_tiktok_input_callback(InputEvent* event, void* context) { consumed = true; } else if(event->type == InputTypeShort) { if(event->key == InputKeyOk) { + // delays adjusted for emulation of a finger tap hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); furi_delay_ms(25); hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); - furi_delay_ms(100); + furi_delay_ms(75); hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); furi_delay_ms(25); hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); consumed = true; } else if(event->key == InputKeyUp) { - // Swipe to previous video - hid_hal_mouse_scroll(hid_tiktok->hid, -6); - hid_hal_mouse_scroll(hid_tiktok->hid, -8); - hid_hal_mouse_scroll(hid_tiktok->hid, -10); - hid_hal_mouse_scroll(hid_tiktok->hid, -8); - hid_hal_mouse_scroll(hid_tiktok->hid, -6); + // Emulate up swipe + hid_hal_mouse_scroll(hid_tiktok->hid, -12); + hid_hal_mouse_scroll(hid_tiktok->hid, -24); + hid_hal_mouse_scroll(hid_tiktok->hid, -38); + hid_hal_mouse_scroll(hid_tiktok->hid, -24); + hid_hal_mouse_scroll(hid_tiktok->hid, -12); consumed = true; } else if(event->key == InputKeyDown) { - // Swipe to next video - hid_hal_mouse_scroll(hid_tiktok->hid, 6); - hid_hal_mouse_scroll(hid_tiktok->hid, 8); - hid_hal_mouse_scroll(hid_tiktok->hid, 10); - hid_hal_mouse_scroll(hid_tiktok->hid, 8); - hid_hal_mouse_scroll(hid_tiktok->hid, 6); + // Emulate down swipe + hid_hal_mouse_scroll(hid_tiktok->hid, 12); + hid_hal_mouse_scroll(hid_tiktok->hid, 24); + hid_hal_mouse_scroll(hid_tiktok->hid, 38); + hid_hal_mouse_scroll(hid_tiktok->hid, 24); + hid_hal_mouse_scroll(hid_tiktok->hid, 12); consumed = true; } else if(event->key == InputKeyBack) { // Pause diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 26e61cf66..7fe0e6bd7 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -5,12 +5,13 @@ App( entry_point="js_app", stack_size=2 * 1024, resources="examples", - order=0, + order=10, provides=["js_app_start"], sources=[ "js_app.c", "js_modules.c", "js_thread.c", + "js_value.c", "plugin_api/app_api_table.cpp", "views/console_view.c", "modules/js_flipper.c", @@ -22,7 +23,7 @@ App( appid="js_app_start", apptype=FlipperAppType.STARTUP, entry_point="js_app_on_system_start", - order=160, + order=110, sources=["js_app.c"], ) @@ -110,6 +111,23 @@ App( fap_libs=["assets"], ) +App( + appid="js_gui__widget", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_widget_ep", + requires=["js_app"], + sources=["modules/js_gui/widget.c"], +) + +App( + appid="js_gui__icon", + apptype=FlipperAppType.PLUGIN, + entry_point="js_gui_icon_ep", + requires=["js_app"], + sources=["modules/js_gui/icon.c"], + fap_libs=["assets"], +) + App( appid="js_notification", apptype=FlipperAppType.PLUGIN, @@ -158,14 +176,6 @@ App( sources=["modules/js_storage.c"], ) -App( - appid="js_widget", - apptype=FlipperAppType.PLUGIN, - entry_point="js_widget_ep", - requires=["js_app"], - sources=["modules/js_widget.c"], -) - App( appid="js_vgm", apptype=FlipperAppType.PLUGIN, diff --git a/applications/system/js_app/examples/apps/Scripts/js_examples/gpio.js b/applications/system/js_app/examples/apps/Scripts/js_examples/gpio.js index 24d0f0286..6ea0a948f 100644 --- a/applications/system/js_app/examples/apps/Scripts/js_examples/gpio.js +++ b/applications/system/js_app/examples/apps/Scripts/js_examples/gpio.js @@ -3,6 +3,7 @@ let gpio = require("gpio"); // initialize pins let led = gpio.get("pc3"); // same as `gpio.get(7)` +let led2 = gpio.get("pa7"); // same as `gpio.get(2)` let pot = gpio.get("pc0"); // same as `gpio.get(16)` let button = gpio.get("pc1"); // same as `gpio.get(15)` led.init({ direction: "out", outMode: "push_pull" }); @@ -16,6 +17,13 @@ eventLoop.subscribe(eventLoop.timer("periodic", 1000), function (_, _item, led, return [led, !state]; }, led, true); +// cycle led pwm +print("Commencing PWM (PA7)"); +eventLoop.subscribe(eventLoop.timer("periodic", 10), function (_, _item, led2, state) { + led2.pwmWrite(10000, state); + return [led2, (state + 1) % 101]; +}, led2, 0); + // read potentiometer when button is pressed print("Press the button (PC1)"); eventLoop.subscribe(button.interrupt(), function (_, _item, pot) { diff --git a/applications/system/js_app/examples/apps/Scripts/js_examples/gui.js b/applications/system/js_app/examples/apps/Scripts/js_examples/gui.js index a1e023853..bc63a7ef6 100644 --- a/applications/system/js_app/examples/apps/Scripts/js_examples/gui.js +++ b/applications/system/js_app/examples/apps/Scripts/js_examples/gui.js @@ -9,8 +9,23 @@ let byteInputView = require("gui/byte_input"); let textBoxView = require("gui/text_box"); let dialogView = require("gui/dialog"); let filePicker = require("gui/file_picker"); +let widget = require("gui/widget"); +let icon = require("gui/icon"); let flipper = require("flipper"); +// declare clock widget children +let cuteDolphinWithWatch = icon.getBuiltin("DolphinWait_59x54"); +let jsLogo = icon.getBuiltin("js_script_10px"); +let stopwatchWidgetElements = [ + { element: "string", x: 67, y: 44, align: "bl", font: "big_numbers", text: "00 00" }, + { element: "string", x: 77, y: 22, align: "bl", font: "primary", text: "Stopwatch" }, + { element: "rect", x: 64, y: 27, w: 28, h: 20, radius: 3, fill: false }, + { element: "rect", x: 100, y: 27, w: 28, h: 20, radius: 3, fill: false }, + { element: "icon", x: 0, y: 5, iconData: cuteDolphinWithWatch }, + { element: "icon", x: 64, y: 13, iconData: jsLogo }, + { element: "button", button: "right", text: "Back" }, +]; + // declare view instances let views = { loading: loadingView.make(), @@ -31,6 +46,7 @@ let views = { longText: textBoxView.makeWith({ text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.", }), + stopwatchWidget: widget.makeWith({}, stopwatchWidgetElements), demos: submenuView.makeWith({ header: "Choose a demo", items: [ @@ -40,6 +56,7 @@ let views = { "Byte input", "Text box", "File picker", + "Widget", "Exit app", ], }), @@ -72,6 +89,8 @@ eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, v views.helloDialog.set("center", "Nice!"); gui.viewDispatcher.switchTo(views.helloDialog); } else if (index === 6) { + gui.viewDispatcher.switchTo(views.stopwatchWidget); + } else if (index === 7) { eventLoop.stop(); } }, gui, eventLoop, views); @@ -111,6 +130,31 @@ eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views gui.viewDispatcher.switchTo(views.demos); }, gui, views, eventLoop); +// go to the demo chooser screen when the right key is pressed on the widget screen +eventLoop.subscribe(views.stopwatchWidget.button, function (_sub, buttonId, gui, views) { + if (buttonId === "right") + gui.viewDispatcher.switchTo(views.demos); +}, gui, views); + +// count time +eventLoop.subscribe(eventLoop.timer("periodic", 500), function (_sub, _item, views, stopwatchWidgetElements, halfSeconds) { + let text = (halfSeconds / 2 / 60).toString(); + if (halfSeconds < 10 * 60 * 2) + text = "0" + text; + + text += (halfSeconds % 2 === 0) ? ":" : " "; + + if (((halfSeconds / 2) % 60) < 10) + text += "0"; + text += ((halfSeconds / 2) % 60).toString(); + + stopwatchWidgetElements[0].text = text; + views.stopwatchWidget.setChildren(stopwatchWidgetElements); + + halfSeconds++; + return [views, stopwatchWidgetElements, halfSeconds]; +}, views, stopwatchWidgetElements, 0); + // run UI gui.viewDispatcher.switchTo(views.demos); eventLoop.run(); diff --git a/applications/system/js_app/examples/apps/Scripts/js_examples/widget-js.fxbm b/applications/system/js_app/examples/apps/Scripts/js_examples/widget-js.fxbm deleted file mode 100644 index 9ba5783ce..000000000 Binary files a/applications/system/js_app/examples/apps/Scripts/js_examples/widget-js.fxbm and /dev/null differ diff --git a/applications/system/js_app/examples/apps/Scripts/js_examples/widget.js b/applications/system/js_app/examples/apps/Scripts/js_examples/widget.js deleted file mode 100644 index 32ba96d1b..000000000 --- a/applications/system/js_app/examples/apps/Scripts/js_examples/widget.js +++ /dev/null @@ -1,62 +0,0 @@ -// Script cannot work without widget module so check before -checkSdkFeatures(["widget"]); - -let widget = require("widget"); - -let demo_seconds = 30; - -print("Loading file", __filename); -print("From directory", __dirname); - -// addText supports "Primary" and "Secondary" font sizes. -widget.addText(10, 10, "Primary", "Example JS widget"); -widget.addText(10, 20, "Secondary", "Example widget from JS!"); - -// load a Xbm file from the same directory as this script. -widget.addText(0, 30, "Secondary", __filename); -let logo = widget.loadImageXbm(__dirname + "/widget-js.fxbm"); - -// add a line (x1, y1, x2, y2) -widget.addLine(10, 35, 120, 35); - -// add a circle/disc (x, y, radius) -widget.addCircle(12, 52, 10); -widget.addDisc(12, 52, 5); - -// add a frame/box (x, y, width, height) -widget.addFrame(30, 45, 10, 10); -widget.addBox(32, 47, 6, 6); - -// add a rounded frame/box (x, y, width, height, radius) -widget.addRframe(50, 45, 15, 15, 3); -widget.addRbox(53, 48, 6, 6, 2); - -// add a dot (x, y) -widget.addDot(100, 45); -widget.addDot(102, 44); -widget.addDot(104, 43); - -// add a glyph (x, y, glyph) -widget.addGlyph(115, 50, "#".charCodeAt(0)); - -// Show the widget (drawing the layers in the orderer they were added) -widget.show(); - -let i = 1; -let bitmap = undefined; -while (widget.isOpen() && i <= demo_seconds) { - // Print statements will only show up once the widget is closed. - print("count is at", i++); - - // You can call remove on any added item, it does not impact the other ids. - if (bitmap) { widget.remove(bitmap); bitmap = undefined; } - // All of the addXXX functions return an id that can be used to remove the item. - else { bitmap = widget.addXbm(77, 45, logo); } - - delay(1000); -} - -// If user did not press the back button, close the widget. -if (widget.isOpen()) { - widget.close(); -} \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo_8e1.js b/applications/system/js_app/examples/apps/Scripts/uart_echo_8e1.js new file mode 100644 index 000000000..171bb4637 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/uart_echo_8e1.js @@ -0,0 +1,15 @@ +// This script is like uart_echo, except it uses 8E1 framing (8 data bits, even +// parity, 1 stop bit) as opposed to the default 8N1 (8 data bits, no parity, +// 1 stop bit) + +let serial = require("serial"); +serial.setup("usart", 230400, { dataBits: "8", parity: "even", stopBits: "1" }); + +while (1) { + let rx_data = serial.readBytes(1, 1000); + if (rx_data !== undefined) { + serial.write(rx_data); + let data_view = Uint8Array(rx_data); + print("0x" + data_view[0].toString(16)); + } +} diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index c321150df..3084b9b2a 100644 --- a/applications/system/js_app/js_app.c +++ b/applications/system/js_app/js_app.c @@ -4,7 +4,9 @@ #include "js_app_i.h" #include #include -#include +#include +#include +#include #define TAG "JS app" @@ -131,12 +133,14 @@ int32_t js_app(void* arg) { } //-V773 typedef struct { - Cli* cli; + PipeSide* pipe; FuriSemaphore* exit_sem; } JsCliContext; static void js_cli_print(JsCliContext* ctx, const char* msg) { - cli_write(ctx->cli, (uint8_t*)msg, strlen(msg)); + UNUSED(ctx); + UNUSED(msg); + pipe_send(ctx->pipe, msg, strlen(msg)); } static void js_cli_exit(JsCliContext* ctx) { @@ -170,7 +174,7 @@ static void js_cli_callback(JsThreadEvent event, const char* msg, void* context) } } -void js_cli_execute(Cli* cli, FuriString* args, void* context) { +void js_cli_execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); const char* path = furi_string_get_cstr(args); @@ -187,14 +191,14 @@ void js_cli_execute(Cli* cli, FuriString* args, void* context) { break; } - JsCliContext ctx = {.cli = cli}; + JsCliContext ctx = {.pipe = pipe}; ctx.exit_sem = furi_semaphore_alloc(1, 0); printf("Running script %s, press CTRL+C to stop\r\n", path); JsThread* js_thread = js_thread_run(path, js_cli_callback, &ctx); while(furi_semaphore_acquire(ctx.exit_sem, 100) != FuriStatusOk) { - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } js_thread_stop(js_thread); @@ -206,8 +210,8 @@ void js_cli_execute(Cli* cli, FuriString* args, void* context) { void js_app_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "js", CliCommandFlagDefault, js_cli_execute, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command(registry, "js", CliCommandFlagDefault, js_cli_execute, NULL); furi_record_close(RECORD_CLI); #endif } diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index 429c01090..c720c54b9 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -202,12 +202,15 @@ static JsSdkCompatStatus return JsSdkCompatStatusCompatible; } -#define JS_SDK_COMPAT_ARGS \ - int32_t major, minor; \ - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&major), JS_ARG_INT32(&minor)); +static const JsValueDeclaration js_sdk_version_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE(JsValueTypeInt32), +}; +static const JsValueArguments js_sdk_version_args = JS_VALUE_ARGS(js_sdk_version_arg_list); void js_sdk_compatibility_status(struct mjs* mjs) { - JS_SDK_COMPAT_ARGS; + int32_t major, minor; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor); JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor); switch(status) { case JsSdkCompatStatusCompatible: @@ -223,7 +226,8 @@ void js_sdk_compatibility_status(struct mjs* mjs) { } void js_is_sdk_compatible(struct mjs* mjs) { - JS_SDK_COMPAT_ARGS; + int32_t major, minor; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor); JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor); mjs_return(mjs, mjs_mk_boolean(mjs, status == JsSdkCompatStatusCompatible)); } @@ -246,7 +250,8 @@ static bool js_internal_compat_ask_user(const char* message) { } void js_check_sdk_compatibility(struct mjs* mjs) { - JS_SDK_COMPAT_ARGS; + int32_t major, minor; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor); JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor); if(status != JsSdkCompatStatusCompatible) { FURI_LOG_E( @@ -267,6 +272,10 @@ void js_check_sdk_compatibility(struct mjs* mjs) { static const char* extra_features[] = { "baseline", // dummy "feature" + "gpio-pwm", + "gui-widget", + "serial-framing", + "gui-widget-extras", // extra modules "blebeacon", @@ -275,7 +284,6 @@ static const char* extra_features[] = { "subghz", "usbdisk", "vgm", - "widget", }; /** @@ -305,15 +313,20 @@ static bool js_internal_supports_all_of(struct mjs* mjs, mjs_val_t feature_arr) return true; } +static const JsValueDeclaration js_sdk_features_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAnyArray), +}; +static const JsValueArguments js_sdk_features_args = JS_VALUE_ARGS(js_sdk_features_arg_list); + void js_does_sdk_support(struct mjs* mjs) { mjs_val_t features; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_features_args, &features); mjs_return(mjs, mjs_mk_boolean(mjs, js_internal_supports_all_of(mjs, features))); } void js_check_sdk_features(struct mjs* mjs) { mjs_val_t features; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_features_args, &features); if(!js_internal_supports_all_of(mjs, features)) { FURI_LOG_E(TAG, "Script requests unsupported features"); diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index 656a7b7b6..8d30443d5 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -2,17 +2,22 @@ #include #include "js_thread_i.h" +#include "js_value.h" #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define PLUGIN_APP_ID "js" #define PLUGIN_API_VERSION 1 #define JS_SDK_VENDOR_FIRMWARE "unleashed" #define JS_SDK_VENDOR "flipperdevices" #define JS_SDK_MAJOR 0 -#define JS_SDK_MINOR 1 +#define JS_SDK_MINOR 3 /** * @brief Returns the foreign pointer in `obj["_"]` @@ -26,14 +31,13 @@ /** * @brief Syntax sugar for constructing an object * - * @example - * ```c + * Example: + * * mjs_val_t my_obj = mjs_mk_object(mjs); * JS_ASSIGN_MULTI(mjs, my_obj) { * JS_FIELD("method1", MJS_MK_FN(js_storage_file_is_open)); * JS_FIELD("method2", MJS_MK_FN(js_storage_file_is_open)); * } - * ``` */ #define JS_ASSIGN_MULTI(mjs, object) \ for(struct { \ @@ -65,184 +69,6 @@ typedef enum { JsForeignMagic_JsEventLoopContract, } JsForeignMagic; -// Are you tired of your silly little JS+C glue code functions being 75% -// argument validation code and 25% actual logic? Introducing: ASS (Argument -// Schema for Scripts)! ASS is a set of macros that reduce the typical -// boilerplate code of "check argument count, get arguments, validate arguments, -// extract C values from arguments" down to just one line! - -/** - * When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies - * that the function requires exactly as many arguments as were specified. - */ -#define JS_EXACTLY == -/** - * When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies - * that the function requires at least as many arguments as were specified. - */ -#define JS_AT_LEAST >= - -#define JS_ENUM_MAP(var_name, ...) \ - static const JsEnumMapping var_name##_mapping[] = { \ - {NULL, sizeof(var_name)}, \ - __VA_ARGS__, \ - {NULL, 0}, \ - }; - -typedef struct { - const char* name; - size_t value; -} JsEnumMapping; - -typedef struct { - void* out; - int (*validator)(mjs_val_t); - void (*converter)(struct mjs*, mjs_val_t*, void* out, const void* extra); - const char* expected_type; - bool (*extended_validator)(struct mjs*, mjs_val_t, const void* extra); - const void* extra_data; -} _js_arg_decl; - -static inline void _js_to_int32(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { - UNUSED(extra); - *(int32_t*)out = mjs_get_int32(mjs, *in); -} -#define JS_ARG_INT32(out) ((_js_arg_decl){out, mjs_is_number, _js_to_int32, "number", NULL, NULL}) - -static inline void _js_to_ptr(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { - UNUSED(extra); - *(void**)out = mjs_get_ptr(mjs, *in); -} -#define JS_ARG_PTR(out) \ - ((_js_arg_decl){out, mjs_is_foreign, _js_to_ptr, "opaque pointer", NULL, NULL}) - -static inline void _js_to_string(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { - UNUSED(extra); - *(const char**)out = mjs_get_string(mjs, in, NULL); -} -#define JS_ARG_STR(out) ((_js_arg_decl){out, mjs_is_string, _js_to_string, "string", NULL, NULL}) - -static inline void _js_to_bool(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { - UNUSED(extra); - *(bool*)out = !!mjs_get_bool(mjs, *in); -} -#define JS_ARG_BOOL(out) ((_js_arg_decl){out, mjs_is_boolean, _js_to_bool, "boolean", NULL, NULL}) - -static inline void _js_passthrough(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { - UNUSED(extra); - UNUSED(mjs); - *(mjs_val_t*)out = *in; -} -#define JS_ARG_ANY(out) ((_js_arg_decl){out, NULL, _js_passthrough, "any", NULL, NULL}) -#define JS_ARG_OBJ(out) ((_js_arg_decl){out, mjs_is_object, _js_passthrough, "any", NULL, NULL}) -#define JS_ARG_FN(out) \ - ((_js_arg_decl){out, mjs_is_function, _js_passthrough, "function", NULL, NULL}) -#define JS_ARG_ARR(out) ((_js_arg_decl){out, mjs_is_array, _js_passthrough, "array", NULL, NULL}) - -static inline bool _js_validate_struct(struct mjs* mjs, mjs_val_t val, const void* extra) { - JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra; - JsForeignMagic struct_magic = *(JsForeignMagic*)mjs_get_ptr(mjs, val); - return struct_magic == expected_magic; -} -#define JS_ARG_STRUCT(type, out) \ - ((_js_arg_decl){ \ - out, \ - mjs_is_foreign, \ - _js_to_ptr, \ - #type, \ - _js_validate_struct, \ - (void*)JsForeignMagic##_##type}) - -static inline bool _js_validate_obj_w_struct(struct mjs* mjs, mjs_val_t val, const void* extra) { - JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra; - JsForeignMagic struct_magic = *(JsForeignMagic*)JS_GET_INST(mjs, val); - return struct_magic == expected_magic; -} -#define JS_ARG_OBJ_WITH_STRUCT(type, out) \ - ((_js_arg_decl){ \ - out, \ - mjs_is_object, \ - _js_passthrough, \ - #type, \ - _js_validate_obj_w_struct, \ - (void*)JsForeignMagic##_##type}) - -static inline bool _js_validate_enum(struct mjs* mjs, mjs_val_t val, const void* extra) { - for(const JsEnumMapping* mapping = (JsEnumMapping*)extra + 1; mapping->name; mapping++) - if(strcmp(mapping->name, mjs_get_string(mjs, &val, NULL)) == 0) return true; - return false; -} -static inline void - _js_convert_enum(struct mjs* mjs, mjs_val_t* val, void* out, const void* extra) { - const JsEnumMapping* mapping = (JsEnumMapping*)extra; - size_t size = mapping->value; // get enum size from first entry - for(mapping++; mapping->name; mapping++) { - if(strcmp(mapping->name, mjs_get_string(mjs, val, NULL)) == 0) { - if(size == 1) - *(uint8_t*)out = mapping->value; - else if(size == 2) - *(uint16_t*)out = mapping->value; - else if(size == 4) - *(uint32_t*)out = mapping->value; - else if(size == 8) - *(uint64_t*)out = mapping->value; - return; - } - } - // unreachable, thanks to _js_validate_enum -} -#define JS_ARG_ENUM(var_name, name) \ - ((_js_arg_decl){ \ - &var_name, \ - mjs_is_string, \ - _js_convert_enum, \ - name " enum", \ - _js_validate_enum, \ - var_name##_mapping}) - -//-V:JS_FETCH_ARGS_OR_RETURN:1008 -/** - * @brief Fetches and validates the arguments passed to a JS function - * - * Example: `int32_t my_arg; JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&my_arg));` - * - * @warning This macro executes `return;` by design in case of an argument count - * mismatch or a validation failure - */ -#define JS_FETCH_ARGS_OR_RETURN(mjs, arg_operator, ...) \ - _js_arg_decl _js_args[] = {__VA_ARGS__}; \ - int _js_arg_cnt = COUNT_OF(_js_args); \ - mjs_val_t _js_arg_vals[_js_arg_cnt]; \ - if(!(mjs_nargs(mjs) arg_operator _js_arg_cnt)) \ - JS_ERROR_AND_RETURN( \ - mjs, \ - MJS_BAD_ARGS_ERROR, \ - "expected %s%d arguments, got %d", \ - #arg_operator, \ - _js_arg_cnt, \ - mjs_nargs(mjs)); \ - for(int _i = 0; _i < _js_arg_cnt; _i++) { \ - _js_arg_vals[_i] = mjs_arg(mjs, _i); \ - if(_js_args[_i].validator) \ - if(!_js_args[_i].validator(_js_arg_vals[_i])) \ - JS_ERROR_AND_RETURN( \ - mjs, \ - MJS_BAD_ARGS_ERROR, \ - "argument %d: expected %s", \ - _i, \ - _js_args[_i].expected_type); \ - if(_js_args[_i].extended_validator) \ - if(!_js_args[_i].extended_validator(mjs, _js_arg_vals[_i], _js_args[_i].extra_data)) \ - JS_ERROR_AND_RETURN( \ - mjs, \ - MJS_BAD_ARGS_ERROR, \ - "argument %d: expected %s", \ - _i, \ - _js_args[_i].expected_type); \ - _js_args[_i].converter( \ - mjs, &_js_arg_vals[_i], _js_args[_i].out, _js_args[_i].extra_data); \ - } - /** * @brief Prepends an error, sets the JS return value to `undefined` and returns * from the C function @@ -255,6 +81,18 @@ static inline void return; \ } while(0) +/** + * @brief Prepends an error, sets the JS return value to `undefined` and returns + * a value C function + * @warning This macro executes `return;` by design + */ +#define JS_ERROR_AND_RETURN_VAL(mjs, error_code, ret_val, ...) \ + do { \ + mjs_prepend_errorf(mjs, error_code, __VA_ARGS__); \ + mjs_return(mjs, MJS_UNDEFINED); \ + return ret_val; \ + } while(0) + typedef struct JsModules JsModules; typedef void* (*JsModuleConstructor)(struct mjs* mjs, mjs_val_t* object, JsModules* modules); @@ -305,3 +143,7 @@ void js_does_sdk_support(struct mjs* mjs); * @brief `checkSdkFeatures` function */ void js_check_sdk_features(struct mjs* mjs); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 600c2676e..a41a28d11 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -92,7 +92,7 @@ static void js_console_debug(struct mjs* mjs) { } static void js_exit_flag_poll(struct mjs* mjs) { - uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, 0); + uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0); if(flags & FuriFlagError) { return; } @@ -102,7 +102,8 @@ static void js_exit_flag_poll(struct mjs* mjs) { } bool js_delay_with_flags(struct mjs* mjs, uint32_t time) { - uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, time); + uint32_t flags = + furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, time); if(flags & FuriFlagError) { return false; } @@ -124,7 +125,7 @@ uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags_mask, uint32_t timeout) { uint32_t flags = furi_thread_flags_get(); furi_check((flags & FuriFlagError) == 0); if(flags == 0) { - flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); + flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny | FuriFlagNoClear, timeout); } else { uint32_t state = furi_thread_flags_clear(flags & flags_mask); furi_check((state & FuriFlagError) == 0); @@ -197,18 +198,15 @@ static void js_require(struct mjs* mjs) { } static void js_parse_int(struct mjs* mjs) { - const char* str; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_STR(&str)); + static const JsValueDeclaration js_parse_int_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 10), + }; + static const JsValueArguments js_parse_int_args = JS_VALUE_ARGS(js_parse_int_arg_list); - int32_t base = 10; - if(mjs_nargs(mjs) >= 2) { - mjs_val_t base_arg = mjs_arg(mjs, 1); - if(!mjs_is_number(base_arg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Base must be a number"); - mjs_return(mjs, MJS_UNDEFINED); - } - base = mjs_get_int(mjs, base_arg); - } + const char* str; + int32_t base; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_parse_int_args, &str, &base); int32_t num; if(strint_to_int32(str, NULL, &num, base) != StrintParseNoError) { diff --git a/applications/system/js_app/js_thread_i.h b/applications/system/js_app/js_thread_i.h index a73cbb4bc..5fbdb06d0 100644 --- a/applications/system/js_app/js_thread_i.h +++ b/applications/system/js_app/js_thread_i.h @@ -11,6 +11,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define INST_PROP_NAME "_" typedef enum { @@ -23,3 +27,7 @@ bool js_delay_with_flags(struct mjs* mjs, uint32_t time); void js_flags_set(struct mjs* mjs, uint32_t flags); uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c new file mode 100644 index 000000000..6ce1cf37a --- /dev/null +++ b/applications/system/js_app/js_value.c @@ -0,0 +1,291 @@ +#include "js_value.h" +#include + +#ifdef APP_UNIT_TESTS +#define JS_VAL_DEBUG +#endif + +size_t js_value_buffer_size(const JsValueParseDeclaration declaration) { + if(declaration.source == JsValueParseSourceValue) { + const JsValueDeclaration* value_decl = declaration.value_decl; + JsValueType type = value_decl->type & JsValueTypeMask; + + if(type == JsValueTypeString) return 1; + + if(type == JsValueTypeObject) { + size_t total = 0; + for(size_t i = 0; i < value_decl->n_children; i++) + total += js_value_buffer_size( + JS_VALUE_PARSE_SOURCE_VALUE(value_decl->object_fields[i].value)); + return total; + } + + return 0; + + } else { + const JsValueArguments* arg_decl = declaration.argument_decl; + size_t total = 0; + for(size_t i = 0; i < arg_decl->n_children; i++) + total += js_value_buffer_size(JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i])); + return total; + } +} + +static size_t js_value_resulting_c_values_count(const JsValueParseDeclaration declaration) { + if(declaration.source == JsValueParseSourceValue) { + const JsValueDeclaration* value_decl = declaration.value_decl; + JsValueType type = value_decl->type & JsValueTypeMask; + + if(type == JsValueTypeObject) { + size_t total = 0; + for(size_t i = 0; i < value_decl->n_children; i++) + total += js_value_resulting_c_values_count( + JS_VALUE_PARSE_SOURCE_VALUE(value_decl->object_fields[i].value)); + return total; + } + + return 1; + + } else { + const JsValueArguments* arg_decl = declaration.argument_decl; + size_t total = 0; + for(size_t i = 0; i < arg_decl->n_children; i++) + total += js_value_resulting_c_values_count( + JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i])); + return total; + } +} + +#define PREPEND_JS_ERROR_AND_RETURN(mjs, flags, ...) \ + do { \ + if((flags) & JsValueParseFlagReturnOnError) \ + mjs_prepend_errorf((mjs), MJS_BAD_ARGS_ERROR, __VA_ARGS__); \ + return JsValueParseStatusJsError; \ + } while(0) + +#define PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, type) \ + PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected %s", type) + +static void js_value_assign_enum_val(void* destination, JsValueType type_w_flags, uint32_t value) { + if(type_w_flags & JsValueTypeEnumSize1) { + *(uint8_t*)destination = value; + } else if(type_w_flags & JsValueTypeEnumSize2) { + *(uint16_t*)destination = value; + } else if(type_w_flags & JsValueTypeEnumSize4) { + *(uint32_t*)destination = value; + } +} + +static bool js_value_is_null_or_undefined(mjs_val_t* val_ptr) { + return mjs_is_null(*val_ptr) || mjs_is_undefined(*val_ptr); +} + +static bool js_value_maybe_assign_default( + const JsValueDeclaration* declaration, + mjs_val_t* val_ptr, + void* destination, + size_t size) { + if((declaration->type & JsValueTypePermitNull) && js_value_is_null_or_undefined(val_ptr)) { + memcpy(destination, &declaration->default_value, size); + return true; + } + return false; +} + +typedef int (*MjsTypecheckFn)(mjs_val_t value); + +static JsValueParseStatus js_value_parse_literal( + struct mjs* mjs, + JsValueParseFlag flags, + mjs_val_t* destination, + mjs_val_t* source, + MjsTypecheckFn typecheck, + const char* type_name) { + if(!typecheck(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, type_name); + *destination = *source; + return JsValueParseStatusOk; +} + +static JsValueParseStatus js_value_parse_va( + struct mjs* mjs, + const JsValueParseDeclaration declaration, + JsValueParseFlag flags, + mjs_val_t* source, + mjs_val_t* buffer, + size_t* buffer_index, + va_list* out_pointers) { + if(declaration.source == JsValueParseSourceArguments) { + const JsValueArguments* arg_decl = declaration.argument_decl; + + for(size_t i = 0; i < arg_decl->n_children; i++) { + mjs_val_t arg_val = mjs_arg(mjs, i); + JsValueParseStatus status = js_value_parse_va( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i]), + flags, + &arg_val, + buffer, + buffer_index, + out_pointers); + if(status != JsValueParseStatusOk) return status; + } + + return JsValueParseStatusOk; + } + + const JsValueDeclaration* value_decl = declaration.value_decl; + JsValueType type_w_flags = value_decl->type; + JsValueType type_noflags = type_w_flags & JsValueTypeMask; + bool is_null_but_allowed = (type_w_flags & JsValueTypePermitNull) && + js_value_is_null_or_undefined(source); + + void* destination = NULL; + if(type_noflags != JsValueTypeObject) destination = va_arg(*out_pointers, void*); + + switch(type_noflags) { + // Literal terms + case JsValueTypeAny: + *(mjs_val_t*)destination = *source; + break; + case JsValueTypeAnyArray: + return js_value_parse_literal(mjs, flags, destination, source, mjs_is_array, "array"); + case JsValueTypeAnyObject: + return js_value_parse_literal(mjs, flags, destination, source, mjs_is_object, "array"); + case JsValueTypeFunction: + return js_value_parse_literal( + mjs, flags, destination, source, mjs_is_function, "function"); + + // Primitive types + case JsValueTypeRawPointer: { + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(void*))) break; + if(!mjs_is_foreign(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "pointer"); + *(void**)destination = mjs_get_ptr(mjs, *source); + break; + } + case JsValueTypeInt32: { + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(int32_t))) break; + if(!mjs_is_number(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "number"); + *(int32_t*)destination = mjs_get_int32(mjs, *source); + break; + } + case JsValueTypeDouble: { + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(double))) break; + if(!mjs_is_number(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "number"); + *(double*)destination = mjs_get_double(mjs, *source); + break; + } + case JsValueTypeBool: { + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(bool))) break; + if(!mjs_is_boolean(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "bool"); + *(bool*)destination = mjs_get_bool(mjs, *source); + break; + } + case JsValueTypeString: { + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(const char*))) + break; + if(!mjs_is_string(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "string"); + buffer[*buffer_index] = *source; + *(const char**)destination = mjs_get_string(mjs, &buffer[*buffer_index], NULL); + (*buffer_index)++; + break; + } + + // Types with children + case JsValueTypeEnum: { + if(is_null_but_allowed) { + js_value_assign_enum_val( + destination, type_w_flags, value_decl->default_value.enum_val); + + } else if(mjs_is_string(*source)) { + const char* str = mjs_get_string(mjs, source, NULL); + furi_check(str); + + bool match_found = false; + for(size_t i = 0; i < value_decl->n_children; i++) { + const JsValueEnumVariant* variant = &value_decl->enum_variants[i]; + if(strcmp(str, variant->string_value) == 0) { + js_value_assign_enum_val(destination, type_w_flags, variant->num_value); + match_found = true; + break; + } + } + + if(!match_found) + PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "one of permitted strings"); + + } else { + PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "string"); + } + break; + } + + case JsValueTypeObject: { + if(!(is_null_but_allowed || mjs_is_object(*source))) + PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "object"); + for(size_t i = 0; i < value_decl->n_children; i++) { + const JsValueObjectField* field = &value_decl->object_fields[i]; + mjs_val_t field_val = mjs_get(mjs, *source, field->field_name, ~0); + JsValueParseStatus status = js_value_parse_va( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(field->value), + flags, + &field_val, + buffer, + buffer_index, + out_pointers); + if(status != JsValueParseStatusOk) + PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "field %s: ", field->field_name); + } + break; + } + + case JsValueTypeMask: + case JsValueTypeEnumSize1: + case JsValueTypeEnumSize2: + case JsValueTypeEnumSize4: + case JsValueTypePermitNull: + furi_crash(); + } + + return JsValueParseStatusOk; +} + +JsValueParseStatus js_value_parse( + struct mjs* mjs, + const JsValueParseDeclaration declaration, + JsValueParseFlag flags, + mjs_val_t* buffer, + size_t buf_size, + mjs_val_t* source, + size_t n_c_vals, + ...) { + furi_check(mjs); + furi_check(buffer); + + if(declaration.source == JsValueParseSourceValue) { + furi_check(source); + furi_check(declaration.value_decl); + } else { + furi_check(source == NULL); + furi_check(declaration.argument_decl); + } + +#ifdef JS_VAL_DEBUG + furi_check(buf_size == js_value_buffer_size(declaration)); + furi_check(n_c_vals == js_value_resulting_c_values_count(declaration)); +#else + UNUSED(js_value_resulting_c_values_count); +#endif + + va_list out_pointers; + va_start(out_pointers, n_c_vals); + + size_t buffer_index = 0; + JsValueParseStatus status = + js_value_parse_va(mjs, declaration, flags, source, buffer, &buffer_index, &out_pointers); + furi_check(buffer_index <= buf_size); + + va_end(out_pointers); + + return status; +} diff --git a/applications/system/js_app/js_value.h b/applications/system/js_app/js_value.h new file mode 100644 index 000000000..765bcb3bb --- /dev/null +++ b/applications/system/js_app/js_value.h @@ -0,0 +1,212 @@ +#pragma once + +#include +#include "js_modules.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + // literal types + JsValueTypeAny, //mjs, &result, context->callback, @@ -68,6 +63,12 @@ static void js_event_loop_callback_generic(void* param) { context->arity, context->arguments); + bool is_error = strcmp(mjs_strerror(context->mjs, error), "NO_ERROR") != 0; + bool asked_to_stop = js_flags_wait(context->mjs, ThreadEventStop, 0) & ThreadEventStop; + if(is_error || asked_to_stop) { + furi_event_loop_stop(context->event_loop); + } + // save returned args for next call if(mjs_array_length(context->mjs, result) != context->arity - SYSTEM_ARGS) return; for(size_t i = 0; i < context->arity - SYSTEM_ARGS; i++) { @@ -111,11 +112,14 @@ static void js_event_loop_subscription_cancel(struct mjs* mjs) { JsEventLoopSubscription* subscription = JS_GET_CONTEXT(mjs); if(subscription->object_type == JsEventLoopObjectTypeTimer) { + // timer operations are deferred, which creates lifetime issues + // just stop the timer and let the cleanup routine free everything when the script is done furi_event_loop_timer_stop(subscription->object); - } else { - furi_event_loop_unsubscribe(subscription->loop, subscription->object); + return; } + furi_event_loop_unsubscribe(subscription->loop, subscription->object); + free(subscription->context->arguments); free(subscription->context); @@ -140,10 +144,16 @@ static void js_event_loop_subscribe(struct mjs* mjs) { JsEventLoop* module = JS_GET_CONTEXT(mjs); // get arguments + static const JsValueDeclaration js_loop_subscribe_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeRawPointer), + JS_VALUE_SIMPLE(JsValueTypeFunction), + }; + static const JsValueArguments js_loop_subscribe_args = + JS_VALUE_ARGS(js_loop_subscribe_arg_list); + JsEventLoopContract* contract; mjs_val_t callback; - JS_FETCH_ARGS_OR_RETURN( - mjs, JS_AT_LEAST, JS_ARG_STRUCT(JsEventLoopContract, &contract), JS_ARG_FN(&callback)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_subscribe_args, &contract, &callback); // create subscription object JsEventLoopSubscription* subscription = malloc(sizeof(JsEventLoopSubscription)); @@ -158,6 +168,7 @@ static void js_event_loop_subscribe(struct mjs* mjs) { mjs_set(mjs, subscription_obj, "cancel", ~0, MJS_MK_FN(js_event_loop_subscription_cancel)); // create callback context + context->event_loop = module->loop; context->object_type = contract->object_type; context->arity = mjs_nargs(mjs) - SYSTEM_ARGS + 2; context->arguments = calloc(context->arity, sizeof(mjs_val_t)); @@ -237,20 +248,22 @@ static void js_event_loop_stop(struct mjs* mjs) { * event */ static void js_event_loop_timer(struct mjs* mjs) { - // get arguments - const char* mode_str; - int32_t interval; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&mode_str), JS_ARG_INT32(&interval)); - JsEventLoop* module = JS_GET_CONTEXT(mjs); + static const JsValueEnumVariant js_loop_timer_mode_variants[] = { + {"periodic", FuriEventLoopTimerTypePeriodic}, + {"oneshot", FuriEventLoopTimerTypeOnce}, + }; + + static const JsValueDeclaration js_loop_timer_arg_list[] = { + JS_VALUE_ENUM(FuriEventLoopTimerType, js_loop_timer_mode_variants), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_loop_timer_args = JS_VALUE_ARGS(js_loop_timer_arg_list); FuriEventLoopTimerType mode; - if(strcasecmp(mode_str, "periodic") == 0) { - mode = FuriEventLoopTimerTypePeriodic; - } else if(strcasecmp(mode_str, "oneshot") == 0) { - mode = FuriEventLoopTimerTypeOnce; - } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 0: unknown mode"); - } + int32_t interval; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_timer_args, &mode, &interval); + + JsEventLoop* module = JS_GET_CONTEXT(mjs); // make timer contract JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); @@ -288,8 +301,14 @@ static mjs_val_t */ static void js_event_loop_queue_send(struct mjs* mjs) { // get arguments + static const JsValueDeclaration js_loop_q_send_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_loop_q_send_args = JS_VALUE_ARGS(js_loop_q_send_arg_list); + mjs_val_t message; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&message)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_q_send_args, &message); + JsEventLoopContract* contract = JS_GET_CONTEXT(mjs); // send message @@ -306,8 +325,14 @@ static void js_event_loop_queue_send(struct mjs* mjs) { */ static void js_event_loop_queue(struct mjs* mjs) { // get arguments + static const JsValueDeclaration js_loop_q_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_loop_q_args = JS_VALUE_ARGS(js_loop_q_arg_list); + int32_t length; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&length)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_q_args, &length); + JsEventLoop* module = JS_GET_CONTEXT(mjs); // make queue contract @@ -333,37 +358,22 @@ static void js_event_loop_queue(struct mjs* mjs) { mjs_return(mjs, queue); } -static void js_event_loop_tick(void* param) { - JsEventLoopTickContext* context = param; - uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0); - if(flags & FuriFlagError) { - return; - } - if(flags & ThreadEventStop) { - furi_event_loop_stop(context->loop); - mjs_exit(context->mjs); - } -} - static void* js_event_loop_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { UNUSED(modules); mjs_val_t event_loop_obj = mjs_mk_object(mjs); JsEventLoop* module = malloc(sizeof(JsEventLoop)); - JsEventLoopTickContext* tick_ctx = malloc(sizeof(JsEventLoopTickContext)); module->loop = furi_event_loop_alloc(); - tick_ctx->loop = module->loop; - tick_ctx->mjs = mjs; - module->tick_context = tick_ctx; - furi_event_loop_tick_set(module->loop, 10, js_event_loop_tick, tick_ctx); SubscriptionArray_init(module->subscriptions); ContractArray_init(module->owned_contracts); - mjs_set(mjs, event_loop_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, module)); - mjs_set(mjs, event_loop_obj, "subscribe", ~0, MJS_MK_FN(js_event_loop_subscribe)); - mjs_set(mjs, event_loop_obj, "run", ~0, MJS_MK_FN(js_event_loop_run)); - mjs_set(mjs, event_loop_obj, "stop", ~0, MJS_MK_FN(js_event_loop_stop)); - mjs_set(mjs, event_loop_obj, "timer", ~0, MJS_MK_FN(js_event_loop_timer)); - mjs_set(mjs, event_loop_obj, "queue", ~0, MJS_MK_FN(js_event_loop_queue)); + JS_ASSIGN_MULTI(mjs, event_loop_obj) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, module)); + JS_FIELD("subscribe", MJS_MK_FN(js_event_loop_subscribe)); + JS_FIELD("run", MJS_MK_FN(js_event_loop_run)); + JS_FIELD("stop", MJS_MK_FN(js_event_loop_stop)); + JS_FIELD("timer", MJS_MK_FN(js_event_loop_timer)); + JS_FIELD("queue", MJS_MK_FN(js_event_loop_queue)); + } *object = event_loop_obj; return module; @@ -418,7 +428,6 @@ static void js_event_loop_destroy(void* inst) { ContractArray_clear(module->owned_contracts); furi_event_loop_free(module->loop); - free(module->tick_context); free(module); } } diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c index ae3fefd71..63de6900a 100644 --- a/applications/system/js_app/modules/js_gpio.c +++ b/applications/system/js_app/modules/js_gpio.c @@ -1,6 +1,7 @@ #include "../js_modules.h" // IWYU pragma: keep #include "./js_event_loop/js_event_loop.h" #include +#include #include #include #include @@ -17,6 +18,7 @@ typedef struct { FuriSemaphore* interrupt_semaphore; JsEventLoopContract* interrupt_contract; FuriHalAdcChannel adc_channel; + FuriHalPwmOutputId pwm_output; FuriHalAdcHandle* adc_handle; } JsGpioPinInst; @@ -52,83 +54,114 @@ static void js_gpio_int_cb(void* arg) { * ``` */ static void js_gpio_init(struct mjs* mjs) { - // deconstruct mode object - mjs_val_t mode_arg; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&mode_arg)); - mjs_val_t direction_arg = mjs_get(mjs, mode_arg, "direction", ~0); - mjs_val_t out_mode_arg = mjs_get(mjs, mode_arg, "outMode", ~0); - mjs_val_t in_mode_arg = mjs_get(mjs, mode_arg, "inMode", ~0); - mjs_val_t edge_arg = mjs_get(mjs, mode_arg, "edge", ~0); - mjs_val_t pull_arg = mjs_get(mjs, mode_arg, "pull", ~0); + // direction variants + typedef enum { + JsGpioDirectionIn, + JsGpioDirectionOut, + } JsGpioDirection; + static const JsValueEnumVariant js_gpio_direction_variants[] = { + {"in", JsGpioDirectionIn}, + {"out", JsGpioDirectionOut}, + }; + static const JsValueDeclaration js_gpio_direction = + JS_VALUE_ENUM(JsGpioDirection, js_gpio_direction_variants); - // get strings - const char* direction = mjs_get_string(mjs, &direction_arg, NULL); - const char* out_mode = mjs_get_string(mjs, &out_mode_arg, NULL); - const char* in_mode = mjs_get_string(mjs, &in_mode_arg, NULL); - const char* edge = mjs_get_string(mjs, &edge_arg, NULL); - const char* pull = mjs_get_string(mjs, &pull_arg, NULL); - if(!direction) - JS_ERROR_AND_RETURN( - mjs, MJS_BAD_ARGS_ERROR, "Expected string in \"direction\" field of mode object"); - if(!out_mode) out_mode = "open_drain"; - if(!in_mode) in_mode = "plain_digital"; - if(!edge) edge = "rising"; + // inMode variants + typedef enum { + JsGpioInModeAnalog = (0 << 0), + JsGpioInModePlainDigital = (1 << 0), + JsGpioInModeInterrupt = (2 << 0), + JsGpioInModeEvent = (3 << 0), + } JsGpioInMode; + static const JsValueEnumVariant js_gpio_in_mode_variants[] = { + {"analog", JsGpioInModeAnalog}, + {"plain_digital", JsGpioInModePlainDigital}, + {"interrupt", JsGpioInModeInterrupt}, + {"event", JsGpioInModeEvent}, + }; + static const JsValueDeclaration js_gpio_in_mode = + JS_VALUE_ENUM_W_DEFAULT(JsGpioInMode, js_gpio_in_mode_variants, JsGpioInModePlainDigital); + + // outMode variants + typedef enum { + JsGpioOutModePushPull, + JsGpioOutModeOpenDrain, + } JsGpioOutMode; + static const JsValueEnumVariant js_gpio_out_mode_variants[] = { + {"push_pull", JsGpioOutModePushPull}, + {"open_drain", JsGpioOutModeOpenDrain}, + }; + static const JsValueDeclaration js_gpio_out_mode = + JS_VALUE_ENUM_W_DEFAULT(JsGpioOutMode, js_gpio_out_mode_variants, JsGpioOutModeOpenDrain); + + // edge variants + typedef enum { + JsGpioEdgeRising = (0 << 2), + JsGpioEdgeFalling = (1 << 2), + JsGpioEdgeBoth = (2 << 2), + } JsGpioEdge; + static const JsValueEnumVariant js_gpio_edge_variants[] = { + {"rising", JsGpioEdgeRising}, + {"falling", JsGpioEdgeFalling}, + {"both", JsGpioEdgeBoth}, + }; + static const JsValueDeclaration js_gpio_edge = + JS_VALUE_ENUM_W_DEFAULT(JsGpioEdge, js_gpio_edge_variants, JsGpioEdgeRising); + + // pull variants + static const JsValueEnumVariant js_gpio_pull_variants[] = { + {"up", GpioPullUp}, + {"down", GpioPullDown}, + }; + static const JsValueDeclaration js_gpio_pull = + JS_VALUE_ENUM_W_DEFAULT(GpioPull, js_gpio_pull_variants, GpioPullNo); + + // complete mode object + static const JsValueObjectField js_gpio_mode_object_fields[] = { + {"direction", &js_gpio_direction}, + {"inMode", &js_gpio_in_mode}, + {"outMode", &js_gpio_out_mode}, + {"edge", &js_gpio_edge}, + {"pull", &js_gpio_pull}, + }; + + // function args + static const JsValueDeclaration js_gpio_init_arg_list[] = { + JS_VALUE_OBJECT_W_DEFAULTS(js_gpio_mode_object_fields), + }; + static const JsValueArguments js_gpio_init_args = JS_VALUE_ARGS(js_gpio_init_arg_list); + + JsGpioDirection direction; + JsGpioInMode in_mode; + JsGpioOutMode out_mode; + JsGpioEdge edge; + GpioPull pull; + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_gpio_init_args, &direction, &in_mode, &out_mode, &edge, &pull); - // convert strings to mode GpioMode mode; - if(strcmp(direction, "out") == 0) { - if(strcmp(out_mode, "push_pull") == 0) - mode = GpioModeOutputPushPull; - else if(strcmp(out_mode, "open_drain") == 0) - mode = GpioModeOutputOpenDrain; - else - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid outMode"); - } else if(strcmp(direction, "in") == 0) { - if(strcmp(in_mode, "analog") == 0) { - mode = GpioModeAnalog; - } else if(strcmp(in_mode, "plain_digital") == 0) { - mode = GpioModeInput; - } else if(strcmp(in_mode, "interrupt") == 0) { - if(strcmp(edge, "rising") == 0) - mode = GpioModeInterruptRise; - else if(strcmp(edge, "falling") == 0) - mode = GpioModeInterruptFall; - else if(strcmp(edge, "both") == 0) - mode = GpioModeInterruptRiseFall; - else - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); - } else if(strcmp(in_mode, "event") == 0) { - if(strcmp(edge, "rising") == 0) - mode = GpioModeEventRise; - else if(strcmp(edge, "falling") == 0) - mode = GpioModeEventFall; - else if(strcmp(edge, "both") == 0) - mode = GpioModeEventRiseFall; - else - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); - } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid inMode"); - } + if(direction == JsGpioDirectionOut) { + static const GpioMode js_gpio_out_mode_lut[] = { + [JsGpioOutModePushPull] = GpioModeOutputPushPull, + [JsGpioOutModeOpenDrain] = GpioModeOutputOpenDrain, + }; + mode = js_gpio_out_mode_lut[out_mode]; } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid direction"); + static const GpioMode js_gpio_in_mode_lut[] = { + [JsGpioInModeAnalog] = GpioModeAnalog, + [JsGpioInModePlainDigital] = GpioModeInput, + [JsGpioInModeInterrupt | JsGpioEdgeRising] = GpioModeInterruptRise, + [JsGpioInModeInterrupt | JsGpioEdgeFalling] = GpioModeInterruptFall, + [JsGpioInModeInterrupt | JsGpioEdgeBoth] = GpioModeInterruptRiseFall, + [JsGpioInModeEvent | JsGpioEdgeRising] = GpioModeEventRise, + [JsGpioInModeEvent | JsGpioEdgeFalling] = GpioModeEventFall, + [JsGpioInModeEvent | JsGpioEdgeBoth] = GpioModeEventRiseFall, + }; + mode = js_gpio_in_mode_lut[in_mode | edge]; } - // convert pull - GpioPull pull_mode; - if(!pull) { - pull_mode = GpioPullNo; - } else if(strcmp(pull, "up") == 0) { - pull_mode = GpioPullUp; - } else if(strcmp(pull, "down") == 0) { - pull_mode = GpioPullDown; - } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid pull"); - } - - // init GPIO JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); - furi_hal_gpio_init(manager_data->pin, mode, pull_mode, GpioSpeedVeryHigh); - mjs_return(mjs, MJS_UNDEFINED); + furi_hal_gpio_init(manager_data->pin, mode, pull, GpioSpeedVeryHigh); } /** @@ -144,8 +177,13 @@ static void js_gpio_init(struct mjs* mjs) { * ``` */ static void js_gpio_write(struct mjs* mjs) { + static const JsValueDeclaration js_gpio_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeBool), + }; + static const JsValueArguments js_gpio_write_args = JS_VALUE_ARGS(js_gpio_write_arg_list); bool level; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_BOOL(&level)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_write_args, &level); + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); furi_hal_gpio_write(manager_data->pin, level); mjs_return(mjs, MJS_UNDEFINED); @@ -231,6 +269,95 @@ static void js_gpio_read_analog(struct mjs* mjs) { mjs_return(mjs, mjs_mk_number(mjs, (double)millivolts)); } +/** + * @brief Determines whether this pin supports PWM + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * assert_eq(true, gpio.get("pa4").isPwmSupported()); + * assert_eq(false, gpio.get("pa5").isPwmSupported()); + * ``` + */ +static void js_gpio_is_pwm_supported(struct mjs* mjs) { + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, manager_data->pwm_output != FuriHalPwmOutputIdNone)); +} + +/** + * @brief Sets PWM parameters and starts the PWM + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let pa4 = gpio.get("pa4"); + * pa4.pwmWrite(10000, 50); + * ``` + */ +static void js_gpio_pwm_write(struct mjs* mjs) { + static const JsValueDeclaration js_gpio_pwm_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_gpio_pwm_write_args = + JS_VALUE_ARGS(js_gpio_pwm_write_arg_list); + int32_t frequency, duty; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_pwm_write_args, &frequency, &duty); + + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + if(manager_data->pwm_output == FuriHalPwmOutputIdNone) { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin"); + } + + if(furi_hal_pwm_is_running(manager_data->pwm_output)) { + furi_hal_pwm_set_params(manager_data->pwm_output, frequency, duty); + } else { + furi_hal_pwm_start(manager_data->pwm_output, frequency, duty); + } +} + +/** + * @brief Determines whether PWM is running + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * assert_eq(false, gpio.get("pa4").isPwmRunning()); + * ``` + */ +static void js_gpio_is_pwm_running(struct mjs* mjs) { + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + if(manager_data->pwm_output == FuriHalPwmOutputIdNone) { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin"); + } + + mjs_return(mjs, mjs_mk_boolean(mjs, furi_hal_pwm_is_running(manager_data->pwm_output))); +} + +/** + * @brief Stops PWM + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let pa4 = gpio.get("pa4"); + * pa4.pwmWrite(10000, 50); + * pa4.pwmStop(); + * ``` + */ +static void js_gpio_pwm_stop(struct mjs* mjs) { + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + if(manager_data->pwm_output == FuriHalPwmOutputIdNone) { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin"); + } + + furi_hal_pwm_stop(manager_data->pwm_output); +} + /** * @brief Returns an object that manages a specified pin. * @@ -242,8 +369,13 @@ static void js_gpio_read_analog(struct mjs* mjs) { * ``` */ static void js_gpio_get(struct mjs* mjs) { + static const JsValueDeclaration js_gpio_get_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gpio_get_args = JS_VALUE_ARGS(js_gpio_get_arg_list); mjs_val_t name_arg; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&name_arg)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_get_args, &name_arg); + const char* name_string = mjs_get_string(mjs, &name_arg, NULL); const GpioPinRecord* pin_record = NULL; @@ -269,12 +401,19 @@ static void js_gpio_get(struct mjs* mjs) { manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0); manager_data->adc_handle = module->adc_handle; manager_data->adc_channel = pin_record->channel; - mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data)); - mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init)); - mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write)); - mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read)); - mjs_set(mjs, manager, "readAnalog", ~0, MJS_MK_FN(js_gpio_read_analog)); - mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt)); + manager_data->pwm_output = pin_record->pwm_output; + JS_ASSIGN_MULTI(mjs, manager) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, manager_data)); + JS_FIELD("init", MJS_MK_FN(js_gpio_init)); + JS_FIELD("write", MJS_MK_FN(js_gpio_write)); + JS_FIELD("read", MJS_MK_FN(js_gpio_read)); + JS_FIELD("readAnalog", MJS_MK_FN(js_gpio_read_analog)); + JS_FIELD("interrupt", MJS_MK_FN(js_gpio_interrupt)); + JS_FIELD("isPwmSupported", MJS_MK_FN(js_gpio_is_pwm_supported)); + JS_FIELD("pwmWrite", MJS_MK_FN(js_gpio_pwm_write)); + JS_FIELD("isPwmRunning", MJS_MK_FN(js_gpio_is_pwm_running)); + JS_FIELD("pwmStop", MJS_MK_FN(js_gpio_pwm_stop)); + } mjs_return(mjs, manager); // remember pin diff --git a/applications/system/js_app/modules/js_gui/file_picker.c b/applications/system/js_app/modules/js_gui/file_picker.c index 49cf5e89d..7b36596cd 100644 --- a/applications/system/js_app/modules/js_gui/file_picker.c +++ b/applications/system/js_app/modules/js_gui/file_picker.c @@ -3,8 +3,14 @@ #include static void js_gui_file_picker_pick_file(struct mjs* mjs) { + static const JsValueDeclaration js_picker_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), + }; + static const JsValueArguments js_picker_args = JS_VALUE_ARGS(js_picker_arg_list); + const char *base_path, *extension; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&base_path), JS_ARG_STR(&extension)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_picker_args, &base_path, &extension); DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); const DialogsFileBrowserOptions browser_options = { diff --git a/applications/system/js_app/modules/js_gui/icon.c b/applications/system/js_app/modules/js_gui/icon.c new file mode 100644 index 000000000..4fc6da2e0 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/icon.c @@ -0,0 +1,150 @@ +#include "../../js_modules.h" +#include +#include +#include +#include + +typedef struct { + const char* name; + const Icon* data; +} IconDefinition; + +#define ICON_DEF(icon) \ + (IconDefinition) { \ + .name = #icon, .data = &I_##icon \ + } + +static const IconDefinition builtin_icons[] = { + ICON_DEF(DolphinWait_59x54), + ICON_DEF(js_script_10px), +}; + +// Firmware's Icon struct needs a frames array, and uses a small CompressHeader +// Here we use a variable size allocation to add the uncompressed data in same allocation +// Also use a one-long array pointing to later in the same struct as the frames array +// CompressHeader includes a first is_compressed byte so we don't need to compress (.fxbm is uncompressed) +typedef struct FURI_PACKED { + Icon icon; + uint8_t* frames[1]; + struct { + uint8_t is_compressed; + uint8_t uncompressed_data[]; + } frame; +} FxbmIconWrapper; + +LIST_DEF(FxbmIconWrapperList, FxbmIconWrapper*, M_PTR_OPLIST); // NOLINT +#define M_OPL_FxbmIconWrapperList_t() LIST_OPLIST(FxbmIconWrapperList) + +typedef struct { + FxbmIconWrapperList_t fxbm_list; +} JsGuiIconInst; + +static const JsValueDeclaration js_icon_get_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), +}; +static const JsValueArguments js_icon_get_args = JS_VALUE_ARGS(js_icon_get_arg_list); + +static void js_gui_icon_get_builtin(struct mjs* mjs) { + const char* icon_name; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_icon_get_args, &icon_name); + + for(size_t i = 0; i < COUNT_OF(builtin_icons); i++) { + if(strcmp(icon_name, builtin_icons[i].name) == 0) { + mjs_return(mjs, mjs_mk_foreign(mjs, (void*)builtin_icons[i].data)); + return; + } + } + + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "no such built-in icon"); +} + +static void js_gui_icon_load_fxbm(struct mjs* mjs) { + const char* fxbm_path; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_icon_get_args, &fxbm_path); + + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + FxbmIconWrapper* fxbm = NULL; + + do { + if(!storage_file_open(file, fxbm_path, FSAM_READ, FSOM_OPEN_EXISTING)) { + break; + } + + struct { + uint32_t size; // Total following size including width and height values + uint32_t width; + uint32_t height; + } fxbm_header; + if(storage_file_read(file, &fxbm_header, sizeof(fxbm_header)) != sizeof(fxbm_header)) { + break; + } + + size_t frame_size = fxbm_header.size - sizeof(uint32_t) * 2; + fxbm = malloc(sizeof(FxbmIconWrapper) + frame_size); + if(storage_file_read(file, fxbm->frame.uncompressed_data, frame_size) != frame_size) { + free(fxbm); + fxbm = NULL; + break; + } + + FURI_CONST_ASSIGN(fxbm->icon.width, fxbm_header.width); + FURI_CONST_ASSIGN(fxbm->icon.height, fxbm_header.height); + FURI_CONST_ASSIGN(fxbm->icon.frame_count, 1); + FURI_CONST_ASSIGN(fxbm->icon.frame_rate, 1); + FURI_CONST_ASSIGN_PTR(fxbm->icon.frames, fxbm->frames); + fxbm->frames[0] = (void*)&fxbm->frame; + fxbm->frame.is_compressed = false; + } while(false); + + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + if(!fxbm) { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "could not load .fxbm icon"); + } + + JsGuiIconInst* js_icon = JS_GET_CONTEXT(mjs); + FxbmIconWrapperList_push_back(js_icon->fxbm_list, fxbm); + mjs_return(mjs, mjs_mk_foreign(mjs, (void*)&fxbm->icon)); +} + +static void* js_gui_icon_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + JsGuiIconInst* js_icon = malloc(sizeof(JsGuiIconInst)); + FxbmIconWrapperList_init(js_icon->fxbm_list); + *object = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, *object) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, js_icon)); + JS_FIELD("getBuiltin", MJS_MK_FN(js_gui_icon_get_builtin)); + JS_FIELD("loadFxbm", MJS_MK_FN(js_gui_icon_load_fxbm)); + } + return js_icon; +} + +static void js_gui_icon_destroy(void* inst) { + JsGuiIconInst* js_icon = inst; + for + M_EACH(fxbm, js_icon->fxbm_list, FxbmIconWrapperList_t) { + free(*fxbm); + } + FxbmIconWrapperList_clear(js_icon->fxbm_list); + free(js_icon); +} + +static const JsModuleDescriptor js_gui_icon_desc = { + "gui__icon", + js_gui_icon_create, + js_gui_icon_destroy, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_gui_icon_desc, +}; + +const FlipperAppPluginDescriptor* js_gui_icon_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c index 22d04855d..c20d980aa 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.c +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -68,8 +68,14 @@ static bool js_gui_vd_nav_callback(void* context) { * @brief `viewDispatcher.sendCustom` */ static void js_gui_vd_send_custom(struct mjs* mjs) { + static const JsValueDeclaration js_gui_vd_send_custom_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_gui_vd_send_custom_args = + JS_VALUE_ARGS(js_gui_vd_send_custom_arg_list); + int32_t event; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&event)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_send_custom_args, &event); JsGui* module = JS_GET_CONTEXT(mjs); view_dispatcher_send_custom_event(module->dispatcher, (uint32_t)event); @@ -79,15 +85,25 @@ static void js_gui_vd_send_custom(struct mjs* mjs) { * @brief `viewDispatcher.sendTo` */ static void js_gui_vd_send_to(struct mjs* mjs) { - enum { - SendDirToFront, - SendDirToBack, - } send_direction; - JS_ENUM_MAP(send_direction, {"front", SendDirToFront}, {"back", SendDirToBack}); - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ENUM(send_direction, "SendDirection")); + typedef enum { + JsSendDirToFront, + JsSendDirToBack, + } JsSendDir; + static const JsValueEnumVariant js_send_dir_variants[] = { + {"front", JsSendDirToFront}, + {"back", JsSendDirToBack}, + }; + static const JsValueDeclaration js_gui_vd_send_to_arg_list[] = { + JS_VALUE_ENUM(JsSendDir, js_send_dir_variants), + }; + static const JsValueArguments js_gui_vd_send_to_args = + JS_VALUE_ARGS(js_gui_vd_send_to_arg_list); + + JsSendDir send_direction; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_send_to_args, &send_direction); JsGui* module = JS_GET_CONTEXT(mjs); - if(send_direction == SendDirToBack) { + if(send_direction == JsSendDirToBack) { view_dispatcher_send_to_back(module->dispatcher); } else { view_dispatcher_send_to_front(module->dispatcher); @@ -98,8 +114,15 @@ static void js_gui_vd_send_to(struct mjs* mjs) { * @brief `viewDispatcher.switchTo` */ static void js_gui_vd_switch_to(struct mjs* mjs) { + static const JsValueDeclaration js_gui_vd_switch_to_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_vd_switch_to_args = + JS_VALUE_ARGS(js_gui_vd_switch_to_arg_list); + mjs_val_t view; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&view)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_switch_to_args, &view); + JsGuiViewData* view_data = JS_GET_INST(mjs, view); mjs_val_t vd_obj = mjs_get_this(mjs); JsGui* module = JS_GET_INST(mjs, vd_obj); @@ -247,19 +270,96 @@ static bool return false; } +/** + * @brief Sets the list of children. Not available from JS. + */ +static bool + js_gui_view_internal_set_children(struct mjs* mjs, mjs_val_t children, JsGuiViewData* data) { + data->descriptor->reset_children(data->specific_view, data->custom_data); + + for(size_t i = 0; i < mjs_array_length(mjs, children); i++) { + mjs_val_t child = mjs_array_get(mjs, children, i); + if(!data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child)) + return false; + } + + return true; +} + /** * @brief `View.set` */ static void js_gui_view_set(struct mjs* mjs) { + static const JsValueDeclaration js_gui_view_set_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_view_set_args = JS_VALUE_ARGS(js_gui_view_set_arg_list); + const char* name; mjs_val_t value; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&name), JS_ARG_ANY(&value)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_set_args, &name, &value); + JsGuiViewData* data = JS_GET_CONTEXT(mjs); bool success = js_gui_view_assign(mjs, name, value, data); UNUSED(success); mjs_return(mjs, MJS_UNDEFINED); } +/** + * @brief `View.addChild` + */ +static void js_gui_view_add_child(struct mjs* mjs) { + static const JsValueDeclaration js_gui_view_add_child_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_view_add_child_args = + JS_VALUE_ARGS(js_gui_view_add_child_arg_list); + + mjs_val_t child; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_add_child_args, &child); + + JsGuiViewData* data = JS_GET_CONTEXT(mjs); + if(!data->descriptor->add_child || !data->descriptor->reset_children) + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); + + bool success = data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child); + UNUSED(success); + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief `View.resetChildren` + */ +static void js_gui_view_reset_children(struct mjs* mjs) { + JsGuiViewData* data = JS_GET_CONTEXT(mjs); + if(!data->descriptor->add_child || !data->descriptor->reset_children) + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); + + data->descriptor->reset_children(data->specific_view, data->custom_data); + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief `View.setChildren` + */ +static void js_gui_view_set_children(struct mjs* mjs) { + static const JsValueDeclaration js_gui_view_set_children_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAnyArray), + }; + static const JsValueArguments js_gui_view_set_children_args = + JS_VALUE_ARGS(js_gui_view_set_children_arg_list); + + mjs_val_t children; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_set_children_args, &children); + + JsGuiViewData* data = JS_GET_CONTEXT(mjs); + if(!data->descriptor->add_child || !data->descriptor->reset_children) + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); + + js_gui_view_internal_set_children(mjs, children, data); +} + /** * @brief `View` destructor */ @@ -283,7 +383,12 @@ static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descr // generic view API mjs_val_t view_obj = mjs_mk_object(mjs); - mjs_set(mjs, view_obj, "set", ~0, MJS_MK_FN(js_gui_view_set)); + JS_ASSIGN_MULTI(mjs, view_obj) { + JS_FIELD("set", MJS_MK_FN(js_gui_view_set)); + JS_FIELD("addChild", MJS_MK_FN(js_gui_view_add_child)); + JS_FIELD("resetChildren", MJS_MK_FN(js_gui_view_reset_children)); + JS_FIELD("setChildren", MJS_MK_FN(js_gui_view_set_children)); + } // object data JsGuiViewData* data = malloc(sizeof(JsGuiViewData)); @@ -304,7 +409,6 @@ static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descr * @brief `ViewFactory.make` */ static void js_gui_vf_make(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); mjs_return(mjs, js_gui_make_view(mjs, descriptor)); } @@ -313,8 +417,15 @@ static void js_gui_vf_make(struct mjs* mjs) { * @brief `ViewFactory.makeWith` */ static void js_gui_vf_make_with(struct mjs* mjs) { - mjs_val_t props; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&props)); + static const JsValueDeclaration js_gui_vf_make_with_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAnyObject), + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_vf_make_with_args = + JS_VALUE_ARGS(js_gui_vf_make_with_arg_list); + + mjs_val_t props, children; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vf_make_with_args, &props, &children); const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); // make the object like normal @@ -334,6 +445,14 @@ static void js_gui_vf_make_with(struct mjs* mjs) { } } + // assign children + if(mjs_is_array(children)) { + if(!data->descriptor->add_child || !data->descriptor->reset_children) + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); + + if(!js_gui_view_internal_set_children(mjs, children, data)) return; + } + mjs_return(mjs, view_obj); } diff --git a/applications/system/js_app/modules/js_gui/js_gui.h b/applications/system/js_app/modules/js_gui/js_gui.h index d400d0a33..d9d98df39 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.h +++ b/applications/system/js_app/modules/js_gui/js_gui.h @@ -50,6 +50,11 @@ typedef void (*JsViewFree)(void* specific_view); typedef void* (*JsViewCustomMake)(struct mjs* mjs, void* specific_view, mjs_val_t view_obj); /** @brief Context destruction for glue code */ typedef void (*JsViewCustomDestroy)(void* specific_view, void* custom_state, FuriEventLoop* loop); +/** @brief `addChild` callback for glue code */ +typedef bool ( + *JsViewAddChild)(struct mjs* mjs, void* specific_view, void* custom_state, mjs_val_t child_obj); +/** @brief `resetChildren` callback for glue code */ +typedef void (*JsViewResetChildren)(void* specific_view, void* custom_state); /** * @brief Descriptor for a JS view @@ -66,15 +71,22 @@ typedef struct { JsViewAlloc alloc; JsViewGetView get_view; JsViewFree free; + JsViewCustomMake custom_make; // get_view -> [custom_make (if set)] -> props[i].assign -> [custom_destroy (if_set)] -> free -// \_______________ creation ________________/ \___ usage ___/ \_________ destruction _________/ +// +-> add_child -+ +// +-> reset_children -+ +// alloc -> get_view -> custom_make -+-> props[i].assign -+> custom_destroy -> free +// \__________ creation __________/ \____ use ____/ \___ destruction ____/ /** * @brief Creates a JS `ViewFactory` object diff --git a/applications/system/js_app/modules/js_gui/widget.c b/applications/system/js_app/modules/js_gui/widget.c new file mode 100644 index 000000000..bb2898030 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/widget.c @@ -0,0 +1,317 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +typedef struct { + FuriMessageQueue* queue; + JsEventLoopContract contract; +} JsWidgetCtx; + +#define QUEUE_LEN 2 + +/** + * @brief Parses position (X and Y) from an element declaration object + */ +static bool element_get_position(struct mjs* mjs, mjs_val_t element, int32_t* x, int32_t* y) { + mjs_val_t x_in = mjs_get(mjs, element, "x", ~0); + mjs_val_t y_in = mjs_get(mjs, element, "y", ~0); + if(!mjs_is_number(x_in) || !mjs_is_number(y_in)) return false; + *x = mjs_get_int32(mjs, x_in); + *y = mjs_get_int32(mjs, y_in); + return true; +} + +/** + * @brief Parses size (W and h) from an element declaration object + */ +static bool element_get_size(struct mjs* mjs, mjs_val_t element, int32_t* w, int32_t* h) { + mjs_val_t w_in = mjs_get(mjs, element, "w", ~0); + mjs_val_t h_in = mjs_get(mjs, element, "h", ~0); + if(!mjs_is_number(w_in) || !mjs_is_number(h_in)) return false; + *w = mjs_get_int32(mjs, w_in); + *h = mjs_get_int32(mjs, h_in); + return true; +} + +/** + * @brief Parses alignment (V and H) from an element declaration object + */ +static bool + element_get_alignment(struct mjs* mjs, mjs_val_t element, Align* align_v, Align* align_h) { + mjs_val_t align_in = mjs_get(mjs, element, "align", ~0); + const char* align = mjs_get_string(mjs, &align_in, NULL); + if(!align) return false; + if(strlen(align) != 2) return false; + + if(align[0] == 't') { + *align_v = AlignTop; + } else if(align[0] == 'c') { + *align_v = AlignCenter; + } else if(align[0] == 'b') { + *align_v = AlignBottom; + } else { + return false; + } + + if(align[1] == 'l') { + *align_h = AlignLeft; + } else if(align[1] == 'm') { // m = middle + *align_h = AlignCenter; + } else if(align[1] == 'r') { + *align_h = AlignRight; + } else { + return false; + } + + return true; +} + +/** + * @brief Parses font from an element declaration object + */ +static bool element_get_font(struct mjs* mjs, mjs_val_t element, Font* font) { + mjs_val_t font_in = mjs_get(mjs, element, "font", ~0); + const char* font_str = mjs_get_string(mjs, &font_in, NULL); + if(!font_str) return false; + + if(strcmp(font_str, "primary") == 0) { + *font = FontPrimary; + } else if(strcmp(font_str, "secondary") == 0) { + *font = FontSecondary; + } else if(strcmp(font_str, "keyboard") == 0) { + *font = FontKeyboard; + } else if(strcmp(font_str, "big_numbers") == 0) { + *font = FontBigNumbers; + } else { + return false; + } + return true; +} + +/** + * @brief Parses text from an element declaration object + */ +static bool element_get_text(struct mjs* mjs, mjs_val_t element, mjs_val_t* text) { + *text = mjs_get(mjs, element, "text", ~0); + return mjs_is_string(*text); +} + +/** + * @brief Widget button element callback + */ +static void js_widget_button_callback(GuiButtonType result, InputType type, JsWidgetCtx* context) { + UNUSED(type); + furi_check(furi_message_queue_put(context->queue, &result, 0) == FuriStatusOk); +} + +#define DESTRUCTURE_OR_RETURN(mjs, child_obj, part, ...) \ + if(!element_get_##part(mjs, child_obj, __VA_ARGS__)) \ + JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element " #part); + +static bool js_widget_add_child( + struct mjs* mjs, + Widget* widget, + JsWidgetCtx* context, + mjs_val_t child_obj) { + UNUSED(context); + if(!mjs_is_object(child_obj)) + JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "child must be an object"); + + mjs_val_t element_type_term = mjs_get(mjs, child_obj, "element", ~0); + const char* element_type = mjs_get_string(mjs, &element_type_term, NULL); + if(!element_type) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "child object must have `element` property"); + + if((strcmp(element_type, "string") == 0) || (strcmp(element_type, "string_multiline") == 0)) { + int32_t x, y; + Align align_v, align_h; + Font font; + mjs_val_t text; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + DESTRUCTURE_OR_RETURN(mjs, child_obj, alignment, &align_v, &align_h); + DESTRUCTURE_OR_RETURN(mjs, child_obj, font, &font); + DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text); + if(strcmp(element_type, "string") == 0) { + widget_add_string_element( + widget, x, y, align_h, align_v, font, mjs_get_string(mjs, &text, NULL)); + } else { + widget_add_string_multiline_element( + widget, x, y, align_h, align_v, font, mjs_get_string(mjs, &text, NULL)); + } + + } else if(strcmp(element_type, "text_box") == 0) { + int32_t x, y, w, h; + Align align_v, align_h; + Font font; + mjs_val_t text; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h); + DESTRUCTURE_OR_RETURN(mjs, child_obj, alignment, &align_v, &align_h); + DESTRUCTURE_OR_RETURN(mjs, child_obj, font, &font); + DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text); + mjs_val_t strip_to_dots_in = mjs_get(mjs, child_obj, "stripToDots", ~0); + if(!mjs_is_boolean(strip_to_dots_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element stripToDots"); + bool strip_to_dots = mjs_get_bool(mjs, strip_to_dots_in); + widget_add_text_box_element( + widget, x, y, w, h, align_h, align_v, mjs_get_string(mjs, &text, NULL), strip_to_dots); + + } else if(strcmp(element_type, "text_scroll") == 0) { + int32_t x, y, w, h; + mjs_val_t text; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h); + DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text); + widget_add_text_scroll_element(widget, x, y, w, h, mjs_get_string(mjs, &text, NULL)); + + } else if(strcmp(element_type, "button") == 0) { + mjs_val_t btn_in = mjs_get(mjs, child_obj, "button", ~0); + const char* btn_name = mjs_get_string(mjs, &btn_in, NULL); + if(!btn_name) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element button"); + GuiButtonType btn_type; + if(strcmp(btn_name, "left") == 0) { + btn_type = GuiButtonTypeLeft; + } else if(strcmp(btn_name, "center") == 0) { + btn_type = GuiButtonTypeCenter; + } else if(strcmp(btn_name, "right") == 0) { + btn_type = GuiButtonTypeRight; + } else { + JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "incorrect button type"); + } + mjs_val_t text; + DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text); + widget_add_button_element( + widget, + btn_type, + mjs_get_string(mjs, &text, NULL), + (ButtonCallback)js_widget_button_callback, + context); + + } else if(strcmp(element_type, "icon") == 0) { + int32_t x, y; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + mjs_val_t icon_data_in = mjs_get(mjs, child_obj, "iconData", ~0); + if(!mjs_is_foreign(icon_data_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element iconData"); + const Icon* icon = mjs_get_ptr(mjs, icon_data_in); + widget_add_icon_element(widget, x, y, icon); + + } else if(strcmp(element_type, "rect") == 0) { + int32_t x, y, w, h; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h); + mjs_val_t radius_in = mjs_get(mjs, child_obj, "radius", ~0); + if(!mjs_is_number(radius_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element radius"); + int32_t radius = mjs_get_int32(mjs, radius_in); + mjs_val_t fill_in = mjs_get(mjs, child_obj, "fill", ~0); + if(!mjs_is_boolean(fill_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element fill"); + int32_t fill = mjs_get_bool(mjs, fill_in); + widget_add_rect_element(widget, x, y, w, h, radius, fill); + + } else if(strcmp(element_type, "circle") == 0) { + int32_t x, y; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + mjs_val_t radius_in = mjs_get(mjs, child_obj, "radius", ~0); + if(!mjs_is_number(radius_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element radius"); + int32_t radius = mjs_get_int32(mjs, radius_in); + mjs_val_t fill_in = mjs_get(mjs, child_obj, "fill", ~0); + if(!mjs_is_boolean(fill_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element fill"); + int32_t fill = mjs_get_bool(mjs, fill_in); + widget_add_circle_element(widget, x, y, radius, fill); + + } else if(strcmp(element_type, "line") == 0) { + int32_t x1, y1, x2, y2; + mjs_val_t x1_in = mjs_get(mjs, child_obj, "x1", ~0); + mjs_val_t y1_in = mjs_get(mjs, child_obj, "y1", ~0); + mjs_val_t x2_in = mjs_get(mjs, child_obj, "x2", ~0); + mjs_val_t y2_in = mjs_get(mjs, child_obj, "y2", ~0); + if(!mjs_is_number(x1_in) || !mjs_is_number(y1_in) || !mjs_is_number(x2_in) || + !mjs_is_number(y2_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element positions"); + x1 = mjs_get_int32(mjs, x1_in); + y1 = mjs_get_int32(mjs, y1_in); + x2 = mjs_get_int32(mjs, x2_in); + y2 = mjs_get_int32(mjs, y2_in); + widget_add_line_element(widget, x1, y1, x2, y2); + } + + return true; +} + +static void js_widget_reset_children(Widget* widget, void* state) { + UNUSED(state); + widget_reset(widget); +} + +static mjs_val_t js_widget_button_event_transformer( + struct mjs* mjs, + FuriMessageQueue* queue, + JsWidgetCtx* context) { + UNUSED(context); + GuiButtonType btn_type; + furi_check(furi_message_queue_get(queue, &btn_type, 0) == FuriStatusOk); + const char* btn_name; + if(btn_type == GuiButtonTypeLeft) { + btn_name = "left"; + } else if(btn_type == GuiButtonTypeCenter) { + btn_name = "center"; + } else if(btn_type == GuiButtonTypeRight) { + btn_name = "right"; + } else { + furi_crash(); + } + return mjs_mk_string(mjs, btn_name, ~0, false); +} + +static void* js_widget_custom_make(struct mjs* mjs, Widget* widget, mjs_val_t view_obj) { + UNUSED(widget); + JsWidgetCtx* context = malloc(sizeof(JsWidgetCtx)); + context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(GuiButtonType)); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)js_widget_button_event_transformer, + }, + }; + mjs_set(mjs, view_obj, "button", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void js_widget_custom_destroy(Widget* widget, JsWidgetCtx* context, FuriEventLoop* loop) { + UNUSED(widget); + furi_event_loop_maybe_unsubscribe(loop, context->queue); + furi_message_queue_free(context->queue); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)widget_alloc, + .free = (JsViewFree)widget_free, + .get_view = (JsViewGetView)widget_get_view, + .custom_make = (JsViewCustomMake)js_widget_custom_make, + .custom_destroy = (JsViewCustomDestroy)js_widget_custom_destroy, + .add_child = (JsViewAddChild)js_widget_add_child, + .reset_children = (JsViewResetChildren)js_widget_reset_children, + .prop_cnt = 0, + .props = {}, +}; +JS_GUI_VIEW_DEF(widget, &view_descriptor); diff --git a/applications/system/js_app/modules/js_serial.c b/applications/system/js_app/modules/js_serial.c index b1e578fbc..d903939ce 100644 --- a/applications/system/js_app/modules/js_serial.c +++ b/applications/system/js_app/modules/js_serial.c @@ -20,14 +20,6 @@ typedef struct { char* data; } PatternArrayItem; -static const struct { - const char* name; - const FuriHalSerialId value; -} serial_channels[] = { - {"usart", FuriHalSerialIdUsart}, - {"lpuart", FuriHalSerialIdLpuart}, -}; - ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST); static void @@ -43,52 +35,62 @@ static void } static void js_serial_setup(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); - furi_assert(serial); + static const JsValueEnumVariant js_serial_id_variants[] = { + {"lpuart", FuriHalSerialIdLpuart}, + {"usart", FuriHalSerialIdUsart}, + }; - if(serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is already configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + static const JsValueEnumVariant js_serial_data_bit_variants[] = { + {"6", FuriHalSerialDataBits6}, + {"7", FuriHalSerialDataBits7}, + {"8", FuriHalSerialDataBits8}, + {"9", FuriHalSerialDataBits9}, + }; + static const JsValueDeclaration js_serial_data_bits = JS_VALUE_ENUM_W_DEFAULT( + FuriHalSerialDataBits, js_serial_data_bit_variants, FuriHalSerialDataBits8); - bool args_correct = false; - FuriHalSerialId serial_id = FuriHalSerialIdMax; - uint32_t baudrate = 0; + static const JsValueEnumVariant js_serial_parity_variants[] = { + {"none", FuriHalSerialParityNone}, + {"even", FuriHalSerialParityEven}, + {"odd", FuriHalSerialParityOdd}, + }; + static const JsValueDeclaration js_serial_parity = JS_VALUE_ENUM_W_DEFAULT( + FuriHalSerialParity, js_serial_parity_variants, FuriHalSerialParityNone); - do { - if(mjs_nargs(mjs) != 2) break; + static const JsValueEnumVariant js_serial_stop_bit_variants[] = { + {"0.5", FuriHalSerialStopBits0_5}, + {"1", FuriHalSerialStopBits1}, + {"1.5", FuriHalSerialStopBits1_5}, + {"2", FuriHalSerialStopBits2}, + }; + static const JsValueDeclaration js_serial_stop_bits = JS_VALUE_ENUM_W_DEFAULT( + FuriHalSerialStopBits, js_serial_stop_bit_variants, FuriHalSerialStopBits1); - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_string(arg)) break; + static const JsValueObjectField js_serial_framing_fields[] = { + {"dataBits", &js_serial_data_bits}, + {"parity", &js_serial_parity}, + {"stopBits", &js_serial_stop_bits}, + }; - size_t str_len = 0; - const char* arg_str = mjs_get_string(mjs, &arg, &str_len); - for(size_t i = 0; i < COUNT_OF(serial_channels); i++) { - size_t name_len = strlen(serial_channels[i].name); - if(str_len != name_len) continue; - if(strncmp(arg_str, serial_channels[i].name, str_len) == 0) { - serial_id = serial_channels[i].value; - break; - } - } - if(serial_id == FuriHalSerialIdMax) { - break; - } + static const JsValueDeclaration js_serial_setup_arg_list[] = { + JS_VALUE_ENUM(FuriHalSerialId, js_serial_id_variants), + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_OBJECT_W_DEFAULTS(js_serial_framing_fields), + }; + static const JsValueArguments js_serial_setup_args = JS_VALUE_ARGS(js_serial_setup_arg_list); - arg = mjs_arg(mjs, 1); - if(!mjs_is_number(arg)) break; - baudrate = mjs_get_int32(mjs, arg); + FuriHalSerialId serial_id; + int32_t baudrate; + FuriHalSerialDataBits data_bits = FuriHalSerialDataBits8; + FuriHalSerialParity parity = FuriHalSerialParityNone; + FuriHalSerialStopBits stop_bits = FuriHalSerialStopBits1; + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_serial_setup_args, &serial_id, &baudrate, &data_bits, &parity, &stop_bits); - args_correct = true; - } while(0); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); - if(!args_correct) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is already configured"); expansion_disable(furi_record_open(RECORD_EXPANSION)); furi_record_close(RECORD_EXPANSION); @@ -97,6 +99,7 @@ static void js_serial_setup(struct mjs* mjs) { if(serial->serial_handle) { serial->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1); furi_hal_serial_init(serial->serial_handle, baudrate); + furi_hal_serial_configure_framing(serial->serial_handle, data_bits, parity, stop_bits); furi_hal_serial_async_rx_start( serial->serial_handle, js_serial_on_async_rx, serial, false); serial->setup_done = true; @@ -122,28 +125,20 @@ static void js_serial_deinit(JsSerialInst* js_serial) { } static void js_serial_end(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); js_serial_deinit(serial); } static void js_serial_write(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); bool args_correct = true; @@ -227,43 +222,20 @@ static size_t js_serial_receive(JsSerialInst* serial, char* buf, size_t len, uin return bytes_read; } +static const JsValueDeclaration js_serial_read_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, INT32_MAX), +}; +static const JsValueArguments js_serial_read_args = JS_VALUE_ARGS(js_serial_read_arg_list); + static void js_serial_read(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - size_t read_len = 0; - uint32_t timeout = FuriWaitForever; - - do { - size_t num_args = mjs_nargs(mjs); - if(num_args == 1) { - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_number(arg)) { - break; - } - read_len = mjs_get_int32(mjs, arg); - } else if(num_args == 2) { - mjs_val_t len_arg = mjs_arg(mjs, 0); - mjs_val_t timeout_arg = mjs_arg(mjs, 1); - if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) { - break; - } - read_len = mjs_get_int32(mjs, len_arg); - timeout = mjs_get_int32(mjs, timeout_arg); - } - } while(0); - - if(read_len == 0) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + int32_t read_len, timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_args, &read_len, &timeout); char* read_buf = malloc(read_len); size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout); @@ -277,37 +249,19 @@ static void js_serial_read(struct mjs* mjs) { } static void js_serial_readln(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - bool args_correct = false; - uint32_t timeout = FuriWaitForever; + static const JsValueDeclaration js_serial_readln_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_serial_readln_args = JS_VALUE_ARGS(js_serial_readln_arg_list); - do { - size_t num_args = mjs_nargs(mjs); - if(num_args > 1) { - break; - } else if(num_args == 1) { - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_number(arg)) { - break; - } - timeout = mjs_get_int32(mjs, arg); - } - args_correct = true; - } while(0); + int32_t timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_readln_args, &timeout); - if(!args_correct) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } FuriString* rx_buf = furi_string_alloc(); size_t bytes_read = 0; char read_char = 0; @@ -334,42 +288,13 @@ static void js_serial_readln(struct mjs* mjs) { } static void js_serial_read_bytes(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - size_t read_len = 0; - uint32_t timeout = FuriWaitForever; - - do { - size_t num_args = mjs_nargs(mjs); - if(num_args == 1) { - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_number(arg)) { - break; - } - read_len = mjs_get_int32(mjs, arg); - } else if(num_args == 2) { - mjs_val_t len_arg = mjs_arg(mjs, 0); - mjs_val_t timeout_arg = mjs_arg(mjs, 1); - if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) { - break; - } - read_len = mjs_get_int32(mjs, len_arg); - timeout = mjs_get_int32(mjs, timeout_arg); - } - } while(0); - - if(read_len == 0) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + int32_t read_len, timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_args, &read_len, &timeout); char* read_buf = malloc(read_len); size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout); @@ -398,27 +323,19 @@ static char* js_serial_receive_any(JsSerialInst* serial, size_t* len, uint32_t t } static void js_serial_read_any(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - uint32_t timeout = FuriWaitForever; + static const JsValueDeclaration js_serial_read_any_arg_list[] = { + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, INT32_MAX), + }; + static const JsValueArguments js_serial_read_any_args = + JS_VALUE_ARGS(js_serial_read_any_arg_list); - do { - size_t num_args = mjs_nargs(mjs); - if(num_args == 1) { - mjs_val_t timeout_arg = mjs_arg(mjs, 0); - if(!mjs_is_number(timeout_arg)) { - break; - } - timeout = mjs_get_int32(mjs, timeout_arg); - } - } while(0); + int32_t timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_any_args, &timeout); size_t bytes_read = 0; char* read_buf = js_serial_receive_any(serial, &bytes_read, timeout); @@ -662,16 +579,19 @@ static void* js_serial_create(struct mjs* mjs, mjs_val_t* object, JsModules* mod UNUSED(modules); JsSerialInst* js_serial = malloc(sizeof(JsSerialInst)); js_serial->mjs = mjs; + mjs_val_t serial_obj = mjs_mk_object(mjs); - mjs_set(mjs, serial_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_serial)); - mjs_set(mjs, serial_obj, "setup", ~0, MJS_MK_FN(js_serial_setup)); - mjs_set(mjs, serial_obj, "end", ~0, MJS_MK_FN(js_serial_end)); - mjs_set(mjs, serial_obj, "write", ~0, MJS_MK_FN(js_serial_write)); - mjs_set(mjs, serial_obj, "read", ~0, MJS_MK_FN(js_serial_read)); - mjs_set(mjs, serial_obj, "readln", ~0, MJS_MK_FN(js_serial_readln)); - mjs_set(mjs, serial_obj, "readBytes", ~0, MJS_MK_FN(js_serial_read_bytes)); - mjs_set(mjs, serial_obj, "readAny", ~0, MJS_MK_FN(js_serial_read_any)); - mjs_set(mjs, serial_obj, "expect", ~0, MJS_MK_FN(js_serial_expect)); + JS_ASSIGN_MULTI(mjs, serial_obj) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, js_serial)); + JS_FIELD("setup", MJS_MK_FN(js_serial_setup)); + JS_FIELD("end", MJS_MK_FN(js_serial_end)); + JS_FIELD("write", MJS_MK_FN(js_serial_write)); + JS_FIELD("read", MJS_MK_FN(js_serial_read)); + JS_FIELD("readln", MJS_MK_FN(js_serial_readln)); + JS_FIELD("readBytes", MJS_MK_FN(js_serial_read_bytes)); + JS_FIELD("readAny", MJS_MK_FN(js_serial_read_any)); + JS_FIELD("expect", MJS_MK_FN(js_serial_expect)); + } *object = serial_obj; return js_serial; diff --git a/applications/system/js_app/modules/js_storage.c b/applications/system/js_app/modules/js_storage.c index 1d4053a5f..66d002f33 100644 --- a/applications/system/js_app/modules/js_storage.c +++ b/applications/system/js_app/modules/js_storage.c @@ -1,42 +1,79 @@ #include "../js_modules.h" // IWYU pragma: keep #include -// ---=== file ops ===--- +// ========================== +// Common argument signatures +// ========================== + +static const JsValueDeclaration js_storage_1_int_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), +}; +static const JsValueArguments js_storage_1_int_args = JS_VALUE_ARGS(js_storage_1_int_arg_list); + +static const JsValueDeclaration js_storage_1_str_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), +}; +static const JsValueArguments js_storage_1_str_args = JS_VALUE_ARGS(js_storage_1_str_arg_list); + +static const JsValueDeclaration js_storage_2_str_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), +}; +static const JsValueArguments js_storage_2_str_args = JS_VALUE_ARGS(js_storage_2_str_arg_list); + +// ====================== +// File object operations +// ====================== static void js_storage_file_close(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_close(file))); } static void js_storage_file_is_open(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_is_open(file))); } static void js_storage_file_read(struct mjs* mjs) { - enum { - ReadModeAscii, - ReadModeBinary, - } read_mode; - JS_ENUM_MAP(read_mode, {"ascii", ReadModeAscii}, {"binary", ReadModeBinary}); + typedef enum { + JsStorageReadModeAscii, + JsStorageReadModeBinary, + } JsStorageReadMode; + static const JsValueEnumVariant js_storage_read_mode_variants[] = { + {"ascii", JsStorageReadModeAscii}, + {"binary", JsStorageReadModeBinary}, + }; + static const JsValueDeclaration js_storage_read_arg_list[] = { + JS_VALUE_ENUM(JsStorageReadMode, js_storage_read_mode_variants), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_storage_read_args = JS_VALUE_ARGS(js_storage_read_arg_list); + + JsStorageReadMode read_mode; int32_t length; - JS_FETCH_ARGS_OR_RETURN( - mjs, JS_EXACTLY, JS_ARG_ENUM(read_mode, "ReadMode"), JS_ARG_INT32(&length)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_read_args, &read_mode, &length); + File* file = JS_GET_CONTEXT(mjs); char buffer[length]; size_t actually_read = storage_file_read(file, buffer, length); - if(read_mode == ReadModeAscii) { + if(read_mode == JsStorageReadModeAscii) { mjs_return(mjs, mjs_mk_string(mjs, buffer, actually_read, true)); - } else if(read_mode == ReadModeBinary) { + } else if(read_mode == JsStorageReadModeBinary) { mjs_return(mjs, mjs_mk_array_buf(mjs, buffer, actually_read)); } } static void js_storage_file_write(struct mjs* mjs) { + static const JsValueDeclaration js_storage_file_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_storage_file_write_args = + JS_VALUE_ARGS(js_storage_file_write_arg_list); + mjs_val_t data; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&data)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_file_write_args, &data); + const void* buf; size_t len; if(mjs_is_string(data)) { @@ -52,52 +89,58 @@ static void js_storage_file_write(struct mjs* mjs) { static void js_storage_file_seek_relative(struct mjs* mjs) { int32_t offset; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_int_args, &offset); File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, false))); } static void js_storage_file_seek_absolute(struct mjs* mjs) { int32_t offset; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_int_args, &offset); File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, true))); } static void js_storage_file_tell(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_number(mjs, storage_file_tell(file))); } static void js_storage_file_truncate(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_truncate(file))); } static void js_storage_file_size(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_number(mjs, storage_file_size(file))); } static void js_storage_file_eof(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_eof(file))); } static void js_storage_file_copy_to(struct mjs* mjs) { - File* source = JS_GET_CONTEXT(mjs); + static const JsValueDeclaration js_storage_file_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_storage_file_write_args = + JS_VALUE_ARGS(js_storage_file_write_arg_list); + mjs_val_t dest_obj; int32_t bytes; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&dest_obj), JS_ARG_INT32(&bytes)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_file_write_args, &dest_obj, &bytes); + + File* source = JS_GET_CONTEXT(mjs); File* destination = JS_GET_INST(mjs, dest_obj); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_copy_to_file(source, destination, bytes))); } -// ---=== top-level file ops ===--- +// ========================= +// Top-level file operations +// ========================= // common destructor for file and dir objects static void js_storage_file_destructor(struct mjs* mjs, mjs_val_t obj) { @@ -106,23 +149,33 @@ static void js_storage_file_destructor(struct mjs* mjs, mjs_val_t obj) { } static void js_storage_open_file(struct mjs* mjs) { - const char* path; - FS_AccessMode access_mode; - FS_OpenMode open_mode; - JS_ENUM_MAP(access_mode, {"r", FSAM_READ}, {"w", FSAM_WRITE}, {"rw", FSAM_READ_WRITE}); - JS_ENUM_MAP( - open_mode, + static const JsValueEnumVariant js_storage_fsam_variants[] = { + {"r", FSAM_READ}, + {"w", FSAM_WRITE}, + {"rw", FSAM_READ_WRITE}, + }; + + static const JsValueEnumVariant js_storage_fsom_variants[] = { {"open_existing", FSOM_OPEN_EXISTING}, {"open_always", FSOM_OPEN_ALWAYS}, {"open_append", FSOM_OPEN_APPEND}, {"create_new", FSOM_CREATE_NEW}, - {"create_always", FSOM_CREATE_ALWAYS}); - JS_FETCH_ARGS_OR_RETURN( - mjs, - JS_EXACTLY, - JS_ARG_STR(&path), - JS_ARG_ENUM(access_mode, "AccessMode"), - JS_ARG_ENUM(open_mode, "OpenMode")); + {"create_always", FSOM_CREATE_ALWAYS}, + }; + + static const JsValueDeclaration js_storage_open_file_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_ENUM(FS_AccessMode, js_storage_fsam_variants), + JS_VALUE_ENUM(FS_OpenMode, js_storage_fsom_variants), + }; + static const JsValueArguments js_storage_open_file_args = + JS_VALUE_ARGS(js_storage_open_file_arg_list); + + const char* path; + FS_AccessMode access_mode; + FS_OpenMode open_mode; + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_storage_open_file_args, &path, &access_mode, &open_mode); Storage* storage = JS_GET_CONTEXT(mjs); File* file = storage_file_alloc(storage); @@ -152,16 +205,18 @@ static void js_storage_open_file(struct mjs* mjs) { static void js_storage_file_exists(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_exists(storage, path))); } -// ---=== dir ops ===--- +// ==================== +// Directory operations +// ==================== static void js_storage_read_directory(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); File* dir = storage_file_alloc(storage); @@ -200,30 +255,32 @@ static void js_storage_read_directory(struct mjs* mjs) { static void js_storage_directory_exists(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_dir_exists(storage, path))); } static void js_storage_make_directory(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_mkdir(storage, path))); } -// ---=== common ops ===--- +// ================= +// Common operations +// ================= static void js_storage_file_or_dir_exists(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_exists(storage, path))); } static void js_storage_stat(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); FileInfo file_info; uint32_t timestamp; @@ -244,21 +301,21 @@ static void js_storage_stat(struct mjs* mjs) { static void js_storage_remove(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove(storage, path))); } static void js_storage_rmrf(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove_recursive(storage, path))); } static void js_storage_rename(struct mjs* mjs) { const char *old, *new; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&old), JS_ARG_STR(&new)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &old, &new); Storage* storage = JS_GET_CONTEXT(mjs); FS_Error status = storage_common_rename(storage, old, new); mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK)); @@ -266,7 +323,7 @@ static void js_storage_rename(struct mjs* mjs) { static void js_storage_copy(struct mjs* mjs) { const char *source, *dest; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&source), JS_ARG_STR(&dest)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &source, &dest); Storage* storage = JS_GET_CONTEXT(mjs); FS_Error status = storage_common_copy(storage, source, dest); mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK || status == FSE_EXIST)); @@ -274,7 +331,7 @@ static void js_storage_copy(struct mjs* mjs) { static void js_storage_fs_info(struct mjs* mjs) { const char* fs; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&fs)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &fs); Storage* storage = JS_GET_CONTEXT(mjs); uint64_t total_space, free_space; if(storage_common_fs_info(storage, fs, &total_space, &free_space) != FSE_OK) { @@ -290,15 +347,19 @@ static void js_storage_fs_info(struct mjs* mjs) { } static void js_storage_next_available_filename(struct mjs* mjs) { + static const JsValueDeclaration js_storage_naf_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_storage_naf_args = JS_VALUE_ARGS(js_storage_naf_arg_list); + const char *dir_path, *file_name, *file_ext; int32_t max_len; - JS_FETCH_ARGS_OR_RETURN( - mjs, - JS_EXACTLY, - JS_ARG_STR(&dir_path), - JS_ARG_STR(&file_name), - JS_ARG_STR(&file_ext), - JS_ARG_INT32(&max_len)); + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_storage_naf_args, &dir_path, &file_name, &file_ext, &max_len); + Storage* storage = JS_GET_CONTEXT(mjs); FuriString* next_name = furi_string_alloc(); storage_get_next_filename(storage, dir_path, file_name, file_ext, next_name, max_len); @@ -306,23 +367,27 @@ static void js_storage_next_available_filename(struct mjs* mjs) { furi_string_free(next_name); } -// ---=== path ops ===--- +// =============== +// Path operations +// =============== static void js_storage_are_paths_equal(struct mjs* mjs) { const char *path1, *path2; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path1), JS_ARG_STR(&path2)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &path1, &path2); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_equivalent_path(storage, path1, path2))); } static void js_storage_is_subpath_of(struct mjs* mjs) { const char *parent, *child; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&parent), JS_ARG_STR(&child)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &parent, &child); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_is_subdir(storage, parent, child))); } -// ---=== module ctor & dtor ===--- +// ================== +// Module ctor & dtor +// ================== static void* js_storage_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { UNUSED(modules); @@ -363,7 +428,9 @@ static void js_storage_destroy(void* data) { furi_record_close(RECORD_STORAGE); } -// ---=== boilerplate ===--- +// =========== +// Boilerplate +// =========== static const JsModuleDescriptor js_storage_desc = { "storage", diff --git a/applications/system/js_app/modules/js_widget.c b/applications/system/js_app/modules/js_widget.c deleted file mode 100644 index 1c0e98b7c..000000000 --- a/applications/system/js_app/modules/js_widget.c +++ /dev/null @@ -1,922 +0,0 @@ -#include -#include -#include -#include -#include -#include "../js_modules.h" - -typedef struct WidgetComponent WidgetComponent; -ARRAY_DEF(ComponentArray, WidgetComponent*, M_PTR_OPLIST); - -typedef struct XbmImage XbmImage; -LIST_DEF(XbmImageList, XbmImage*, M_POD_OPLIST); - -struct WidgetComponent { - void (*draw)(Canvas* canvas, void* model); - void (*free)(WidgetComponent* component); - void* model; - uint32_t id; -}; - -struct XbmImage { - uint32_t width; - uint32_t height; - uint8_t data[]; -}; - -typedef struct { - uint8_t x; - uint8_t y; - uint8_t w; - uint8_t h; -} BoxElement; - -typedef struct { - uint8_t x; - uint8_t y; - uint8_t r; -} CircleElement; - -typedef struct { - uint8_t x; - uint8_t y; - uint8_t r; -} DiscElement; - -typedef struct { - uint8_t x; - uint8_t y; -} DotElement; - -typedef struct { - uint8_t x; - uint8_t y; - const Icon* icon; -} IconElement; - -typedef struct { - uint8_t x; - uint8_t y; - uint8_t w; - uint8_t h; -} FrameElement; - -typedef struct { - uint8_t x; - uint8_t y; - uint16_t ch; -} GlyphElement; - -typedef struct { - uint8_t x1; - uint8_t y1; - uint8_t x2; - uint8_t y2; -} LineElement; - -typedef struct { - uint8_t x; - uint8_t y; - uint8_t w; - uint8_t h; - uint8_t r; -} RboxElement; - -typedef struct { - uint8_t x; - uint8_t y; - uint8_t w; - uint8_t h; - uint8_t r; -} RframeElement; - -typedef struct { - uint8_t x; - uint8_t y; - Font font; - FuriString* text; -} TextElement; - -typedef struct { - uint8_t x; - uint8_t y; - uint32_t index; - View* view; -} XbmElement; - -typedef struct { - ComponentArray_t component; - XbmImageList_t image; - uint32_t max_assigned_id; -} WidgetModel; - -typedef struct { - View* view; - ViewHolder* view_holder; - bool is_shown; -} JsWidgetInst; - -static JsWidgetInst* get_this_ctx(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsWidgetInst* widget = mjs_get_ptr(mjs, obj_inst); - furi_assert(widget); - return widget; -} - -static void ret_bad_args(struct mjs* mjs, const char* error) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, MJS_UNDEFINED); -} - -static bool check_arg_count(struct mjs* mjs, size_t count) { - size_t num_args = mjs_nargs(mjs); - if(num_args != count) { - ret_bad_args(mjs, "Wrong argument count"); - return false; - } - return true; -} - -static void js_widget_load_image_xbm(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) { - return; - } - - mjs_val_t path_arg = mjs_arg(mjs, 0); - size_t path_len = 0; - const char* path = mjs_get_string(mjs, &path_arg, &path_len); - if(!path) { - ret_bad_args(mjs, "Path must be a string"); - return; - } - - Storage* storage = furi_record_open(RECORD_STORAGE); - File* file = storage_file_alloc(storage); - XbmImage* xbm = NULL; - - do { - if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { - ret_bad_args(mjs, "Failed to open file"); - break; - } - - uint32_t size = 0; - if(storage_file_read(file, &size, sizeof(size)) != sizeof(size)) { - ret_bad_args(mjs, "Failed to get file size"); - break; - } - - xbm = malloc(size); - if(storage_file_read(file, xbm, size) != size) { - ret_bad_args(mjs, "Failed to load entire file"); - free(xbm); - xbm = NULL; - break; - } - } while(false); - - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - - if(xbm == NULL) { - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - uint32_t count = 0; - with_view_model( - widget->view, - WidgetModel * model, - { - count = XbmImageList_size(model->image); - XbmImageList_push_back(model->image, xbm); - }, - false); - - mjs_return(mjs, mjs_mk_number(mjs, count)); -} - -static void js_widget_remove(struct mjs* mjs) { - bool removed = false; - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) { - return; - } - - with_view_model( - widget->view, - WidgetModel * model, - { - uint32_t id = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - ComponentArray_it_t it; - ComponentArray_it(it, model->component); - while(!ComponentArray_end_p(it)) { - WidgetComponent* component = *ComponentArray_ref(it); - if(component->id == id) { - if(component->free) { - component->free(component); - } - ComponentArray_remove(model->component, it); - removed = true; - break; - } - ComponentArray_next(it); - } - }, - true); - - mjs_return(mjs, mjs_mk_boolean(mjs, removed)); -} - -static void widget_box_draw(Canvas* canvas, void* model) { - BoxElement* element = model; - canvas_draw_box(canvas, element->x, element->y, element->w, element->h); -} - -static void widget_box_free(WidgetComponent* component) { - BoxElement* element = component->model; - free(element); - free(component); -} - -static void js_widget_add_box(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 4)) return; - - int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1)); - int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2)); - int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3)); - - WidgetComponent* component = malloc(sizeof(WidgetComponent)); - component->draw = widget_box_draw; - component->free = widget_box_free; - component->model = malloc(sizeof(BoxElement)); - BoxElement* element = component->model; - element->x = x; - element->y = y; - element->w = w; - element->h = h; - - with_view_model( - widget->view, - WidgetModel * model, - { - ++model->max_assigned_id; - component->id = model->max_assigned_id; - ComponentArray_push_back(model->component, component); - }, - true); - - mjs_return(mjs, mjs_mk_number(mjs, component->id)); -} - -static void widget_circle_draw(Canvas* canvas, void* model) { - CircleElement* element = model; - canvas_draw_circle(canvas, element->x, element->y, element->r); -} - -static void widget_circle_free(WidgetComponent* component) { - CircleElement* element = component->model; - free(element); - free(component); -} - -static void js_widget_add_circle(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 3)) return; - - int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1)); - int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 2)); - - WidgetComponent* component = malloc(sizeof(WidgetComponent)); - component->draw = widget_circle_draw; - component->free = widget_circle_free; - component->model = malloc(sizeof(CircleElement)); - CircleElement* element = component->model; - element->x = x; - element->y = y; - element->r = r; - - with_view_model( - widget->view, - WidgetModel * model, - { - ++model->max_assigned_id; - component->id = model->max_assigned_id; - ComponentArray_push_back(model->component, component); - }, - true); - - mjs_return(mjs, mjs_mk_number(mjs, component->id)); -} - -static void widget_disc_draw(Canvas* canvas, void* model) { - DiscElement* element = model; - canvas_draw_disc(canvas, element->x, element->y, element->r); -} - -static void widget_disc_free(WidgetComponent* component) { - DiscElement* element = component->model; - free(element); - free(component); -} - -static void js_widget_add_disc(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 3)) return; - - int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1)); - int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 2)); - - WidgetComponent* component = malloc(sizeof(WidgetComponent)); - component->draw = widget_disc_draw; - component->free = widget_disc_free; - component->model = malloc(sizeof(DiscElement)); - DiscElement* element = component->model; - element->x = x; - element->y = y; - element->r = r; - - with_view_model( - widget->view, - WidgetModel * model, - { - ++model->max_assigned_id; - component->id = model->max_assigned_id; - ComponentArray_push_back(model->component, component); - }, - true); - - mjs_return(mjs, mjs_mk_number(mjs, component->id)); -} - -static void widget_dot_draw(Canvas* canvas, void* model) { - DotElement* element = model; - canvas_draw_dot(canvas, element->x, element->y); -} - -static void widget_dot_free(WidgetComponent* component) { - DotElement* element = component->model; - free(element); - free(component); -} - -static void js_widget_add_dot(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; - - int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1)); - - WidgetComponent* component = malloc(sizeof(WidgetComponent)); - component->draw = widget_dot_draw; - component->free = widget_dot_free; - component->model = malloc(sizeof(DotElement)); - DotElement* element = component->model; - element->x = x; - element->y = y; - - with_view_model( - widget->view, - WidgetModel * model, - { - ++model->max_assigned_id; - component->id = model->max_assigned_id; - ComponentArray_push_back(model->component, component); - }, - true); - - mjs_return(mjs, mjs_mk_number(mjs, component->id)); -} - -static void widget_frame_draw(Canvas* canvas, void* model) { - FrameElement* element = model; - canvas_draw_frame(canvas, element->x, element->y, element->w, element->h); -} - -static void widget_frame_free(WidgetComponent* component) { - FrameElement* element = component->model; - free(element); - free(component); -} - -static void js_widget_add_frame(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 4)) return; - - int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1)); - int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2)); - int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3)); - - WidgetComponent* component = malloc(sizeof(WidgetComponent)); - component->draw = widget_frame_draw; - component->free = widget_frame_free; - component->model = malloc(sizeof(FrameElement)); - FrameElement* element = component->model; - element->x = x; - element->y = y; - element->w = w; - element->h = h; - - with_view_model( - widget->view, - WidgetModel * model, - { - ++model->max_assigned_id; - component->id = model->max_assigned_id; - ComponentArray_push_back(model->component, component); - }, - true); - - mjs_return(mjs, mjs_mk_number(mjs, component->id)); -} - -static void widget_glyph_draw(Canvas* canvas, void* model) { - GlyphElement* element = model; - canvas_draw_glyph(canvas, element->x, element->y, element->ch); -} - -static void widget_glyph_free(WidgetComponent* component) { - GlyphElement* element = component->model; - free(element); - free(component); -} - -static void js_widget_add_glyph(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 3)) return; - - int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1)); - int32_t ch = mjs_get_int32(mjs, mjs_arg(mjs, 2)); - - WidgetComponent* component = malloc(sizeof(WidgetComponent)); - component->draw = widget_glyph_draw; - component->free = widget_glyph_free; - component->model = malloc(sizeof(GlyphElement)); - GlyphElement* element = component->model; - element->x = x; - element->y = y; - element->ch = ch; - - with_view_model( - widget->view, - WidgetModel * model, - { - ++model->max_assigned_id; - component->id = model->max_assigned_id; - ComponentArray_push_back(model->component, component); - }, - true); - - mjs_return(mjs, mjs_mk_number(mjs, component->id)); -} - -static void widget_line_draw(Canvas* canvas, void* model) { - LineElement* element = model; - canvas_draw_line(canvas, element->x1, element->y1, element->x2, element->y2); -} - -static void widget_line_free(WidgetComponent* component) { - LineElement* element = component->model; - free(element); - free(component); -} - -static void js_widget_add_line(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 4)) return; - - int32_t x1 = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - int32_t y1 = mjs_get_int32(mjs, mjs_arg(mjs, 1)); - int32_t x2 = mjs_get_int32(mjs, mjs_arg(mjs, 2)); - int32_t y2 = mjs_get_int32(mjs, mjs_arg(mjs, 3)); - - WidgetComponent* component = malloc(sizeof(WidgetComponent)); - component->draw = widget_line_draw; - component->free = widget_line_free; - component->model = malloc(sizeof(LineElement)); - LineElement* element = component->model; - element->x1 = x1; - element->y1 = y1; - element->x2 = x2; - element->y2 = y2; - - with_view_model( - widget->view, - WidgetModel * model, - { - ++model->max_assigned_id; - component->id = model->max_assigned_id; - ComponentArray_push_back(model->component, component); - }, - true); - - mjs_return(mjs, mjs_mk_number(mjs, component->id)); -} - -static void widget_rbox_draw(Canvas* canvas, void* model) { - RboxElement* element = model; - canvas_draw_rbox(canvas, element->x, element->y, element->w, element->h, element->r); -} - -static void widget_rbox_free(WidgetComponent* component) { - BoxElement* element = component->model; - free(element); - free(component); -} - -static void js_widget_add_rbox(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 5)) return; - - int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1)); - int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2)); - int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3)); - int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 4)); - - WidgetComponent* component = malloc(sizeof(WidgetComponent)); - component->draw = widget_rbox_draw; - component->free = widget_rbox_free; - component->model = malloc(sizeof(RboxElement)); - RboxElement* element = component->model; - element->x = x; - element->y = y; - element->w = w; - element->h = h; - element->r = r; - - with_view_model( - widget->view, - WidgetModel * model, - { - ++model->max_assigned_id; - component->id = model->max_assigned_id; - ComponentArray_push_back(model->component, component); - }, - true); - - mjs_return(mjs, mjs_mk_number(mjs, component->id)); -} - -static void widget_rframe_draw(Canvas* canvas, void* model) { - RframeElement* element = model; - canvas_draw_rframe(canvas, element->x, element->y, element->w, element->h, element->r); -} - -static void widget_rframe_free(WidgetComponent* component) { - RframeElement* element = component->model; - free(element); - free(component); -} - -static void js_widget_add_rframe(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 5)) return; - - int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1)); - int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2)); - int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3)); - int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 4)); - - WidgetComponent* component = malloc(sizeof(WidgetComponent)); - component->draw = widget_rframe_draw; - component->free = widget_rframe_free; - component->model = malloc(sizeof(RframeElement)); - RframeElement* element = component->model; - element->x = x; - element->y = y; - element->w = w; - element->h = h; - element->r = r; - - with_view_model( - widget->view, - WidgetModel * model, - { - ++model->max_assigned_id; - component->id = model->max_assigned_id; - ComponentArray_push_back(model->component, component); - }, - true); - - mjs_return(mjs, mjs_mk_number(mjs, component->id)); -} - -static void widget_text_draw(Canvas* canvas, void* model) { - TextElement* element = model; - canvas_set_font(canvas, element->font); - canvas_draw_str(canvas, element->x, element->y, furi_string_get_cstr(element->text)); -} - -static void widget_text_free(WidgetComponent* component) { - TextElement* element = component->model; - furi_string_free(element->text); - free(element); - free(component); -} - -static void js_widget_add_text(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 4)) return; - - int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1)); - - mjs_val_t font_arg = mjs_arg(mjs, 2); - size_t font_name_len = 0; - const char* font_name_text = mjs_get_string(mjs, &font_arg, &font_name_len); - if(!font_name_text) { - ret_bad_args(mjs, "Font name must be a string"); - return; - } - - Font font = FontTotalNumber; - size_t cmp_str_len = strlen("Primary"); - if(font_name_len == cmp_str_len && strncmp(font_name_text, "Primary", cmp_str_len) == 0) { - font = FontPrimary; - } else { - cmp_str_len = strlen("Secondary"); - if(font_name_len == cmp_str_len && - strncmp(font_name_text, "Secondary", cmp_str_len) == 0) { - font = FontSecondary; - } - } - if(font == FontTotalNumber) { - ret_bad_args(mjs, "Unknown font name"); - return; - } - - mjs_val_t text_arg = mjs_arg(mjs, 3); - size_t text_len = 0; - const char* text = mjs_get_string(mjs, &text_arg, &text_len); - if(!text) { - ret_bad_args(mjs, "Text must be a string"); - return; - } - FuriString* text_str = furi_string_alloc_set(text); - - WidgetComponent* component = malloc(sizeof(WidgetComponent)); - component->draw = widget_text_draw; - component->free = widget_text_free; - component->model = malloc(sizeof(TextElement)); - TextElement* element = component->model; - element->x = x; - element->y = y; - element->font = font; - element->text = text_str; - - with_view_model( - widget->view, - WidgetModel * model, - { - ++model->max_assigned_id; - component->id = model->max_assigned_id; - ComponentArray_push_back(model->component, component); - }, - true); - - mjs_return(mjs, mjs_mk_number(mjs, component->id)); -} - -static void widget_xbm_draw(Canvas* canvas, void* model) { - XbmElement* element = model; - XbmImage* image = NULL; - - with_view_model( - element->view, - WidgetModel * widget_model, - { image = *XbmImageList_get(widget_model->image, element->index); }, - false); - - if(image) { - canvas_draw_xbm(canvas, element->x, element->y, image->width, image->height, image->data); - } -} - -static void widget_xbm_free(WidgetComponent* component) { - XbmElement* element = component->model; - free(element); - free(component); -} - -static void js_widget_add_xbm(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 3)) return; - - int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0)); - int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1)); - int32_t index = mjs_get_int32(mjs, mjs_arg(mjs, 2)); - with_view_model( - widget->view, - WidgetModel * widget_model, - { - size_t count = XbmImageList_size(widget_model->image); - if(index < 0 || index >= (int32_t)count) { - ret_bad_args(mjs, "Invalid image index"); - return; - } - }, - false); - - WidgetComponent* component = malloc(sizeof(WidgetComponent)); - component->draw = widget_xbm_draw; - component->free = widget_xbm_free; - component->model = malloc(sizeof(XbmElement)); - XbmElement* element = component->model; - element->x = x; - element->y = y; - element->index = index; - - with_view_model( - widget->view, - WidgetModel * model, - { - ++model->max_assigned_id; - component->id = model->max_assigned_id; - element->view = widget->view; - ComponentArray_push_back(model->component, component); - }, - true); - - mjs_return(mjs, mjs_mk_number(mjs, component->id)); -} - -static void js_widget_is_open(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - mjs_return(mjs, mjs_mk_boolean(mjs, widget->is_shown)); -} - -static void widget_callback(void* context, uint32_t arg) { - UNUSED(arg); - JsWidgetInst* widget = context; - view_holder_set_view(widget->view_holder, NULL); - widget->is_shown = false; -} - -static void widget_exit(void* context) { - JsWidgetInst* widget = context; - // Using timer to schedule view_holder stop, will not work under high CPU load - furi_timer_pending_callback(widget_callback, widget, 0); -} - -static void js_widget_show(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - if(widget->is_shown) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Widget is already shown"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - view_holder_set_view(widget->view_holder, widget->view); - widget->is_shown = true; - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_widget_close(struct mjs* mjs) { - JsWidgetInst* widget = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - view_holder_set_view(widget->view_holder, NULL); - widget->is_shown = false; - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void widget_draw_callback(Canvas* canvas, void* model) { - WidgetModel* widget_model = model; - canvas_clear(canvas); - - ComponentArray_it_t it; - ComponentArray_it(it, widget_model->component); - while(!ComponentArray_end_p(it)) { - WidgetComponent* component = *ComponentArray_ref(it); - if(component->draw != NULL) { - component->draw(canvas, component->model); - } - ComponentArray_next(it); - } -} - -static void* js_widget_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { - UNUSED(modules); - JsWidgetInst* widget = malloc(sizeof(JsWidgetInst)); - - mjs_val_t widget_obj = mjs_mk_object(mjs); - mjs_set(mjs, widget_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, widget)); - // addBox(x: number, y: number, w: number, h: number): number (returns id of the added component) - mjs_set(mjs, widget_obj, "addBox", ~0, MJS_MK_FN(js_widget_add_box)); - // addCircle(x: number, y: number, r: number): number (returns id of the added component) - mjs_set(mjs, widget_obj, "addCircle", ~0, MJS_MK_FN(js_widget_add_circle)); - // addDisc(x: number, y: number, r: number): number (returns id of the added component) - mjs_set(mjs, widget_obj, "addDisc", ~0, MJS_MK_FN(js_widget_add_disc)); - // addDot(x: number, y: number): number (returns id of the added component) - mjs_set(mjs, widget_obj, "addDot", ~0, MJS_MK_FN(js_widget_add_dot)); - // addFrame(x: number, y: number, w: number, h: number): number (returns id of the added component) - mjs_set(mjs, widget_obj, "addFrame", ~0, MJS_MK_FN(js_widget_add_frame)); - // addGlyph(x: number, y: number, ch: number): number (returns id of the added component) - mjs_set(mjs, widget_obj, "addGlyph", ~0, MJS_MK_FN(js_widget_add_glyph)); - // addLine(x1: number, y1: number, x2: number, y2: number): number (returns id of the added component) - mjs_set(mjs, widget_obj, "addLine", ~0, MJS_MK_FN(js_widget_add_line)); - // addRbox(x: number, y: number, w: number, h: number, r: number): number (returns id of the added component) - mjs_set(mjs, widget_obj, "addRbox", ~0, MJS_MK_FN(js_widget_add_rbox)); - // addRframe(x: number, y: number, w: number, h: number, r: number): number (returns id of the added component) - mjs_set(mjs, widget_obj, "addRframe", ~0, MJS_MK_FN(js_widget_add_rframe)); - // addText(x: number, y: number, font: string, text: string): number (returns id of the added component) - mjs_set(mjs, widget_obj, "addText", ~0, MJS_MK_FN(js_widget_add_text)); - // addXbm(x: number, y: number, index: number): number (returns id of the added component) - mjs_set(mjs, widget_obj, "addXbm", ~0, MJS_MK_FN(js_widget_add_xbm)); - // loadImageXbm(path: string): number (returns index of the loaded image) - mjs_set(mjs, widget_obj, "loadImageXbm", ~0, MJS_MK_FN(js_widget_load_image_xbm)); - // remove(id: number): boolean (returns true if the component was removed) - mjs_set(mjs, widget_obj, "remove", ~0, MJS_MK_FN(js_widget_remove)); - // isOpen(): boolean (returns true if the widget is open) - mjs_set(mjs, widget_obj, "isOpen", ~0, MJS_MK_FN(js_widget_is_open)); - // show(): void (shows the widget) - mjs_set(mjs, widget_obj, "show", ~0, MJS_MK_FN(js_widget_show)); - // close(): void (closes the widget) - mjs_set(mjs, widget_obj, "close", ~0, MJS_MK_FN(js_widget_close)); - - widget->view = view_alloc(); - view_allocate_model(widget->view, ViewModelTypeLockFree, sizeof(WidgetModel)); - view_set_draw_callback(widget->view, widget_draw_callback); - with_view_model( - widget->view, - WidgetModel * model, - { - ComponentArray_init(model->component); - XbmImageList_init(model->image); - model->max_assigned_id = 0; - }, - true); - - Gui* gui = furi_record_open(RECORD_GUI); - widget->view_holder = view_holder_alloc(); - view_holder_attach_to_gui(widget->view_holder, gui); - view_holder_set_back_callback(widget->view_holder, widget_exit, widget); - - *object = widget_obj; - return widget; -} - -static void js_widget_destroy(void* inst) { - JsWidgetInst* widget = inst; - - view_holder_set_view(widget->view_holder, NULL); - view_holder_free(widget->view_holder); - widget->view_holder = NULL; - - furi_record_close(RECORD_GUI); - - with_view_model( - widget->view, - WidgetModel * model, - { - ComponentArray_it_t it; - ComponentArray_it(it, model->component); - while(!ComponentArray_end_p(it)) { - WidgetComponent* component = *ComponentArray_ref(it); - if(component && component->free) { - component->free(component); - } - ComponentArray_next(it); - } - ComponentArray_reset(model->component); - ComponentArray_clear(model->component); - XbmImageList_clear(model->image); - }, - false); - view_free(widget->view); - widget->view = NULL; - - free(widget); -} - -static const JsModuleDescriptor js_widget_desc = { - "widget", - js_widget_create, - js_widget_destroy, - NULL, -}; - -static const FlipperAppPluginDescriptor widget_plugin_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_widget_desc, -}; - -const FlipperAppPluginDescriptor* js_widget_ep(void) { - return &widget_plugin_descriptor; -} diff --git a/applications/system/js_app/packages/fz-sdk/global.d.ts b/applications/system/js_app/packages/fz-sdk/global.d.ts index ba6996f27..4c7f217d0 100644 --- a/applications/system/js_app/packages/fz-sdk/global.d.ts +++ b/applications/system/js_app/packages/fz-sdk/global.d.ts @@ -72,7 +72,7 @@ * @brief Checks compatibility between the script and the JS SDK that the * firmware provides * - * @note You're looking at JS SDK v0.1 + * @note You're looking at JS SDK v0.3 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script @@ -92,7 +92,7 @@ declare function sdkCompatibilityStatus(expectedMajor: number, expectedMinor: nu * @brief Checks compatibility between the script and the JS SDK that the * firmware provides in a boolean fashion * - * @note You're looking at JS SDK v0.1 + * @note You're looking at JS SDK v0.3 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script @@ -105,7 +105,7 @@ declare function isSdkCompatible(expectedMajor: number, expectedMinor: number): * @brief Asks the user whether to continue executing the script if the versions * are not compatible. Does nothing if they are. * - * @note You're looking at JS SDK v0.1 + * @note You're looking at JS SDK v0.3 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script diff --git a/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts b/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts index b484ebbf6..cd5ce2b60 100644 --- a/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts @@ -75,6 +75,34 @@ export interface Pin { * @version Added in JS SDK 0.1 */ interrupt(): Contract; + /** + * Determines whether this pin supports PWM. If `false`, all other + * PWM-related methods on this pin will throw an error when called. + * @note On Flipper Zero only pins PA4 and PA7 support PWM + * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + */ + isPwmSupported(): boolean; + /** + * Sets PWM parameters and starts the PWM. Configures the pin with + * `{ direction: "out", outMode: "push_pull" }`. Throws an error if PWM is + * not supported on this pin. + * @param freq Frequency in Hz + * @param duty Duty cycle in % + * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + */ + pwmWrite(freq: number, duty: number): void; + /** + * Determines whether PWM is running. Throws an error if PWM is not + * supported on this pin. + * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + */ + isPwmRunning(): boolean; + /** + * Stops PWM. Does not restore previous pin configuration. Throws an error + * if PWM is not supported on this pin. + * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + */ + pwmStop(): void; } /** diff --git a/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts b/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts index 5556e7fbb..7080ad3ae 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts @@ -33,9 +33,10 @@ type Props = { length: number, defaultData: Uint8Array | ArrayBuffer, } -declare class ByteInput extends View { +type Child = never; +declare class ByteInput extends View { input: Contract; } -declare class ByteInputFactory extends ViewFactory { } +declare class ByteInputFactory extends ViewFactory { } declare const factory: ByteInputFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts b/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts index 9bd0c3966..2fffcb873 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts @@ -37,9 +37,10 @@ type Props = { center: string, right: string, } -declare class Dialog extends View { +type Child = never; +declare class Dialog extends View { input: Contract<"left" | "center" | "right">; } -declare class DialogFactory extends ViewFactory { } +declare class DialogFactory extends ViewFactory { } declare const factory: DialogFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts b/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts index 49e591426..6a848bd03 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts @@ -26,7 +26,8 @@ import type { View, ViewFactory } from "."; type Props = {}; -declare class EmptyScreen extends View { } -declare class EmptyScreenFactory extends ViewFactory { } +type Child = never; +declare class EmptyScreen extends View { } +declare class EmptyScreenFactory extends ViewFactory { } declare const factory: EmptyScreenFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts new file mode 100644 index 000000000..8d2b68bce --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts @@ -0,0 +1,18 @@ +export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px"; + +export type IconData = symbol & { "__tag__": "icon" }; +// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist. + +/** + * Gets a built-in firmware icon for use in GUI + * @param icon Name of the icon + * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + */ +export declare function getBuiltin(icon: BuiltinIcon): IconData; + +/** + * Loads a .fxbm icon (XBM Flipper sprite, from flipperzero-game-engine) for use in GUI + * @param path Path to the .fxbm file + * @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` + */ +export declare function loadFxbm(path: string): IconData; diff --git a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts index 93a6846c2..969b6934e 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts @@ -26,23 +26,23 @@ * assumes control over the entire viewport and all input events. Different * types of views are available (not all of which are unfortunately currently * implemented in JS): - * | View | Has JS adapter? | - * |----------------------|------------------| - * | `button_menu` | ❌ | - * | `button_panel` | ❌ | - * | `byte_input` | ✅ | - * | `dialog_ex` | ✅ (as `dialog`) | - * | `empty_screen` | ✅ | - * | `file_browser` | ❌ | - * | `loading` | ✅ | - * | `menu` | ❌ | - * | `number_input` | ❌ | - * | `popup` | ❌ | - * | `submenu` | ✅ | - * | `text_box` | ✅ | - * | `text_input` | ✅ | - * | `variable_item_list` | ❌ | - * | `widget` | ❌ | + * | View | Has JS adapter? | + * |----------------------|-----------------------| + * | `button_menu` | ❌ | + * | `button_panel` | ❌ | + * | `byte_input` | ✅ | + * | `dialog_ex` | ✅ (as `dialog`) | + * | `empty_screen` | ✅ | + * | `file_browser` | ✅ (as `file_picker`) | + * | `loading` | ✅ | + * | `menu` | ❌ | + * | `number_input` | ❌ | + * | `popup` | ❌ | + * | `submenu` | ✅ | + * | `text_box` | ✅ | + * | `text_input` | ✅ | + * | `variable_item_list` | ❌ | + * | `widget` | ✅ | * * In JS, each view has its own set of properties (or just "props"). The * programmer can manipulate these properties in two ways: @@ -121,7 +121,7 @@ import type { Contract } from "../event_loop"; type Properties = { [K: string]: any }; -export declare class View { +export declare class View { /** * Assign value to property by name * @param property Name of the property @@ -129,9 +129,26 @@ export declare class View { * @version Added in JS SDK 0.1 */ set

(property: P, value: Props[P]): void; + /** + * Adds a child to the View + * @param child Child to add + * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + */ + addChild(child: C): void; + /** + * Removes all children from the View + * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + */ + resetChildren(): void; + /** + * Removes all previous children from the View and assigns new children + * @param children The list of children to assign + * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + */ + setChildren(children: Child[]): void; } -export declare class ViewFactory> { +export declare class ViewFactory> { /** * Create view instance with default values, can be changed later with set() * @version Added in JS SDK 0.1 @@ -140,9 +157,10 @@ export declare class ViewFactory /** * Create view instance with custom values, can be changed later with set() * @param initial Dictionary of property names to values - * @version Added in JS SDK 0.1 + * @param children Optional list of children to add to the view + * @version Added in JS SDK 0.1; amended in JS SDK 0.2, extra feature `"gui-widget"` */ - makeWith(initial: Partial): V; + makeWith(initial: Partial, children?: Child[]): V; } /** @@ -163,7 +181,7 @@ declare class ViewDispatcher { * View object currently shown * @version Added in JS SDK 0.1 */ - currentView: View; + currentView: View; /** * Sends a number to the custom event handler * @param event number to send @@ -175,7 +193,7 @@ declare class ViewDispatcher { * @param assoc View-ViewDispatcher association as returned by `add` * @version Added in JS SDK 0.1 */ - switchTo(assoc: View): void; + switchTo(assoc: View): void; /** * Sends this ViewDispatcher to the front or back, above or below all other * GUI viewports diff --git a/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts b/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts index b8b10c43a..d636f21ca 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts @@ -27,7 +27,8 @@ import type { View, ViewFactory } from "."; type Props = {}; -declare class Loading extends View { } -declare class LoadingFactory extends ViewFactory { } +type Child = never; +declare class Loading extends View { } +declare class LoadingFactory extends ViewFactory { } declare const factory: LoadingFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts b/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts index 31e08aab8..e73856bee 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts @@ -31,9 +31,10 @@ type Props = { header: string, items: string[], }; -declare class Submenu extends View { +type Child = never; +declare class Submenu extends View { chosen: Contract; } -declare class SubmenuFactory extends ViewFactory { } +declare class SubmenuFactory extends ViewFactory { } declare const factory: SubmenuFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts b/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts index a46ec73fa..32003bd95 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts @@ -33,9 +33,10 @@ type Props = { font: "text" | "hex", focus: "start" | "end", } -declare class TextBox extends View { +type Child = never; +declare class TextBox extends View { chosen: Contract; } -declare class TextBoxFactory extends ViewFactory { } +declare class TextBoxFactory extends ViewFactory { } declare const factory: TextBoxFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts b/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts index 5d64b038b..9d0d180ba 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts @@ -37,9 +37,10 @@ type Props = { defaultText: string, defaultTextClear: boolean, } -declare class TextInput extends View { +type Child = never; +declare class TextInput extends View { input: Contract; } -declare class TextInputFactory extends ViewFactory { } +declare class TextInputFactory extends ViewFactory { } declare const factory: TextInputFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts b/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts new file mode 100644 index 000000000..bf4aab22b --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts @@ -0,0 +1,70 @@ +/** + * Displays a combination of custom elements on one screen. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let emptyView = require("gui/widget"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the GUI example. + * + * # View props + * This view does not have any props. + * + * # Children + * This view has the elements as its children. + * + * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + * @module + */ + +import type { View, ViewFactory } from "."; +import type { IconData } from "./icon"; +import type { Contract } from "../event_loop"; + +type Position = { x: number, y: number }; +type Size = { w: number, h: number }; +type Alignment = { align: `${"t" | "c" | "b"}${"l" | "m" | "r"}` }; +type Font = { font: "primary" | "secondary" | "keyboard" | "big_numbers" }; +type Text = { text: string }; + +type StringMultilineElement = { element: "string_multiline" } & Position & Alignment & Font & Text; +type StringElement = { element: "string" } & Position & Alignment & Font & Text; +type TextBoxElement = { element: "text_box", stripToDots: boolean } & Position & Size & Alignment & Text; +type TextScrollElement = { element: "text_scroll" } & Position & Size & Text; +type ButtonElement = { element: "button", button: "left" | "center" | "right" } & Text; +type IconElement = { element: "icon", iconData: IconData } & Position; +type RectElement = { element: "rect", radius: number, fill: boolean } & Position & Size; /** @version Amended in JS SDK 0.3, extra feature `"gui-widget-extras"` */ +type CircleElement = { element: "circle", radius: number, fill: boolean } & Position; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */ +type LineElement = { element: "line", x1: number, y1: number, x2: number, y2: number }; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */ + +type Element = StringMultilineElement + | StringElement + | TextBoxElement + | TextScrollElement + | ButtonElement + | IconElement + | RectElement + | CircleElement + | LineElement; + +type Props = {}; +type Child = Element; +declare class Widget extends View { + /** + * Event source for buttons. Only gets fired if there's a corresponding + * button element. + */ + button: Contract<"left" | "center" | "right">; +} +declare class WidgetFactory extends ViewFactory { } +declare const factory: WidgetFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/package.json b/applications/system/js_app/packages/fz-sdk/package.json index 56a02a068..4d298bef8 100644 --- a/applications/system/js_app/packages/fz-sdk/package.json +++ b/applications/system/js_app/packages/fz-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@darkflippers/fz-sdk-ul", - "version": "0.1.3", + "version": "0.3.0", "description": "Type declarations and documentation for native JS modules available on Unleashed Custom Firmware for Flipper Zero", "keywords": [ "unleashed", diff --git a/applications/system/js_app/packages/fz-sdk/serial/index.d.ts b/applications/system/js_app/packages/fz-sdk/serial/index.d.ts index 3c249352e..5064c4213 100644 --- a/applications/system/js_app/packages/fz-sdk/serial/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/serial/index.d.ts @@ -4,16 +4,33 @@ * @module */ +export interface Framing { + /** + * @note 6 data bits can only be selected when parity is enabled (even or + * odd) + * @note 9 data bits can only be selected when parity is disabled (none) + */ + dataBits: "6" | "7" | "8" | "9"; + parity: "none" | "even" | "odd"; + /** + * @note LPUART only supports whole stop bit lengths (i.e. 1 and 2 but not + * 0.5 and 1.5) + */ + stopBits: "0.5" | "1" | "1.5" | "2"; +} + /** * @brief Initializes the serial port * * Automatically disables Expansion module service to prevent interference. * - * @param port The port to initialize (`"lpuart"` or `"start"`) - * @param baudRate + * @param port The port to initialize (`"lpuart"` or `"usart"`) + * @param baudRate Baud rate + * @param framing See `Framing` type * @version Added in JS SDK 0.1 + * @version Added `framing` parameter in JS SDK 0.3, extra feature `"serial-framing"` */ -export declare function setup(port: "lpuart" | "usart", baudRate: number): void; +export declare function setup(port: "lpuart" | "usart", baudRate: number, framing?: Framing): void; /** * @brief Writes data to the serial port diff --git a/applications/system/js_app/packages/fz-sdk/widget/index.d.ts b/applications/system/js_app/packages/fz-sdk/widget/index.d.ts deleted file mode 100644 index 6644b38b6..000000000 --- a/applications/system/js_app/packages/fz-sdk/widget/index.d.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Displays a customizable Widget on screen - * @version Available with JS feature `widget` - * @module - */ - -type ComponentId = number; - -/** - * @brief Add a box component - * @param x Horizontal position - * @param y Vertical position - * @param w Width - * @param h Height - */ -export declare function addBox(x: number, y: number, w: number, h: number): ComponentId; - -/** - * @brief Add a circle component - * @param x Horizontal position - * @param y Vertical position - * @param r Radius - */ -export declare function addCircle(x: number, y: number, r: number): ComponentId; - -/** - * @brief Add a disc component - * @param x Horizontal position - * @param y Vertical position - * @param r Radius - */ -export declare function addDisc(x: number, y: number, r: number): ComponentId; - -/** - * @brief Add a dot component - * @param x Horizontal position - * @param y Vertical position - */ -export declare function addDot(x: number, y: number): ComponentId; - -/** - * @brief Add a frame component - * @param x Horizontal position - * @param y Vertical position - * @param w Width - * @param h Height - */ -export declare function addFrame(x: number, y: number, w: number, h: number): ComponentId; - -/** - * @brief Add a glyph component - * @param x Horizontal position - * @param y Vertical position - * @param ch ASCII character code (eg. `"C".charCodeAt(0)`) - */ -export declare function addGlyph(x: number, y: number, ch: number): ComponentId; - -/** - * @brief Add a line component - * @param x1 Horizontal position 1 - * @param y1 Vertical position 1 - * @param x2 Horizontal position 2 - * @param y2 Vertical position 2 - */ -export declare function addLine(x1: number, y1: number, x2: number, y2: number): ComponentId; - -/** - * @brief Add a rounded box component - * @param x Horizontal position - * @param y Vertical position - * @param w Width - * @param h Height - * @param r Radius - */ -export declare function addRbox(x: number, y: number, w: number, h: number, r: number): ComponentId; - -/** - * @brief Add a rounded frame component - * @param x Horizontal position - * @param y Vertical position - * @param w Width - * @param h Height - * @param r Radius - */ -export declare function addRframe(x: number, y: number, w: number, h: number, r: number): ComponentId; - -/** - * @brief Add a text component - * @param x Horizontal position - * @param y Vertical position - * @param font What font to use, Primary or Secondary - * @param text Text to display - */ -export declare function addText(x: number, y: number, font: "Primary" | "Secondary", text: string): ComponentId; - -type XbmId = number; - -/** - * @brief Add an xbm image component - * @param x Horizontal position - * @param y Vertical position - * @param index Loaded xbm id to use - */ -export declare function addXbm(x: number, y: number, index: XbmId): ComponentId; - -/** - * @brief Load an xbm image sprite - * @param path Xbm file to load - */ -export declare function loadImageXbm(path: string): XbmId; - -/** - * @brief Remove a component - * @param id Component id to remove - */ -export declare function remove(id: ComponentId): boolean; - -/** - * @brief Check if the widget view is shown - */ -export declare function isOpen(): boolean; - -/** - * @brief Show the widget view - */ -export declare function show(): void; - -/** - * @brief Close the widget view - */ -export declare function close(): void; diff --git a/applications/system/js_app/plugin_api/app_api_table_i.h b/applications/system/js_app/plugin_api/app_api_table_i.h index b2debbde8..76556bcdd 100644 --- a/applications/system/js_app/plugin_api/app_api_table_i.h +++ b/applications/system/js_app/plugin_api/app_api_table_i.h @@ -1,4 +1,5 @@ -#include "js_plugin_api.h" +#include "../js_modules.h" + /* * A list of app's private functions and objects to expose for plugins. * It is used to generate a table of symbols for import resolver to use. @@ -8,4 +9,16 @@ static constexpr auto app_api_table = sort(create_array_t( API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)), API_METHOD(js_flags_set, void, (struct mjs*, uint32_t)), API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)), - API_METHOD(js_module_get, void*, (JsModules*, const char*)))); + API_METHOD(js_module_get, void*, (JsModules*, const char*)), + API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)), + API_METHOD( + js_value_parse, + JsValueParseStatus, + (struct mjs * mjs, + const JsValueParseDeclaration declaration, + JsValueParseFlag flags, + mjs_val_t* buffer, + size_t buf_size, + mjs_val_t* source, + size_t n_c_vals, + ...)))); diff --git a/applications/system/js_app/plugin_api/js_plugin_api.h b/applications/system/js_app/plugin_api/js_plugin_api.h deleted file mode 100644 index 421b68576..000000000 --- a/applications/system/js_app/plugin_api/js_plugin_api.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void JsModules; - -bool js_delay_with_flags(struct mjs* mjs, uint32_t time); - -void js_flags_set(struct mjs* mjs, uint32_t flags); - -uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); - -void* js_module_get(JsModules* modules, const char* name); - -#ifdef __cplusplus -} -#endif diff --git a/applications/system/mfkey/application.fam b/applications/system/mfkey/application.fam new file mode 100644 index 000000000..dfd513847 --- /dev/null +++ b/applications/system/mfkey/application.fam @@ -0,0 +1,28 @@ +App( + appid="mfkey", + name="MFKey", + apptype=FlipperAppType.EXTERNAL, + targets=["f7"], + entry_point="mfkey_main", + requires=[ + "gui", + "storage", + ], + stack_size=1 * 1024, + fap_icon="mfkey.png", + fap_category="NFC", + fap_author="@noproto", + fap_icon_assets="images", + fap_weburl="https://github.com/noproto/FlipperMfkey", + fap_description="MIFARE Classic key recovery tool", + fap_version="3.0", +) + +App( + appid="mfkey_init_plugin", + apptype=FlipperAppType.PLUGIN, + entry_point="init_plugin_ep", + requires=["mfkey"], + sources=["init_plugin.c"], + fal_embedded=True, +) diff --git a/applications/system/mfkey/crypto1.c b/applications/system/mfkey/crypto1.c new file mode 100644 index 000000000..e862b14d1 --- /dev/null +++ b/applications/system/mfkey/crypto1.c @@ -0,0 +1,22 @@ +#pragma GCC optimize("O3") +#pragma GCC optimize("-funroll-all-loops") + +#include +#include "crypto1.h" +#include "mfkey.h" + +#define BIT(x, n) ((x) >> (n) & 1) + +void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr) { + int i; + uint64_t lfsr_value = 0; + for(i = 23; i >= 0; --i) { + lfsr_value = lfsr_value << 1 | BIT(state->odd, i ^ 3); + lfsr_value = lfsr_value << 1 | BIT(state->even, i ^ 3); + } + + // Assign the key value to the MfClassicKey struct + for(i = 0; i < 6; ++i) { + lfsr->data[i] = (lfsr_value >> ((5 - i) * 8)) & 0xFF; + } +} diff --git a/applications/system/mfkey/crypto1.h b/applications/system/mfkey/crypto1.h new file mode 100644 index 000000000..9caf5b35e --- /dev/null +++ b/applications/system/mfkey/crypto1.h @@ -0,0 +1,256 @@ +#ifndef CRYPTO1_H +#define CRYPTO1_H + +#include +#include "mfkey.h" +#include +#include + +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + +static inline uint32_t prng_successor(uint32_t x, uint32_t n); +static inline int filter(uint32_t const x); +static inline uint8_t evenparity32(uint32_t x); +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2); +void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr); +static inline uint32_t crypt_word(struct Crypto1State* s); +static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x); +static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x); +static uint32_t crypt_word_par( + struct Crypto1State* s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t* parity_keystream_bits); +static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x); +static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb); +static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb); + +static const uint8_t lookup1[256] = { + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, + 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; +static const uint8_t lookup2[256] = { + 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, + 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, + 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, + 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, + 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, + 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, + 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, + 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, + 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; + +static inline int filter(uint32_t const x) { + uint32_t f; + f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; + f |= 0x0d938 >> (x >> 16 & 0xf) & 1; + return BIT(0xEC57E80A, f); +} + +#ifndef __ARM_ARCH_7EM__ +static inline uint8_t evenparity32(uint32_t x) { + return __builtin_parity(x); +} +#endif + +#ifdef __ARM_ARCH_7EM__ +static inline uint8_t evenparity32(uint32_t x) { + uint32_t result; + __asm__ volatile("eor r1, %[x], %[x], lsr #16 \n\t" // r1 = x ^ (x >> 16) + "eor r1, r1, r1, lsr #8 \n\t" // r1 = r1 ^ (r1 >> 8) + "eor r1, r1, r1, lsr #4 \n\t" // r1 = r1 ^ (r1 >> 4) + "eor r1, r1, r1, lsr #2 \n\t" // r1 = r1 ^ (r1 >> 2) + "eor r1, r1, r1, lsr #1 \n\t" // r1 = r1 ^ (r1 >> 1) + "and %[result], r1, #1 \n\t" // result = r1 & 1 + : [result] "=r"(result) + : [x] "r"(x) + : "r1"); + return result; +} +#endif + +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { + int p = data[item] >> 25; + p = p << 1 | evenparity32(data[item] & mask1); + p = p << 1 | evenparity32(data[item] & mask2); + data[item] = p << 24 | (data[item] & 0xffffff); +} + +static inline uint32_t crypt_word(struct Crypto1State* s) { + // "in" and "x" are always 0 (last iteration) + uint32_t res_ret = 0; + uint32_t feedin, t; + for(int i = 0; i <= 31; i++) { + res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 + feedin = LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return res_ret; +} + +static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 0; i <= 31; i++) { + next_in = BEBIT(in, i); + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return; +} + +static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x) { + uint32_t ret = 0; + uint32_t feedin, t, next_in; + uint8_t next_ret; + for(int i = 0; i <= 31; i++) { + next_in = BEBIT(in, i); + next_ret = filter(s->odd); + feedin = next_ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + ret |= next_ret << (24 ^ i); + } + return ret; +} + +static uint8_t get_nth_byte(uint32_t value, int n) { + if(n < 0 || n > 3) { + // Handle invalid input + return 0; + } + return (value >> (8 * (3 - n))) & 0xFF; +} + +static uint8_t crypt_bit(struct Crypto1State* s, uint8_t in, int is_encrypted) { + uint32_t feedin, t; + uint8_t ret = filter(s->odd); + feedin = ret & !!is_encrypted; + feedin ^= !!in; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= LF_POLY_EVEN & s->even; + s->even = s->even << 1 | evenparity32(feedin); + t = s->odd, s->odd = s->even, s->even = t; + return ret; +} + +static inline uint32_t crypt_word_par( + struct Crypto1State* s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t* parity_keystream_bits) { + uint32_t ret = 0; + *parity_keystream_bits = 0; // Reset parity keystream bits + + for(int i = 0; i < 32; i++) { + uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); + ret |= bit << (24 ^ i); + // Save keystream parity bit + if((i + 1) % 8 == 0) { + *parity_keystream_bits |= + (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) + << (3 - (i / 8)); + } + } + return ret; +} + +static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + } + return; +} + +// TODO: +/* +uint32_t rollback_word(struct Crypto1State *s, uint32_t in, int x) { + uint32_t res_ret = 0; + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + res_ret |= (ret << (24 ^ i)); + } + return res_ret; +} +*/ + +uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) { + int out; + uint8_t ret; + uint32_t t; + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + + out = s->even & 1; + out ^= LF_POLY_EVEN & (s->even >>= 1); + out ^= LF_POLY_ODD & s->odd; + out ^= !!in; + out ^= (ret = filter(s->odd)) & !!fb; + + s->even |= evenparity32(out) << 23; + return ret; +} + +uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) { + int i; + uint32_t ret = 0; + for(i = 31; i >= 0; --i) + ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); + return ret; +} + +static inline uint32_t prng_successor(uint32_t x, uint32_t n) { + SWAPENDIAN(x); + while(n--) + x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + return SWAPENDIAN(x); +} + +#endif // CRYPTO1_H diff --git a/applications/system/mfkey/images/mfkey.png b/applications/system/mfkey/images/mfkey.png new file mode 100644 index 000000000..f1694bb33 Binary files /dev/null and b/applications/system/mfkey/images/mfkey.png differ diff --git a/applications/system/mfkey/init_plugin.c b/applications/system/mfkey/init_plugin.c new file mode 100644 index 000000000..8540a8f2d --- /dev/null +++ b/applications/system/mfkey/init_plugin.c @@ -0,0 +1,356 @@ +#include +#include +#include +#include +#include +#include +#include "mfkey.h" +#include "crypto1.h" +#include "plugin_interface.h" +#include + +#define TAG "MFKey" + +// TODO: Remove defines that are not needed +#define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log") +#define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested.log") +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 + +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + +bool key_already_found_for_nonce_in_dict(KeysDict* dict, MfClassicNonce* nonce) { + // This function must not be passed the CUID dictionary + bool found = false; + uint8_t key_bytes[sizeof(MfClassicKey)]; + keys_dict_rewind(dict); + while(keys_dict_get_next_key(dict, key_bytes, sizeof(MfClassicKey))) { + uint64_t k = bit_lib_bytes_to_num_be(key_bytes, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for(int i = 0; i < 24; i++) { + (&temp)->odd |= (BIT(k, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(k, 2 * i) << (i ^ 3)); + } + if(nonce->attack == mfkey32) { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { + found = true; + break; + } + } else if(nonce->attack == static_nested || nonce->attack == static_encrypted) { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if(nonce->ks1_1_enc == expected_ks1) { + found = true; + break; + } + } + } + return found; +} + +bool napi_mf_classic_mfkey32_nonces_check_presence() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool nonces_present = storage_common_stat(storage, MF_CLASSIC_NONCE_PATH, NULL) == FSE_OK; + + furi_record_close(RECORD_STORAGE); + + return nonces_present; +} + +bool napi_mf_classic_nested_nonces_check_presence() { + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = buffered_file_stream_alloc(storage); + bool nonces_present = false; + FuriString* line = furi_string_alloc(); + + do { + if(!buffered_file_stream_open( + stream, MF_CLASSIC_NESTED_NONCE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + break; + } + + while(stream_read_line(stream, line)) { + if(furi_string_search_str(line, "dist 0") != FURI_STRING_FAILURE) { + nonces_present = true; + break; + } + } + + } while(false); + + furi_string_free(line); + buffered_file_stream_close(stream); + stream_free(stream); + furi_record_close(RECORD_STORAGE); + + return nonces_present; +} + +int binaryStringToInt(const char* binStr) { + int result = 0; + while(*binStr) { + result <<= 1; + if(*binStr == '1') { + result |= 1; + } + binStr++; + } + return result; +} + +bool load_mfkey32_nonces( + MfClassicNonceArray* nonce_array, + ProgramState* program_state, + KeysDict* system_dict, + bool system_dict_exists, + KeysDict* user_dict) { + bool array_loaded = false; + + do { + // https://github.com/flipperdevices/flipperzero-firmware/blob/5134f44c09d39344a8747655c0d59864bb574b96/applications/services/storage/filesystem_api_defines.h#L8-L22 + if(!buffered_file_stream_open( + nonce_array->stream, MF_CLASSIC_NONCE_PATH, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { + buffered_file_stream_close(nonce_array->stream); + break; + } + + // Check for newline ending + if(!stream_eof(nonce_array->stream)) { + if(!stream_seek(nonce_array->stream, -1, StreamOffsetFromEnd)) break; + uint8_t last_char = 0; + if(stream_read(nonce_array->stream, &last_char, 1) != 1) break; + if(last_char != '\n') { + //FURI_LOG_D(TAG, "Adding new line ending"); + if(stream_write_char(nonce_array->stream, '\n') != 1) break; + } + if(!stream_rewind(nonce_array->stream)) break; + } + + // Read total amount of nonces + FuriString* next_line; + next_line = furi_string_alloc(); + while(!(program_state->close_thread_please)) { + if(!stream_read_line(nonce_array->stream, next_line)) { + //FURI_LOG_T(TAG, "No nonces left"); + break; + } + /* + FURI_LOG_T( + TAG, + "Read line: %s, len: %zu", + furi_string_get_cstr(next_line), + furi_string_size(next_line)); + */ + if(!furi_string_start_with_str(next_line, "Sec")) continue; + const char* next_line_cstr = furi_string_get_cstr(next_line); + MfClassicNonce res = {0}; + res.attack = mfkey32; + int i = 0; + char* endptr; + for(i = 0; i <= 17; i++) { + if(i != 0) { + next_line_cstr = strchr(next_line_cstr, ' '); + if(next_line_cstr) { + next_line_cstr++; + } else { + break; + } + } + unsigned long value = strtoul(next_line_cstr, &endptr, 16); + switch(i) { + case 5: + res.uid = value; + break; + case 7: + res.nt0 = value; + break; + case 9: + res.nr0_enc = value; + break; + case 11: + res.ar0_enc = value; + break; + case 13: + res.nt1 = value; + break; + case 15: + res.nr1_enc = value; + break; + case 17: + res.ar1_enc = value; + break; + default: + break; // Do nothing + } + next_line_cstr = endptr; + } + res.p64 = prng_successor(res.nt0, 64); + res.p64b = prng_successor(res.nt1, 64); + res.uid_xor_nt0 = res.uid ^ res.nt0; + res.uid_xor_nt1 = res.uid ^ res.nt1; + + (program_state->total)++; + if((system_dict_exists && key_already_found_for_nonce_in_dict(system_dict, &res)) || + (key_already_found_for_nonce_in_dict(user_dict, &res))) { + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + //FURI_LOG_I(TAG, "No key found for %8lx %8lx", res.uid, res.ar1_enc); + // TODO: Refactor + nonce_array->remaining_nonce_array = realloc( //-V701 + nonce_array->remaining_nonce_array, + sizeof(MfClassicNonce) * ((nonce_array->remaining_nonces) + 1)); + nonce_array->remaining_nonces++; + nonce_array->remaining_nonce_array[(nonce_array->remaining_nonces) - 1] = res; + nonce_array->total_nonces++; + } + furi_string_free(next_line); + buffered_file_stream_close(nonce_array->stream); + + array_loaded = true; + //FURI_LOG_I(TAG, "Loaded %lu Mfkey32 nonces", nonce_array->total_nonces); + } while(false); + + return array_loaded; +} + +bool load_nested_nonces( + MfClassicNonceArray* nonce_array, + ProgramState* program_state, + KeysDict* system_dict, + bool system_dict_exists, + KeysDict* user_dict) { + if(!buffered_file_stream_open( + nonce_array->stream, MF_CLASSIC_NESTED_NONCE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + return false; + } + + FuriString* next_line = furi_string_alloc(); + bool array_loaded = false; + + while(stream_read_line(nonce_array->stream, next_line)) { + const char* line = furi_string_get_cstr(next_line); + + // Only process lines ending with "dist 0" + if(!strstr(line, "dist 0")) { + continue; + } + + MfClassicNonce res = {0}; + res.attack = static_encrypted; + + int parsed = sscanf( + line, + "Sec %*d key %*c cuid %" PRIx32 " nt0 %" PRIx32 " ks0 %" PRIx32 + " par0 %4[01] nt1 %" PRIx32 " ks1 %" PRIx32 " par1 %4[01]", + &res.uid, + &res.nt0, + &res.ks1_1_enc, + res.par_1_str, + &res.nt1, + &res.ks1_2_enc, + res.par_2_str); + + if(parsed >= 4) { // At least one nonce is present + res.par_1 = binaryStringToInt(res.par_1_str); + res.uid_xor_nt0 = res.uid ^ res.nt0; + + if(parsed == 7) { // Both nonces are present + res.attack = static_nested; + res.par_2 = binaryStringToInt(res.par_2_str); + res.uid_xor_nt1 = res.uid ^ res.nt1; + } + + (program_state->total)++; + if((system_dict_exists && key_already_found_for_nonce_in_dict(system_dict, &res)) || + (key_already_found_for_nonce_in_dict(user_dict, &res))) { + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + + nonce_array->remaining_nonce_array = realloc( + nonce_array->remaining_nonce_array, + sizeof(MfClassicNonce) * (nonce_array->remaining_nonces + 1)); + nonce_array->remaining_nonce_array[nonce_array->remaining_nonces] = res; + nonce_array->remaining_nonces++; + nonce_array->total_nonces++; + array_loaded = true; + } + } + + furi_string_free(next_line); + buffered_file_stream_close(nonce_array->stream); + + //FURI_LOG_I(TAG, "Loaded %lu Static Nested nonces", nonce_array->total_nonces); + return array_loaded; +} + +MfClassicNonceArray* napi_mf_classic_nonce_array_alloc( + KeysDict* system_dict, + bool system_dict_exists, + KeysDict* user_dict, + ProgramState* program_state) { + MfClassicNonceArray* nonce_array = malloc(sizeof(MfClassicNonceArray)); + MfClassicNonce* remaining_nonce_array_init = malloc(sizeof(MfClassicNonce) * 1); + nonce_array->remaining_nonce_array = remaining_nonce_array_init; + Storage* storage = furi_record_open(RECORD_STORAGE); + nonce_array->stream = buffered_file_stream_alloc(storage); + furi_record_close(RECORD_STORAGE); + + if(program_state->mfkey32_present) { + load_mfkey32_nonces( + nonce_array, program_state, system_dict, system_dict_exists, user_dict); + } + + if(program_state->nested_present) { + load_nested_nonces(nonce_array, program_state, system_dict, system_dict_exists, user_dict); + } + + return nonce_array; +} + +void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) { + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_array); + furi_assert(nonce_array->stream); + + // TODO: Already closed? + buffered_file_stream_close(nonce_array->stream); + stream_free(nonce_array->stream); + free(nonce_array); +} + +/* Actual implementation of app<>plugin interface */ +static const MfkeyPlugin init_plugin = { + .name = "Initialization Plugin", + .napi_mf_classic_mfkey32_nonces_check_presence = + &napi_mf_classic_mfkey32_nonces_check_presence, + .napi_mf_classic_nested_nonces_check_presence = &napi_mf_classic_nested_nonces_check_presence, + .napi_mf_classic_nonce_array_alloc = &napi_mf_classic_nonce_array_alloc, + .napi_mf_classic_nonce_array_free = &napi_mf_classic_nonce_array_free, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor init_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &init_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* init_plugin_ep() { + return &init_plugin_descriptor; +} diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c new file mode 100644 index 000000000..ae5cc90f2 --- /dev/null +++ b/applications/system/mfkey/mfkey.c @@ -0,0 +1,915 @@ +#pragma GCC optimize("O3") +#pragma GCC optimize("-funroll-all-loops") + +// TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first? +// (a cache for key_already_found_for_nonce_in_dict) +// TODO: Selectively unroll loops to reduce binary size +// TODO: Collect parity during Mfkey32 attacks to further optimize the attack +// TODO: Why different sscanf between Mfkey32 and Nested? +// TODO: "Read tag again with NFC app" message upon completion, "Complete. Keys added: " +// TODO: Separate Mfkey32 and Nested functions where possible to reduce branch statements +// TODO: Find ~1 KB memory leak +// TODO: Use seednt16 to reduce static encrypted key candidates: https://gist.github.com/noproto/8102f8f32546564cd674256e62ff76ea +// https://eprint.iacr.org/2024/1275.pdf section X +// TODO: Static Encrypted: Minimum RAM for adding to keys dict (avoid crashes) +// TODO: Static Encrypted: Optimize KeysDict or buffer keys to write in chunks + +#include +#include +#include +#include +#include "mfkey_icons.h" +#include +#include +#include +#include +#include +#include +#include +#include "mfkey.h" +#include "crypto1.h" +#include "plugin_interface.h" +#include +#include +#include + +#define TAG "MFKey" + +// TODO: Remove defines that are not needed +#define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") +#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 + +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +//#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) + +static int eta_round_time = 44; +static int eta_total_time = 705; +// MSB_LIMIT: Chunk size (out of 256) +static int MSB_LIMIT = 16; + +static inline int + check_state(struct Crypto1State* t, MfClassicNonce* n, ProgramState* program_state) { + if(!(t->odd | t->even)) return 0; + if(n->attack == mfkey32) { + uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); + if(rb != n->ar0_enc) { + return 0; + } + rollback_word_noret(t, n->nr0_enc, 1); + rollback_word_noret(t, n->uid_xor_nt0, 0); + struct Crypto1State temp = {t->odd, t->even}; + crypt_word_noret(t, n->uid_xor_nt1, 0); + crypt_word_noret(t, n->nr1_enc, 1); + if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) { + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } else if(n->attack == static_nested) { + struct Crypto1State temp = {t->odd, t->even}; + rollback_word_noret(t, n->uid_xor_nt1, 0); + if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) { + rollback_word_noret(&temp, n->uid_xor_nt1, 0); + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } else if(n->attack == static_encrypted) { + // TODO: Parity bits from rollback_word? + if(n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) { + // Reduce with parity + uint8_t local_parity_keystream_bits; + struct Crypto1State temp = {t->odd, t->even}; + if((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == + n->ks1_1_enc) && + (local_parity_keystream_bits == n->par_1)) { + // Found key candidate + crypto1_get_lfsr(t, &(n->key)); + program_state->num_candidates++; + keys_dict_add_key(program_state->cuid_dict, n->key.data, sizeof(MfClassicKey)); + } + } + } + return 0; +} + +static inline int state_loop( + unsigned int* states_buffer, + int xks, + int m1, + int m2, + unsigned int in, + uint8_t and_val) { + int states_tail = 0; + int round = 0, s = 0, xks_bit = 0, round_in = 0; + + for(round = 1; round <= 12; round++) { + xks_bit = BIT(xks, round); + if(round > 4) { + round_in = ((in >> (2 * (round - 4))) & and_val) << 24; + } + + for(s = 0; s <= states_tail; s++) { + states_buffer[s] <<= 1; + + if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) { + states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit; + if(round > 4) { + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } + } else if(filter(states_buffer[s]) == xks_bit) { + // TODO: Refactor + if(round > 4) { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = states_buffer[s] | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= round_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } else { + states_buffer[++states_tail] = states_buffer[++s]; + states_buffer[s] = states_buffer[s - 1] | 1; + } + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + return states_tail; +} + +int binsearch(unsigned int data[], int start, int stop) { + int mid, val = data[stop] & 0xff000000; + while(start != stop) { + mid = (stop - start) >> 1; + if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) + stop = start + mid; + else + start += mid + 1; + } + return start; +} +void quicksort(unsigned int array[], int low, int high) { + //if (SIZEOF(array) == 0) + // return; + if(low >= high) return; + int middle = low + (high - low) / 2; + unsigned int pivot = array[middle]; + int i = low, j = high; + while(i <= j) { + while(array[i] < pivot) { + i++; + } + while(array[j] > pivot) { + j--; + } + if(i <= j) { // swap + int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + i++; + j--; + } + } + if(low < j) { + quicksort(array, low, j); + } + if(high > i) { + quicksort(array, i, high); + } +} +int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) { + in <<= 24; + for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { + if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { + data[tbl] |= filter(data[tbl]) ^ bit; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } else if(filter(data[tbl]) == bit) { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution(data, tbl, m1, m2); + data[tbl++] ^= in; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } else { + data[tbl--] = data[end--]; + } + } + return end; +} + +int old_recover( + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce* n, + unsigned int in, + int first_run, + ProgramState* program_state) { + int o, e, i; + if(rem == -1) { + for(e = e_head; e <= e_tail; ++e) { + even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); + for(o = o_head; o <= o_tail; ++o, ++s) { + struct Crypto1State temp = {0, 0}; + temp.even = odd[o]; + temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); + if(check_state(&temp, n, program_state)) { + return -1; + } + } + } + return s; + } + if(first_run == 0) { + for(i = 0; (i < 4) && (rem-- != 0); i++) { + oks >>= 1; + eks >>= 1; + in >>= 2; + o_tail = extend_table( + odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); + if(o_head > o_tail) return s; + e_tail = extend_table( + even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); + if(e_head > e_tail) return s; + } + } + first_run = 0; + quicksort(odd, o_head, o_tail); + quicksort(even, e_head, e_tail); + while(o_tail >= o_head && e_tail >= e_head) { + if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover( + odd, + o_tail--, + o, + oks, + even, + e_tail--, + e, + eks, + rem, + s, + n, + in, + first_run, + program_state); + if(s == -1) { + break; + } + } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } else { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; +} + +static inline int sync_state(ProgramState* program_state) { + int ts = furi_hal_rtc_get_timestamp(); + int elapsed_time = ts - program_state->eta_timestamp; + if(elapsed_time < program_state->eta_round) { + program_state->eta_round -= elapsed_time; + } else { + program_state->eta_round = 0; + } + if(elapsed_time < program_state->eta_total) { + program_state->eta_total -= elapsed_time; + } else { + program_state->eta_total = 0; + } + program_state->eta_timestamp = ts; + if(program_state->close_thread_please) { + return 1; + } + return 0; +} + +int calculate_msb_tables( + int oks, + int eks, + int msb_round, + MfClassicNonce* n, + unsigned int* states_buffer, + struct Msb* odd_msbs, + struct Msb* even_msbs, + unsigned int* temp_states_odd, + unsigned int* temp_states_even, + unsigned int in, + ProgramState* program_state) { + //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG + unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1 + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + int states_tail = 0, tail = 0; + int i = 0, j = 0, semi_state = 0, found = 0; + unsigned int msb = 0; + in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; + // TODO: Why is this necessary? + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + + for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { + if(semi_state % 32768 == 0) { + if(sync_state(program_state) == 1) { + return 0; + } + } + + if(filter(semi_state) == (oks & 1)) { //-V547 + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); + + for(i = states_tail; i >= 0; i--) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + found = 0; + for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) { + if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) { + found = 1; + break; + } + } + + if(!found) { + tail = odd_msbs[msb - msb_head].tail++; + odd_msbs[msb - msb_head].states[tail] = states_buffer[i]; + } + } + } + } + + if(filter(semi_state) == (eks & 1)) { //-V547 + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + + for(i = 0; i <= states_tail; i++) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + found = 0; + + for(j = 0; j < even_msbs[msb - msb_head].tail; j++) { + if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) { + found = 1; + break; + } + } + + if(!found) { + tail = even_msbs[msb - msb_head].tail++; + even_msbs[msb - msb_head].states[tail] = states_buffer[i]; + } + } + } + } + } + + oks >>= 12; + eks >>= 12; + + for(i = 0; i < MSB_LIMIT; i++) { + if(sync_state(program_state) == 1) { + return 0; + } + // TODO: Why is this necessary? + memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); + memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); + memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); + memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); + int res = old_recover( + temp_states_odd, + 0, + odd_msbs[i].tail, + oks, + temp_states_even, + 0, + even_msbs[i].tail, + eks, + 3, + 0, + n, + in >> 16, + 1, + program_state); + if(res == -1) { + return 1; + } + //odd_msbs[i].tail = 0; + //even_msbs[i].tail = 0; + } + + return 0; +} + +void** allocate_blocks(const size_t* block_sizes, int num_blocks) { + void** block_pointers = malloc(num_blocks * sizeof(void*)); + + for(int i = 0; i < num_blocks; i++) { + if(memmgr_heap_get_max_free_block() < block_sizes[i]) { + // Not enough memory, free previously allocated blocks + for(int j = 0; j < i; j++) { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } + + block_pointers[i] = malloc(block_sizes[i]); + } + + return block_pointers; +} + +bool is_full_speed() { + return MSB_LIMIT == 16; +} + +bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) { + bool found = false; + const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; + const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; + const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); + void** block_pointers = allocate_blocks(block_sizes, num_blocks); + if(block_pointers == NULL) { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed + if(is_full_speed()) { + //eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if(block_pointers == NULL) { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + // Adjust estimates for static encrypted attacks + if(n->attack == static_encrypted) { + eta_round_time *= 4; + eta_total_time *= 4; + if(is_full_speed()) { + eta_round_time *= 4; + eta_total_time *= 4; + } + } + struct Msb* odd_msbs = block_pointers[0]; + struct Msb* even_msbs = block_pointers[1]; + unsigned int* temp_states_odd = block_pointers[2]; + unsigned int* temp_states_even = block_pointers[3]; + unsigned int* states_buffer = block_pointers[4]; + int oks = 0, eks = 0; + int i = 0, msb = 0; + for(i = 31; i >= 0; i -= 2) { + oks = oks << 1 | BEBIT(ks2, i); + } + for(i = 30; i >= 0; i -= 2) { + eks = eks << 1 | BEBIT(ks2, i); + } + int bench_start = furi_hal_rtc_get_timestamp(); + program_state->eta_total = eta_total_time; + program_state->eta_timestamp = bench_start; + for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { + program_state->search = msb; + program_state->eta_round = eta_round_time; + program_state->eta_total = eta_total_time - (eta_round_time * msb); + if(calculate_msb_tables( + oks, + eks, + msb, + n, + states_buffer, + odd_msbs, + even_msbs, + temp_states_odd, + temp_states_even, + in, + program_state)) { + //int bench_stop = furi_hal_rtc_get_timestamp(); + //FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); + found = true; + break; + } + if(program_state->close_thread_please) { + break; + } + } + // Free the allocated blocks + for(int i = 0; i < num_blocks; i++) { + free(block_pointers[i]); + } + free(block_pointers); + return found; +} + +bool key_already_found_for_nonce_in_solved( + MfClassicKey* keyarray, + int keyarray_size, + MfClassicNonce* nonce) { + for(int k = 0; k < keyarray_size; k++) { + uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for(int i = 0; i < 24; i++) { + (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); + } + if(nonce->attack == mfkey32) { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { + return true; + } + } else if(nonce->attack == static_nested) { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if(nonce->ks1_1_enc == expected_ks1) { + return true; + } + } + } + return false; +} + +#pragma GCC push_options +#pragma GCC optimize("Os") +static void finished_beep() { + // Beep to indicate completion + NotificationApp* notification = furi_record_open("notification"); + notification_message(notification, &sequence_audiovisual_alert); + notification_message(notification, &sequence_display_backlight_on); + furi_record_close("notification"); +} + +void mfkey(ProgramState* program_state) { + uint32_t ks_enc = 0, nt_xor_uid = 0; + MfClassicKey found_key; // Recovered key + size_t keyarray_size = 0; + MfClassicKey* keyarray = malloc(sizeof(MfClassicKey) * 1); + uint32_t i = 0, j = 0; + //FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); + flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); + flipper_application_map_to_memory(app); + const FlipperAppPluginDescriptor* app_descriptor = + flipper_application_plugin_get_descriptor(app); + const MfkeyPlugin* init_plugin = app_descriptor->entry_point; + // Check for nonces + program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); + program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); + if(!(program_state->mfkey32_present) && !(program_state->nested_present)) { + program_state->err = MissingNonces; + program_state->mfkey_state = Error; + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + free(keyarray); + return; + } + // Read dictionaries (optional) + KeysDict* system_dict = {0}; + bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); + KeysDict* user_dict = {0}; + bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); + uint32_t total_dict_keys = 0; + if(system_dict_exists) { + system_dict = + keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); + total_dict_keys += keys_dict_get_total_keys(system_dict); + } + user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); + if(user_dict_exists) { + total_dict_keys += keys_dict_get_total_keys(user_dict); + } + user_dict_exists = true; + program_state->dict_count = total_dict_keys; + program_state->mfkey_state = DictionaryAttack; + // Read nonces + MfClassicNonceArray* nonce_arr; + nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( + system_dict, system_dict_exists, user_dict, program_state); + if(system_dict_exists) { + keys_dict_free(system_dict); + } + if(nonce_arr->total_nonces == 0) { + // Nothing to crack + program_state->err = ZeroNonces; + program_state->mfkey_state = Error; + init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + keys_dict_free(user_dict); + free(keyarray); + return; + } + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_arr); + furi_assert(nonce_arr->stream); + // TODO: Already closed? + buffered_file_stream_close(nonce_arr->stream); + stream_free(nonce_arr->stream); + //FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); + program_state->mfkey_state = MFKeyAttack; + // TODO: Work backwards on this array and free memory + for(i = 0; i < nonce_arr->total_nonces; i++) { + MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; + if(key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) { + nonce_arr->remaining_nonces--; + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + //FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); + FuriString* cuid_dict_path; + switch(next_nonce.attack) { + case mfkey32: + ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; + nt_xor_uid = 0; + break; + case static_nested: + ks_enc = next_nonce.ks1_2_enc; + nt_xor_uid = next_nonce.uid_xor_nt1; + break; + case static_encrypted: + ks_enc = next_nonce.ks1_1_enc; + nt_xor_uid = next_nonce.uid_xor_nt0; + cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); + // May need RECORD_STORAGE? + program_state->cuid_dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenAlways, + sizeof(MfClassicKey)); + break; + } + + if(!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { + if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { + keys_dict_free(program_state->cuid_dict); + } + if(program_state->close_thread_please) { + break; + } + // No key found in recover() or static encrypted + (program_state->num_completed)++; + continue; + } + (program_state->cracked)++; + (program_state->num_completed)++; + found_key = next_nonce.key; + bool already_found = false; + for(j = 0; j < keyarray_size; j++) { + if(memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) { + already_found = true; + break; + } + } + if(already_found == false) { + // New key + keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); //-V701 + keyarray_size += 1; + keyarray[keyarray_size - 1] = found_key; + (program_state->unique_cracked)++; + } + } + // TODO: Update display to show all keys were found + // TODO: Prepend found key(s) to user dictionary file + //FURI_LOG_I(TAG, "Unique keys found:"); + for(i = 0; i < keyarray_size; i++) { + //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); + keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); + } + if(keyarray_size > 0) { + dolphin_deed(DolphinDeedNfcMfcAdd); + } + free(nonce_arr); + keys_dict_free(user_dict); + free(keyarray); + if(program_state->mfkey_state == Error) { + return; + } + //FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG + program_state->mfkey_state = Complete; + // No need to alert the user if they asked it to stop + if(!(program_state->close_thread_please)) { + finished_beep(); + } + return; +} + +// Screen is 128x64 px +static void render_callback(Canvas* const canvas, void* ctx) { + furi_assert(ctx); + ProgramState* program_state = ctx; + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + char draw_str[44] = {}; + + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_frame(canvas, 0, 15, 128, 64); + + // FontSecondary by default, title is drawn at the end + snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); + canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); + canvas_draw_icon(canvas, 114, 4, &I_mfkey); + if(program_state->mfkey_state == MFKeyAttack) { + float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); + float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); + float progress = (float)program_state->num_completed / (float)program_state->total; + if(eta_round < 0 || eta_round > 1) { + // Round ETA miscalculated + eta_round = 1; + program_state->eta_round = 0; + } + if(eta_total < 0 || eta_round > 1) { + // Total ETA miscalculated + eta_total = 1; + program_state->eta_total = 0; + } + snprintf( + draw_str, + sizeof(draw_str), + "Cracking: %d/%d - in prog.", + program_state->num_completed, + program_state->total); + elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Round: %d/%d - ETA %02d Sec", + (program_state->search) + 1, // Zero indexed + 256 / MSB_LIMIT, + program_state->eta_round); + elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); + snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); + elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); + } else if(program_state->mfkey_state == DictionaryAttack) { + snprintf( + draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); + canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); + canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); + } else if(program_state->mfkey_state == Complete) { + // TODO: Scrollable list view to see cracked keys if user presses down + elements_progress_bar(canvas, 5, 18, 118, 1); + canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); + snprintf( + draw_str, + sizeof(draw_str), + "Keys added to user dict: %d", + program_state->unique_cracked); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); + if(program_state->num_candidates > 0) { + snprintf( + draw_str, + sizeof(draw_str), + "SEN key candidates: %d", + program_state->num_candidates); + canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); + } + } else if(program_state->mfkey_state == Ready) { + canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); + elements_button_center(canvas, "Start"); + elements_button_right(canvas, "Help"); + } else if(program_state->mfkey_state == Help) { + canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); + canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); + canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); + canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); + } else if(program_state->mfkey_state == Error) { + canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); + if(program_state->err == MissingNonces) { + canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); + } else if(program_state->err == ZeroNonces) { + canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); + } else if(program_state->err == InsufficientRAM) { + canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); + } else { + // Unhandled error + } + } else { + // Unhandled program state + } + // Title + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); + furi_mutex_release(program_state->mutex); +} + +static void input_callback(InputEvent* input_event, void* event_queue) { + furi_assert(event_queue); + furi_message_queue_put((FuriMessageQueue*)event_queue, input_event, FuriWaitForever); +} + +static void mfkey_state_init(ProgramState* program_state) { + program_state->mfkey_state = Ready; + program_state->cracked = 0; + program_state->unique_cracked = 0; + program_state->num_completed = 0; + program_state->num_candidates = 0; + program_state->total = 0; + program_state->dict_count = 0; +} + +// Entrypoint for worker thread +static int32_t mfkey_worker_thread(void* ctx) { + ProgramState* program_state = ctx; + program_state->mfkey_state = Initializing; + mfkey(program_state); + return 0; +} + +int32_t mfkey_main() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + ProgramState* program_state = malloc(sizeof(ProgramState)); + + mfkey_state_init(program_state); + + program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, program_state); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + program_state->mfkeythread = + furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); + + InputEvent input_event; + for(bool main_loop = true; main_loop;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); + + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + + if(event_status == FuriStatusOk) { + if(input_event.type == InputTypePress) { + switch(input_event.key) { + case InputKeyRight: + if(program_state->mfkey_state == Ready) { + program_state->mfkey_state = Help; + } + break; + case InputKeyOk: + if(program_state->mfkey_state == Ready) { + furi_thread_start(program_state->mfkeythread); + } + break; + case InputKeyBack: + if(program_state->mfkey_state == Help) { + program_state->mfkey_state = Ready; + } else { + program_state->close_thread_please = true; + // Wait until thread is finished + furi_thread_join(program_state->mfkeythread); + main_loop = false; + } + break; + default: + break; + } + } + } + + furi_mutex_release(program_state->mutex); + view_port_update(view_port); + } + + // Thread joined in back event handler + furi_thread_free(program_state->mfkeythread); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(program_state->mutex); + free(program_state); + + return 0; +} +#pragma GCC pop_options diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h new file mode 100644 index 000000000..4a7ab3423 --- /dev/null +++ b/applications/system/mfkey/mfkey.h @@ -0,0 +1,108 @@ +#ifndef MFKEY_H +#define MFKEY_H + +#include +#include +#include +#include +#include +#include +#include + +struct Crypto1State { + uint32_t odd, even; +}; +struct Msb { + int tail; + uint32_t states[768]; +}; + +typedef enum { + MissingNonces, + ZeroNonces, + InsufficientRAM, +} MFKeyError; + +typedef enum { + Ready, + Initializing, + DictionaryAttack, + MFKeyAttack, + Complete, + Error, + Help, +} MFKeyState; + +// TODO: Can we eliminate any of the members of this struct? +typedef struct { + FuriMutex* mutex; + MFKeyError err; + MFKeyState mfkey_state; + int cracked; + int unique_cracked; + int num_completed; + int num_candidates; + int total; + int dict_count; + int search; + int eta_timestamp; + int eta_total; + int eta_round; + bool mfkey32_present; + bool nested_present; + bool close_thread_please; + FuriThread* mfkeythread; + KeysDict* cuid_dict; +} ProgramState; + +typedef enum { + mfkey32, + static_nested, + static_encrypted +} AttackType; + +typedef struct { + AttackType attack; + MfClassicKey key; // key + uint32_t uid; // serial number + uint32_t nt0; // tag challenge first + uint32_t nt1; // tag challenge second + uint32_t uid_xor_nt0; // uid ^ nt0 + uint32_t uid_xor_nt1; // uid ^ nt1 + union { + // Mfkey32 + struct { + uint32_t p64; // 64th successor of nt0 + uint32_t p64b; // 64th successor of nt1 + uint32_t nr0_enc; // first encrypted reader challenge + uint32_t ar0_enc; // first encrypted reader response + uint32_t nr1_enc; // second encrypted reader challenge + uint32_t ar1_enc; // second encrypted reader response + }; + // Nested + struct { + uint32_t ks1_1_enc; // first encrypted keystream + uint32_t ks1_2_enc; // second encrypted keystream + char par_1_str[5]; // first parity bits (string representation) + char par_2_str[5]; // second parity bits (string representation) + uint8_t par_1; // first parity bits + uint8_t par_2; // second parity bits + }; + }; +} MfClassicNonce; + +typedef struct { + Stream* stream; + uint32_t total_nonces; + MfClassicNonce* remaining_nonce_array; + size_t remaining_nonces; +} MfClassicNonceArray; + +struct KeysDict { + Stream* stream; + size_t key_size; + size_t key_size_symbols; + size_t total_keys; +}; + +#endif // MFKEY_H diff --git a/applications/system/mfkey/mfkey.png b/applications/system/mfkey/mfkey.png new file mode 100644 index 000000000..f1694bb33 Binary files /dev/null and b/applications/system/mfkey/mfkey.png differ diff --git a/applications/system/mfkey/plugin_interface.h b/applications/system/mfkey/plugin_interface.h new file mode 100644 index 000000000..e7ca438b8 --- /dev/null +++ b/applications/system/mfkey/plugin_interface.h @@ -0,0 +1,13 @@ +#pragma once + +#define PLUGIN_APP_ID "mfkey" +#define PLUGIN_API_VERSION 1 + +typedef struct { + const char* name; + bool (*napi_mf_classic_mfkey32_nonces_check_presence)(); + bool (*napi_mf_classic_nested_nonces_check_presence)(); + MfClassicNonceArray* ( + *napi_mf_classic_nonce_array_alloc)(KeysDict*, bool, KeysDict*, ProgramState*); + void (*napi_mf_classic_nonce_array_free)(MfClassicNonceArray*); +} MfkeyPlugin; diff --git a/applications/system/updater/application.fam b/applications/system/updater/application.fam index a693fa6f5..0c56a06ef 100644 --- a/applications/system/updater/application.fam +++ b/applications/system/updater/application.fam @@ -27,7 +27,7 @@ App( provides=["updater_start"], entry_point="updater_srv", stack_size=2 * 1024, - order=10, + order=20, ) App( @@ -35,5 +35,5 @@ App( apptype=FlipperAppType.STARTUP, entry_point="updater_on_system_start", requires=["updater_app"], - order=110, + order=90, ) diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index 56a16bd9d..bad8f0cd6 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -1,12 +1,14 @@ #include #include -#include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -63,8 +65,8 @@ static const CliSubcommand update_cli_subcommands[] = { {.command = "help", .handler = updater_cli_help}, }; -static void updater_cli_ep(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void updater_cli_ep(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); FuriString* subcommand; subcommand = furi_string_alloc(); @@ -105,8 +107,8 @@ static void updater_start_app(void* context, uint32_t arg) { void updater_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = (Cli*)furi_record_open(RECORD_CLI); - cli_add_command(cli, "update", CliCommandFlagDefault, updater_cli_ep, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command(registry, "update", CliCommandFlagDefault, updater_cli_ep, NULL); furi_record_close(RECORD_CLI); #else UNUSED(updater_cli_ep); diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_0.png b/assets/dolphin/external/L1_Doom_128x64/frame_0.png new file mode 100644 index 000000000..974fda986 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_1.png b/assets/dolphin/external/L1_Doom_128x64/frame_1.png new file mode 100644 index 000000000..3a9a3a71a Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_10.png b/assets/dolphin/external/L1_Doom_128x64/frame_10.png new file mode 100644 index 000000000..7cf69cc1b Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_11.png b/assets/dolphin/external/L1_Doom_128x64/frame_11.png new file mode 100644 index 000000000..99eb9b2a1 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_12.png b/assets/dolphin/external/L1_Doom_128x64/frame_12.png new file mode 100644 index 000000000..973f97378 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_13.png b/assets/dolphin/external/L1_Doom_128x64/frame_13.png new file mode 100644 index 000000000..1a7e06051 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_13.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_14.png b/assets/dolphin/external/L1_Doom_128x64/frame_14.png new file mode 100644 index 000000000..428ac9f1f Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_14.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_15.png b/assets/dolphin/external/L1_Doom_128x64/frame_15.png new file mode 100644 index 000000000..aad59f943 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_15.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_16.png b/assets/dolphin/external/L1_Doom_128x64/frame_16.png new file mode 100644 index 000000000..4dfc84535 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_16.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_17.png b/assets/dolphin/external/L1_Doom_128x64/frame_17.png new file mode 100644 index 000000000..81f4f8beb Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_17.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_18.png b/assets/dolphin/external/L1_Doom_128x64/frame_18.png new file mode 100644 index 000000000..78ad1c754 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_18.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_19.png b/assets/dolphin/external/L1_Doom_128x64/frame_19.png new file mode 100644 index 000000000..efd1d6b0e Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_19.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_2.png b/assets/dolphin/external/L1_Doom_128x64/frame_2.png new file mode 100644 index 000000000..d740f5e33 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_20.png b/assets/dolphin/external/L1_Doom_128x64/frame_20.png new file mode 100644 index 000000000..24693dfe4 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_20.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_21.png b/assets/dolphin/external/L1_Doom_128x64/frame_21.png new file mode 100644 index 000000000..f08e98d81 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_21.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_22.png b/assets/dolphin/external/L1_Doom_128x64/frame_22.png new file mode 100644 index 000000000..baa327482 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_22.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_23.png b/assets/dolphin/external/L1_Doom_128x64/frame_23.png new file mode 100644 index 000000000..f1b7fe627 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_23.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_24.png b/assets/dolphin/external/L1_Doom_128x64/frame_24.png new file mode 100644 index 000000000..24693dfe4 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_24.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_25.png b/assets/dolphin/external/L1_Doom_128x64/frame_25.png new file mode 100644 index 000000000..2f12df072 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_25.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_26.png b/assets/dolphin/external/L1_Doom_128x64/frame_26.png new file mode 100644 index 000000000..e90099bd6 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_26.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_27.png b/assets/dolphin/external/L1_Doom_128x64/frame_27.png new file mode 100644 index 000000000..aaf9e6654 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_27.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_28.png b/assets/dolphin/external/L1_Doom_128x64/frame_28.png new file mode 100644 index 000000000..6e4a77679 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_28.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_29.png b/assets/dolphin/external/L1_Doom_128x64/frame_29.png new file mode 100644 index 000000000..06fa8b6e4 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_29.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_3.png b/assets/dolphin/external/L1_Doom_128x64/frame_3.png new file mode 100644 index 000000000..81bff4b92 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_30.png b/assets/dolphin/external/L1_Doom_128x64/frame_30.png new file mode 100644 index 000000000..6d6717c40 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_30.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_31.png b/assets/dolphin/external/L1_Doom_128x64/frame_31.png new file mode 100644 index 000000000..25770b6fe Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_31.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_32.png b/assets/dolphin/external/L1_Doom_128x64/frame_32.png new file mode 100644 index 000000000..24ee19f40 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_32.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_33.png b/assets/dolphin/external/L1_Doom_128x64/frame_33.png new file mode 100644 index 000000000..3581d8d59 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_33.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_34.png b/assets/dolphin/external/L1_Doom_128x64/frame_34.png new file mode 100644 index 000000000..914390376 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_34.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_35.png b/assets/dolphin/external/L1_Doom_128x64/frame_35.png new file mode 100644 index 000000000..6e6ac6a02 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_35.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_36.png b/assets/dolphin/external/L1_Doom_128x64/frame_36.png new file mode 100644 index 000000000..2f37e6371 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_36.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_37.png b/assets/dolphin/external/L1_Doom_128x64/frame_37.png new file mode 100644 index 000000000..e6f374c4f Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_37.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_38.png b/assets/dolphin/external/L1_Doom_128x64/frame_38.png new file mode 100644 index 000000000..189a16f7b Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_38.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_4.png b/assets/dolphin/external/L1_Doom_128x64/frame_4.png new file mode 100644 index 000000000..424f481ff Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_5.png b/assets/dolphin/external/L1_Doom_128x64/frame_5.png new file mode 100644 index 000000000..fa7e6ad10 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_6.png b/assets/dolphin/external/L1_Doom_128x64/frame_6.png new file mode 100644 index 000000000..063f13bae Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_7.png b/assets/dolphin/external/L1_Doom_128x64/frame_7.png new file mode 100644 index 000000000..0995b83bf Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_8.png b/assets/dolphin/external/L1_Doom_128x64/frame_8.png new file mode 100644 index 000000000..f5911b1b8 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_9.png b/assets/dolphin/external/L1_Doom_128x64/frame_9.png new file mode 100644 index 000000000..57491c2ed Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/meta.txt b/assets/dolphin/external/L1_Doom_128x64/meta.txt new file mode 100644 index 000000000..838239623 --- /dev/null +++ b/assets/dolphin/external/L1_Doom_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 15 +Active frames: 24 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 0 \ No newline at end of file diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_0.png b/assets/dolphin/external/L1_Showtime_128x64/frame_0.png new file mode 100755 index 000000000..7eed9a024 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_1.png b/assets/dolphin/external/L1_Showtime_128x64/frame_1.png new file mode 100755 index 000000000..827730087 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_10.png b/assets/dolphin/external/L1_Showtime_128x64/frame_10.png new file mode 100755 index 000000000..c627bd6f9 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_11.png b/assets/dolphin/external/L1_Showtime_128x64/frame_11.png new file mode 100755 index 000000000..0535101be Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_12.png b/assets/dolphin/external/L1_Showtime_128x64/frame_12.png new file mode 100755 index 000000000..1284019a8 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_13.png b/assets/dolphin/external/L1_Showtime_128x64/frame_13.png new file mode 100755 index 000000000..bc71a08a0 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_13.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_14.png b/assets/dolphin/external/L1_Showtime_128x64/frame_14.png new file mode 100755 index 000000000..1444ab836 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_14.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_15.png b/assets/dolphin/external/L1_Showtime_128x64/frame_15.png new file mode 100755 index 000000000..0945008a5 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_15.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_16.png b/assets/dolphin/external/L1_Showtime_128x64/frame_16.png new file mode 100755 index 000000000..0d1246fc7 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_16.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_17.png b/assets/dolphin/external/L1_Showtime_128x64/frame_17.png new file mode 100755 index 000000000..4d0b7227f Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_17.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_18.png b/assets/dolphin/external/L1_Showtime_128x64/frame_18.png new file mode 100755 index 000000000..d53d074e6 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_18.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_19.png b/assets/dolphin/external/L1_Showtime_128x64/frame_19.png new file mode 100755 index 000000000..0d421d372 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_19.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_2.png b/assets/dolphin/external/L1_Showtime_128x64/frame_2.png new file mode 100755 index 000000000..a52e051b9 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_20.png b/assets/dolphin/external/L1_Showtime_128x64/frame_20.png new file mode 100755 index 000000000..a5962bd2c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_20.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_21.png b/assets/dolphin/external/L1_Showtime_128x64/frame_21.png new file mode 100755 index 000000000..5113c1095 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_21.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_22.png b/assets/dolphin/external/L1_Showtime_128x64/frame_22.png new file mode 100755 index 000000000..88ba06d37 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_22.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_23.png b/assets/dolphin/external/L1_Showtime_128x64/frame_23.png new file mode 100755 index 000000000..6507bdc6b Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_23.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_24.png b/assets/dolphin/external/L1_Showtime_128x64/frame_24.png new file mode 100755 index 000000000..5d4360a82 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_24.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_25.png b/assets/dolphin/external/L1_Showtime_128x64/frame_25.png new file mode 100755 index 000000000..817c78ab5 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_25.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_26.png b/assets/dolphin/external/L1_Showtime_128x64/frame_26.png new file mode 100755 index 000000000..93f02f48e Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_26.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_27.png b/assets/dolphin/external/L1_Showtime_128x64/frame_27.png new file mode 100755 index 000000000..2b856cc79 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_27.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_28.png b/assets/dolphin/external/L1_Showtime_128x64/frame_28.png new file mode 100755 index 000000000..bcf538072 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_28.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_29.png b/assets/dolphin/external/L1_Showtime_128x64/frame_29.png new file mode 100755 index 000000000..68b32d80c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_29.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_3.png b/assets/dolphin/external/L1_Showtime_128x64/frame_3.png new file mode 100755 index 000000000..e7bf65287 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_30.png b/assets/dolphin/external/L1_Showtime_128x64/frame_30.png new file mode 100755 index 000000000..4d34f9afb Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_30.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_31.png b/assets/dolphin/external/L1_Showtime_128x64/frame_31.png new file mode 100755 index 000000000..274a16074 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_31.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_32.png b/assets/dolphin/external/L1_Showtime_128x64/frame_32.png new file mode 100755 index 000000000..7dbb729ef Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_32.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_33.png b/assets/dolphin/external/L1_Showtime_128x64/frame_33.png new file mode 100755 index 000000000..3cfbe5a98 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_33.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_34.png b/assets/dolphin/external/L1_Showtime_128x64/frame_34.png new file mode 100755 index 000000000..4e64d9db3 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_34.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_35.png b/assets/dolphin/external/L1_Showtime_128x64/frame_35.png new file mode 100755 index 000000000..09fe5c1cd Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_35.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_36.png b/assets/dolphin/external/L1_Showtime_128x64/frame_36.png new file mode 100755 index 000000000..4139bd8b5 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_36.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_37.png b/assets/dolphin/external/L1_Showtime_128x64/frame_37.png new file mode 100755 index 000000000..0384fbdae Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_37.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_38.png b/assets/dolphin/external/L1_Showtime_128x64/frame_38.png new file mode 100755 index 000000000..2632807f9 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_38.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_39.png b/assets/dolphin/external/L1_Showtime_128x64/frame_39.png new file mode 100755 index 000000000..f257489a1 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_39.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_4.png b/assets/dolphin/external/L1_Showtime_128x64/frame_4.png new file mode 100755 index 000000000..8a0c1734e Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_40.png b/assets/dolphin/external/L1_Showtime_128x64/frame_40.png new file mode 100755 index 000000000..cbcff2c89 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_40.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_41.png b/assets/dolphin/external/L1_Showtime_128x64/frame_41.png new file mode 100755 index 000000000..4377a91c6 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_41.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_42.png b/assets/dolphin/external/L1_Showtime_128x64/frame_42.png new file mode 100755 index 000000000..30b446707 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_42.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_43.png b/assets/dolphin/external/L1_Showtime_128x64/frame_43.png new file mode 100755 index 000000000..10d9579a3 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_43.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_44.png b/assets/dolphin/external/L1_Showtime_128x64/frame_44.png new file mode 100755 index 000000000..6d9362c51 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_44.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_45.png b/assets/dolphin/external/L1_Showtime_128x64/frame_45.png new file mode 100755 index 000000000..f834ad6c1 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_45.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_46.png b/assets/dolphin/external/L1_Showtime_128x64/frame_46.png new file mode 100755 index 000000000..4c799effb Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_46.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_47.png b/assets/dolphin/external/L1_Showtime_128x64/frame_47.png new file mode 100755 index 000000000..017549d29 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_47.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_48.png b/assets/dolphin/external/L1_Showtime_128x64/frame_48.png new file mode 100755 index 000000000..28497ac23 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_48.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_49.png b/assets/dolphin/external/L1_Showtime_128x64/frame_49.png new file mode 100755 index 000000000..5a25c32da Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_49.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_5.png b/assets/dolphin/external/L1_Showtime_128x64/frame_5.png new file mode 100755 index 000000000..04ad4360c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_6.png b/assets/dolphin/external/L1_Showtime_128x64/frame_6.png new file mode 100755 index 000000000..86bcbf48c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_7.png b/assets/dolphin/external/L1_Showtime_128x64/frame_7.png new file mode 100755 index 000000000..3e2a2c739 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_8.png b/assets/dolphin/external/L1_Showtime_128x64/frame_8.png new file mode 100755 index 000000000..d9babe8dc Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_9.png b/assets/dolphin/external/L1_Showtime_128x64/frame_9.png new file mode 100755 index 000000000..24dcababb Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/meta.txt b/assets/dolphin/external/L1_Showtime_128x64/meta.txt new file mode 100755 index 000000000..da24febdc --- /dev/null +++ b/assets/dolphin/external/L1_Showtime_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 26 +Active frames: 26 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 41 42 43 44 45 46 47 48 49 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 69 +Y: 47 +Text: SHOWTIME! +AlignH: Left +AlignV: Center +StartFrame: 41 +EndFrame: 44 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 7e8bee083..951a4cdf7 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -211,23 +211,9 @@ Min level: 1 Max level: 3 Weight: 3 -Name: L1_Happy_holidays_128x64 +Name: L1_Showtime_128x64 Min butthurt: 0 -Max butthurt: 14 -Min level: 1 -Max level: 3 -Weight: 4 - -Name: L1_Sleigh_ride_128x64 -Min butthurt: 0 -Max butthurt: 14 -Min level: 1 -Max level: 3 -Weight: 4 - -Name: L1_New_year_128x64 -Min butthurt: 0 -Max butthurt: 14 +Max butthurt: 10 Min level: 1 Max level: 3 Weight: 4 @@ -252,3 +238,10 @@ Max butthurt: 10 Min level: 3 Max level: 3 Weight: 2 + +Name: L1_Doom_128x64 +Min butthurt: 0 +Max butthurt: 13 +Min level: 1 +Max level: 3 +Weight: 4 diff --git a/assets/icons/Archive/settings_10px.png b/assets/icons/Archive/settings_10px.png new file mode 100644 index 000000000..2c1e4a262 Binary files /dev/null and b/assets/icons/Archive/settings_10px.png differ diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 1220a5eea..062e453a8 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -759,7 +759,7 @@ GENERATE_BUGLIST = YES # the documentation. # The default value is: YES. -GENERATE_DEPRECATEDLIST= YES +GENERATE_DEPRECATEDLIST= NO # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond diff --git a/documentation/FAQ.md b/documentation/FAQ.md index 67581999e..7dea58c5d 100644 --- a/documentation/FAQ.md +++ b/documentation/FAQ.md @@ -1,57 +1,59 @@ # FAQ -## I bought Flipper Zero and I don't know what I can do with it, pls help +## I bought Flipper Zero, and I don't know what I can do with it, please help! - Start with reading [official main page](https://flipperzero.one/) - Then check out official docs where you can find answers to [most questions](https://docs.flipper.net/) -## How do I install Unleashed firmware? +## How do I install Unleashed Firmware? See [this](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md) -## What version I should install? What do letters `e`, `r`, `c`... mean? +## What version should I install? What do the letters `e`, `c`... mean? Follow this link for [details](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#recommended-update-option---web-updater). -## INSTALLED UNLEASHED AND NOW BACKLIGHT DOESNT WORK? +## I installed Unleashed and now the backlight doesn't work -You’ve installed a version made for custom RGB modded flippers. The version ending in `r` is specifically for `RGB` modded flippers.
+You’ve enabled RGB backlight mod in settings made for custom RGB modded flippers.
Please, do not use that version if your flipper isn’t modded! +Disable in Settings -> LCD & Notifications -> RGB mod settings + +If you have RGB backlight mod do the same but enable the mod instead -## What apps (plugins) are included with Unleashed FW? +## What apps (plugins) are included with Unleashed Firmware? See default pack and extra pack (for `e` build) list [here](https://github.com/xMasterX/all-the-plugins/tree/dev). -## Where I can find differences between original (official) firmware and Unleashed firmware? +## Where can I find differences between the original (official) firmware and Unleashed Firmware? [Right here](https://github.com/DarkFlippers/unleashed-firmware#whats-changed) -## How to use SubGHz Remote app? +## How to use the SubGHz Remote app? -1. Open app, press `Back` button, select `New map file` -2. Configure signal files and their names for every button (also you can add only one signal and make other buttons empty - just don't select any files for them in config) -3. Save new map file -4. Open map file and select your previously created file -5. Use buttons to send subghz signal files that you selected in map config at step 2 +1. Open the app, press the `Back` button, and select `New map file` +2. Configure signal files and their names for every button (also you can only add one signal and make other buttons empty - just don't select any files for them in config) +3. Save the new map file +4. Open the map file and select your previously created file +5. Use buttons to send the subghz signal files that you selected in map config at step 2 -## How to build (compile) firmware? +## How to build (compile) the firmware? Follow this [link](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToBuild.md#how-to-build-by-yourself). -## I installed Unleashed firmware and now my mobile app doesn't connect to flipper ( OR I changed flipper device name and my mobile app now doesn't connect to flipper ) +## I installed Unleashed firmware, and now my mobile app doesn't connect to flipper ( OR I changed flipper device name, and my mobile app now doesn't connect to flipper ) - 1. Click Forget flipper in mobile app - 2. Open your `phone settings - bluetooth`, find flipper - if it present here - open its options and click forget device - 3. On flipper itself open `Settings -> Bluetooth -> Forget all devices` and confirm - 4. Make sure your flipper has bluetooth `ON` and open Mobile app and pair it to flipper - 5. Done + 1. Click Forget flipper in the mobile app + 2. Open `Phone Settings - Bluetooth`, find the flipper - if it present here - open its options and click forget device + 3. On the flipper itself open `Settings -> Bluetooth -> Forget all devices` and confirm + 4. Make sure your flipper has bluetooth `ON` then open the mobile app and pair it to the flipper -## My desktop (pin, favourites, etc..) (or other) settings was reset to default after update, what to do? +## My desktop (pin, favourites, etc..) (or other) settings were reset to default after an update, what do I do? -Just configure that settings again, all is fine, and make sure you seen changelogs for the releases that came out after your previous version, when settings struct is changed, settings file are reset after update, this happens only when struct changes is required, so don't assume that settings will be reset in every release, this will happen only in specific ones +Just configure those settings again, and make sure you view the changelogs for the releases that came out after your previous version, when settings struct is changed, the settings file is reset after an update, this happens only when struct changes are required, so don't assume that settings will be reset in every release, this will only happen in specific ones -## Why is flipper not connecting to Chrome? +## Why is my flipper not connecting to Chrome? The most common cause of the flipper not connecting to google chrome is having qFlipper open while trying to connect your flipper.
@@ -59,7 +61,7 @@ Or having second flipper lab page open at same time.
You must close qFlipper (or other flipper lab web pages) before attempting to connect your flipper to Chrome. -## Flipper doesn't work! How to restore firmware??? +## Flipper doesn't work! How to restore firmware? Follow this [guide](https://docs.flipper.net/basics/firmware-update/firmware-recovery) @@ -74,20 +76,20 @@ Flipper Awesome - place where you can find almost all links that you might need: * UL Dev Builds in [Telegram](https://t.me/kotnehleb) * Our [Discord](https://discord.unleashedflip.com) -## How to change flipper name? +## How do I change my flipper's name? -All is simple: +It's easy: 1. Open `Settings -> Desktop -> Change Flipper Name` 2. Enter new name and click `Save` -3. Exit from settings - Flipper will automatically reboot -4. Done, you have custom name which will stay until you reset it to default or replace with new one +3. Exit from settings - flipper will automatically reboot +4. Done, you now have a custom name which will stay until you reset it or replace it with a new one -## How to reset name to default? +## How to reset the name to default? 1. Open `Settings -> Desktop -> Change Flipper Name` -2. Do not enter anything, just click Save +2. Do not enter anything, just click `Save` 3. Exit from settings - Flipper will automatically reboot -4. Done, name is reset to original one. +4. Done, name is reset to its original form. ## How do I copy files from GitHub to my Flipper Zero? @@ -95,41 +97,41 @@ Follow this detailed [guide](https://github.com/wrenchathome/flipperfiles/blob/m ## Where can I find “This file” or “That file” for my flipper? -These 2 repos will cover most (99.9%) of your needs:
+These 2 repos will cover (99.9%) of your needs:
* https://github.com/UberGuidoZ/Flipper/tree/main * https://github.com/UberGuidoZ/Flipper-IRDB/tree/main -## How can I support Unleashed firmware project? +## How can I support the Unleashed firmware project? Please follow this [link](https://github.com/DarkFlippers/unleashed-firmware#please-support-development-of-the-project). -## What are the dev builds? Where I can get latest build for dev branch? +## What are the dev builds? Where I can get the latest build for dev branch? -This is an automatic assembly of the latest commits from the repository that have not yet been released, the previous build is deleted when a new one is uploaded and old remains only as file in the telegram channel +This is an automatic assembly of the latest commits from this repository that have not yet been released. The previous build is deleted when a new one is uploaded, and the old one remains only as a file in the telegram channel > [!CAUTION] > -> Be aware that this is not release ready builds! +> Be wary - these are not release ready builds! > -> They may have bugs and issues, -> if you are using dev build and found issue, -> report it! In [GitHub issues](https://github.com/DarkFlippers/unleashed-firmware/issues) +> They may have bugs and issues! +> If you are using the dev build and find issues: +> Report it! [GitHub issues](https://github.com/DarkFlippers/unleashed-firmware/issues) -Dev builds is available in Discord, Win channel - `unleashed-development`
-Builds also can be found [here](https://t.me/kotnehleb).
+Dev builds are available in Discord, in channel - `unleashed-development`
+Builds can also be found [here](https://t.me/kotnehleb).
And [here](https://dev.unleashedflip.com/)
## What is the update server? We have our own update server https://up.unleashedflip.com/directory.json

-It is identical to the official one, it is impossible to change it in applications without rebuilding the application, it is hardcoded there

+It is identical to the official one; it is impossible to change it in applications without rebuilding the application

If you want to use it, you need to patch or build your own build of the application you are interested in
-Also you can use it with uFBT to build apps for UL SDK, uFBT will accept that link as one of args
+Also you can use it with uFBT to build apps for UL SDK - uFBT will accept that link as one of args
The server will remain active and will be automatically updated -## External Radio: How to connect CC1101 module +## External Radio: How to connect the CC1101 module [Guide](https://github.com/quen0n/flipperzero-ext-cc1101) @@ -145,59 +147,59 @@ The server will remain active and will be automatically updated > [!TIP] > -> If you are using Unleashed firmware - **all region locks are removed by default**! +> If you are using Unleashed Firmware - **all region locks are removed by default**! Also, there is a way to go outside of frequencies stated in `CC1101 datasheet`, but transmission on those frequencies may cause chip damage, make sure you know what you are doing! -Do not edit this settings to bypass region lock since there is no region locks in unleashed, all chip supported frequencies will work without any extra steps.

+Do not edit these settings to bypass region lock since there is no region locks in unleashed, all chip supported frequencies will work without any extra steps.

-But, if you know that you need to bypass subghz chip safety restriction you can unlock the safety restriction which will allow you to go outside the chips supported frequency.

+But, if you know that you need to bypass subghz chip safety restrictions - you can unlock the safety restriction which will allow you to go outside the chips supported frequency.

This covers how to do it and information regarding the risks of damage to the flipper by doing so. Please read [this](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/DangerousSettings.md) before. -## Can I clone a car key fob for my own car to use flipper as a key? +## Can I clone a car key fob for my own car to use my flipper as a key? -No, and trying to do so with Read RAW will lead to key desync or unpair with blacklist which means re-pair is very hard and requires service tools +No, and trying to do so with Read RAW will lead to key desync or unpair with blacklist which means re-pair is very hard to do and requires service tools -## Will Unleashed FW support car keyfobs decoding, cloning, emulating? +## Will Unleashed Firmware support car keyfobs decoding, cloning, emulating? -No, never +No, never! ## Where can I find jamming files? -Nowhere, this is illegal in almost every country in the world +Nowhere, this is illegal in almost every country in the world! -## I saw something on TikTok and want to ask how to do it, I just wanna be like real hacker +## I saw something on TikTok and want to ask how to do it. I just wanna be like real hacker! -And you might be banned for that in our communities, since 99% of that content is fake, or showing illegal actions, and we don't like TikTok related questions. +You might be banned for that in our communities since 99% of that content is fake or illegal. We do NOT like TikTok questions. -## I was banned in Unleashed Discord / Telegram / etc.. How to remove ban? I created GitHub issue and it was removed too! +## I was banned from the Unleashed Discord / Telegram / etc.. How do I get unbanned? I created a GitHub issue, and it was removed too! -Not possible, rules is rules, read them before sending messages in our communities +Not possible, rules are rules, read them before sending messages in our communities ## How to clean .DS_Store and other dot files left from macOS -`sudo dot_clean -mn /Volumes/Flipper\ SD` -> `Flipper\ SD` may be named differently for you, replace with your microSD card name +`sudo dot_clean -mn /Volumes/Flipper\ SD` -> `Flipper\ SD` may be named differently for you, replace it with your microSD card name ## How to sort files on flipper microSD on macOS / Linux? -Will make sorting faster, and will work for OFW +Will make sorting faster, and will work for OFW: 1. `brew install fatsort` -> Install fatsort using `brew.sh` (only on macOS) 2. `diskutil list` -> Find your disk name for flipper microSD 3. `diskutil unmount /Volumes/Flipper\ SD` 4. `sudo fatsort -n /dev/disk4s1` -> Replace `disk4s1` with your microSD id found on step 2 -## Your Flipper feels slow and unresponsive? +## My flipper feels slow and unresponsive? -1. Make sure you using good microSD card from known brand, flipper works with microSD via SPI that means not any microSD will work good even if it works ok with other devices. +1. Make sure you are using a good microSD card from a known brand. Flipper works with microSD via SPI meaning not all microSDs will work well even if they are compatible with other devices. 2. Go into `Settings -> System` and make sure that you have ```text Log Level = None Debug = OFF Heap Trace = None ``` -3. If some of that settings is set to something different - change it to `None` / `OFF` +3. If some settings are set to something different - change them to `None` / `OFF` 4. Make sure your battery is charged, that can affect performance too ## Flipper crashed, stuck, frozen? @@ -206,31 +208,31 @@ Reboot it by holding `Left` + `Back` buttons ![how to reboot flipper gif, shows how to hold left and back button](https://media.tenor.com/eUbBDDEzmwMAAAAC/flipper-zero-flipper-zero-reboot.gif) -## How to reset forgotten Flipper pin code? +## How to reset a forgotten Flipper pin code? **Disconnect USB Cable if it was connected** 1. Turn off the device - hold back button -> `Turn Off` -**If you can't turn it off, try next step but hold buttons for 30-40 seconds)** -2. Hold Up + Back for `~5 sec` -> You will see reset screen -> Hold Right to reset (and Down arrow to exit if you don't want to reset pin code) -3. Done, internal memory (dolphin level, settings, pin code, is erased to default settings) +**If you can't turn it off, try the next step but hold the buttons for 30-40 seconds)** +2. Hold Up + Back for `~5 sec` -> You will see a reset screen -> Hold Right to reset (and Down arrow to exit if you don't want to reset your pin code) +3. Done, internal memory (dolphin level, settings, pin code) is erased to default settings -## What are the differences between x, y and z firmware? +## What are the differences between x, y, and z firmware? If you just got your flipper and not sure what will work better for you, start with original official firmware, if you think you need more features or want to remove subghz region locks then:
* Try installing **Unleashed firmware**, which is fork of official firmware with many new features and preinstalled plugins (check out `e` build).
-* In other case, If you want to experiment more with UI and other things look for existing forks of Unleashed firmware.
+* In other case, If you want to experiment more with UI and other things look for existing forks of Unleashed Firmware.
* Or, create your own fork with your own customisations
-* Also, before reporting any found issue make sure you are in correct repo, if you are using not **Unleashed**, but different fork or original firmware, do not report issue in **Unleashed firmware** repo or UL communities (Telegram, Discord, etc..) +* Also, before reporting any found issues, make sure you are in correct repo, if you are not using **Unleashed**, but a different fork or the original firmware, do not report issue in **Unleashed firmware** repo or UL communities (Telegram, Discord, etc..) -## Is there a correct way to capturing Infrared signals? +## Is there a correct way to capture Infrared signals? -There is indeed especially with AC units, a new documentation has been released with some notes and steps on capturing infrared signals correctly along with some example data so you are able to understand the difference visually between the two. +There is indeed, especially with AC units - a new documentation has been released with some notes and steps on capturing infrared signals correctly along with some example data so you visually able to understand the difference between the two. [More info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/InfraredCaptures.md) ## NFC / RFID FAQ -From our good friend `@Equip` and `@np0`
+From our good friends `@Equip` and `@np0`
### MIFARE Ultralight @@ -249,20 +251,20 @@ The Flipper Zero has no available attacks for this card currently. ### Amiibos - `NTAG215`. That's it. It's not going on a MIFARE Classic. -- Currently, you cannot write Amiibos to new physical tags. yet. +- Currently, you cannot write Amiibos to new physical tags. ### HID / iClass - `Picopass` iClass can be read using the `Picopass` reader plugin - 26bit Picopass can be downgraded to H10301 RFID credentials (note, it is not guaranteed to work if the reader is not configured to read low frequency) -- Readers will need to be configured and have an LF RFID antenna in order to be read. Certain iClass readers are HF only, and do not have the ability to have LF configured -- **Emulation for Picopass** was added on July 26th, and the updated version can be found in latest releases of **Unleashed** firmware with apps preinstalled, or in official Apps Hub via Flipper Mobile app -- Write support for personalization mode cards is doable with app -- The Seader app and a [SAM expansion board](https://www.redteamtools.com/nard-sam-expansion-board-for-flipper-zero-with-hid-seos-iclass-sam/) will allow reading more secure HID cards, which may be helpful in downgrade attacks +- Readers will need to be configured and have an LF RFID antenna in order to be read. Certain iClass readers are HF only, and do not have the ability to have LF configured. +- **Emulation for Picopass** was added on July 26th, and the updated version can be found in latest releases of **Unleashed** Firmware with apps preinstalled, or in official Apps Hub via Flipper Mobile app +- Write support for personalization mode cards is doable with the app +- The Seader app and a [SAM expansion board](https://www.redteamtools.com/nard-sam-expansion-board-for-flipper-zero-with-hid-seos-iclass-sam/) will allow you to read more secure HID cards, which may be helpful in downgrade attacks ### LF-RFID -If you're wanting to make clones of low frequency RFID chips you need to write to T5577's. `Blanks` do not exist. All of the chips the Flipper Zero can interact with are read-only and cannot be overwritten or purchased blank. +If you want to make clones of low frequency RFID chips you need to write to T5577's. `Blanks` do not exist. All of the chips the Flipper Zero can interact with are read-only and cannot be overwritten or purchased blank. T5577s are multi-emulator chips that the Flipper Zero can program to be other tags diff --git a/documentation/HowToBuild.md b/documentation/HowToBuild.md index eec2e632a..b10c7493b 100644 --- a/documentation/HowToBuild.md +++ b/documentation/HowToBuild.md @@ -29,13 +29,8 @@ Check out `documentation/fbt.md` for details on building and flashing firmware. ./fbt COMPACT=1 DEBUG=0 launch_app APPSRC=applications_user/yourplugin ``` -### Compile everything for development -```sh -./fbt updater_package -``` - -### Compile everything for release + get updater package to update from microSD card +### Compile everything + get updater package to update from microSD card ```sh ./fbt COMPACT=1 DEBUG=0 updater_package @@ -49,13 +44,8 @@ Use `flipper-z-{target}-update-{suffix}.tgz` to flash your device. Check out `documentation/fbt.md` for details on building and flashing firmware. -### Compile everything for development -```powershell -./fbt.cmd updater_package -``` - -### Compile everything for release + get updater package to update from microSD card +### Compile everything + get updater package to update from microSD card ```powershell ./fbt.cmd COMPACT=1 DEBUG=0 updater_package diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 0b8381c1e..b5db2e930 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -62,20 +62,17 @@ Pause script execution by a defined time. ### Modifier keys -Can be combined with a special key command or a single character. +The following modifier keys are recognized: +| Command | Notes | +| ------- | ------------ | +| CTRL | | +| CONTROL | Same as CTRL | +| SHIFT | | +| ALT | | +| GUI | | +| WINDOWS | Same as GUI | -| Command | Notes | -|:----------------|:-------------| -| CONTROL / CTRL | | -| SHIFT | | -| ALT | | -| WINDOWS / GUI | | -| CTRL-ALT | CTRL+ALT | -| CTRL-SHIFT | CTRL+SHIFT | -| ALT-SHIFT | ALT+SHIFT | -| ALT-GUI | ALT+WIN | -| GUI-SHIFT | WIN+SHIFT | -| GUI-CTRL | WIN+CTRL | +You can chain multiple modifier keys together using hyphens (`-`) or spaces. ## Key hold and release @@ -193,3 +190,18 @@ ID 1234:abcd Flipper Devices:Flipper Zero > > VID and PID are hex codes and are mandatory. > Manufacturer and Product are text strings and are optional. + +## Mouse Commands + +Mouse movement and click commands. Mouse click commands support HOLD functionality. + +| Command | Parameters | Notes | +| ------------- | -------------------------------| -------------------------------- | +| LEFTCLICK | None | | +| LEFT_CLICK | None | functionally same as LEFTCLICK | +| RIGHTCLICK | None | | +| RIGHT_CLICK | None | functionally same as RIGHTCLICK | +| MOUSEMOVE | x y: int move mount/direction | | +| MOUSE_MOVE | x y: int move mount/direction | functionally same as MOUSEMOVE | +| MOUSESCROLL | delta: int scroll distance | | +| MOUSE_SCROLL | delta: int scroll distance | functionally same as MOUSESCROLL | diff --git a/documentation/images/js_first_app_on_cli.jpg b/documentation/images/js_first_app_on_cli.jpg new file mode 100644 index 000000000..a59de6a59 Binary files /dev/null and b/documentation/images/js_first_app_on_cli.jpg differ diff --git a/documentation/images/js_first_app_on_fz.jpg b/documentation/images/js_first_app_on_fz.jpg new file mode 100644 index 000000000..ec1539ad2 Binary files /dev/null and b/documentation/images/js_first_app_on_fz.jpg differ diff --git a/documentation/images/js_sdk_code_completion.jpg b/documentation/images/js_sdk_code_completion.jpg new file mode 100644 index 000000000..9ee9b14f1 Binary files /dev/null and b/documentation/images/js_sdk_code_completion.jpg differ diff --git a/documentation/images/js_sdk_npm_start.jpg b/documentation/images/js_sdk_npm_start.jpg new file mode 100644 index 000000000..4e4ce322f Binary files /dev/null and b/documentation/images/js_sdk_npm_start.jpg differ diff --git a/documentation/images/widget.png b/documentation/images/widget.png new file mode 100644 index 000000000..f4dd1ed5b Binary files /dev/null and b/documentation/images/widget.png differ diff --git a/documentation/js/js_about.md b/documentation/js/js_about.md new file mode 100644 index 000000000..eabcd7d18 --- /dev/null +++ b/documentation/js/js_about.md @@ -0,0 +1,16 @@ +# About the JavaScript engine {#js_about_js_engine} + +> Developing applications for Flipper Zero is now much more accessible with the introduction of JavaScript support. + +Previously, building an app for Flipper Zero required C/C++ skills, setting up a development environment, and studying the code of existing applications and documentation. While embedded developers are very familiar with all of this, we wanted to make it easier for people from all backgrounds to create apps for Flipper Zero. + +Flipper firmware now includes a built-in scripting engine that runs JavaScript, one of the most widely used programming languages. You can create script files, share them with others, and launch them directly from the **Apps/Scripts** menu on your Flipper Zero — no need for compiling on a PC. + +JavaScript support is based on the [mJS scripting engine](https://github.com/cesanta/mjs). Originally designed for microcontrollers, mJS makes efficient use of system resources, requiring less than 50k of flash space and 2k of RAM. We've kept the core features of mJS and also added some useful improvements, such as support for compact binary arrays. + +> [!note] +> mJS has some limitations compared to JavaScript engines built into modern browsers. For details on capabilities and limitations, refer to the [mJS documentation on GitHub](https://github.com/cesanta/mjs). + +JavaScript apps can interact with Flipper Zero's resources, including its GUI, buttons, USB-HID device, GPIO, UART interfaces, and more. Let's go through the steps to create your first JavaScript app for Flipper Zero. + +**Next step:** [Your first JavaScript app](#js_your_first_js_app) diff --git a/documentation/js/js_badusb.md b/documentation/js/js_badusb.md index b21126dfc..79ae53f55 100644 --- a/documentation/js/js_badusb.md +++ b/documentation/js/js_badusb.md @@ -1,33 +1,36 @@ -# js_badusb {#js_badusb} +# BadUSB module {#js_badusb} -# BadUSB module ```js let badusb = require("badusb"); ``` # Methods -## setup +## setup() Start USB HID with optional parameters. Should be called before all other methods. -### Parameters -Configuration object (optional): -- vid, pid (number): VID and PID values, both are mandatory -- mfr_name (string): Manufacturer name (32 ASCII characters max), optional -- prod_name (string): Product name (32 ASCII characters max), optional +**Parameters** -### Examples: +Configuration object *(optional)*: +- vid, pid (number): VID and PID values, both are mandatory +- mfrName (string): Manufacturer name (32 ASCII characters max), optional +- prodName (string): Product name (32 ASCII characters max), optional +- layoutPath (string): Path to keyboard layout file, optional + +**Examples** ```js // Start USB HID with default parameters badusb.setup(); // Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer and product strings not defined badusb.setup({ vid: 0xAAAA, pid: 0xBBBB }); // Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer string = "Flipper Devices", product string = "Flipper Zero" -badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper Devices", prod_name: "Flipper Zero" }); +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfrName: "Flipper Devices", prodName: "Flipper Zero" }); ``` -## isConnected +
+ +## isConnected() Returns USB connection state. -### Example: +**Example** ```js if (badusb.isConnected()) { // Do something @@ -36,15 +39,18 @@ if (badusb.isConnected()) { } ``` -## press +
+ +## press() Press and release a key. -### Parameters +**Parameters** + Key or modifier name, key code. -See a list of key names below. +See a [list of key names below](#js_badusb_keynames). -### Examples: +**Examples** ```js badusb.press("a"); // Press "a" key badusb.press("A"); // SHIFT + "a" @@ -54,58 +60,109 @@ badusb.press(98); // Press key with HID code (dec) 98 (Numpad 0 / Insert) badusb.press(0x47); // Press key with HID code (hex) 0x47 (Scroll lock) ``` -## hold +
+ +## hold() Hold a key. Up to 5 keys (excluding modifiers) can be held simultaneously. -### Parameters -Same as `press` +**Parameters** -### Examples: +Same as `press`. + +**Examples** ```js badusb.hold("a"); // Press and hold "a" key badusb.hold("CTRL", "v"); // Press and hold CTRL + "v" combo ``` -## release +
+ +## release() Release a previously held key. -### Parameters -Same as `press` +**Parameters** -Release all keys if called without parameters +Same as `press`. -### Examples: +Release all keys if called without parameters. + +**Examples** ```js badusb.release(); // Release all keys badusb.release("a"); // Release "a" key ``` -## print +
+ +## print() Print a string. -### Parameters -- A string to print -- (optional) delay between key presses +**Parameters** -### Examples: +- A string to print +- *(optional)* Delay between key presses + +**Examples** ```js badusb.print("Hello, world!"); // print "Hello, world!" badusb.print("Hello, world!", 100); // Add 100ms delay between key presses ``` +
-## println +## println() Same as `print` but ended with "ENTER" press. -### Parameters -- A string to print -- (optional) delay between key presses +**Parameters** -### Examples: +- A string to print +- *(optional)* Delay between key presses + +**Examples** ```js badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER" ``` +
-# Key names list +## altPrint() +Prints a string by Alt+Numpad method - works only on Windows! + +**Parameters** + +- A string to print +- *(optional)* delay between key presses + +**Examples** +```js +badusb.altPrint("Hello, world!"); // print "Hello, world!" +badusb.altPrint("Hello, world!", 100); // Add 100ms delay between key presses +``` +
+ +## altPrintln() +Same as `altPrint` but ended with "ENTER" press. + +**Parameters** + +- A string to print +- *(optional)* delay between key presses + +**Examples** +```js +badusb.altPrintln("Hello, world!"); // print "Hello, world!" and press "ENTER" +``` +
+ +## quit() +Releases usb, optional, but allows to interchange with usbdisk. + +**Examples** +```js +badusb.quit(); +usbdisk.start(...) +``` +
+ +# Key names list {#js_badusb_keynames} ## Modifier keys @@ -142,3 +199,4 @@ badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER" | TAB | | | MENU | Context menu key | | Fx | F1-F24 keys | +| NUMx | NUM0-NUM9 keys | diff --git a/documentation/js/js_builtin.md b/documentation/js/js_builtin.md index 9c59b9822..2d2f9417a 100644 --- a/documentation/js/js_builtin.md +++ b/documentation/js/js_builtin.md @@ -1,50 +1,315 @@ # Built-in methods {#js_builtin} -## require +## require() Load a module plugin. -### Parameters +**Parameters** - Module name -### Examples: +**Examples** ```js let serial = require("serial"); // Load "serial" module ``` -## delay -### Parameters +
+ +## delay() +**Parameters** - Delay value in ms -### Examples: +**Examples** ```js delay(500); // Delay for 500ms ``` -## print +
+ +## print() Print a message on a screen console. -### Parameters +**Parameters** The following argument types are supported: - String - Number - Bool - undefined -### Examples: +**Examples** ```js print("string1", "string2", 123); ``` +
-## console.log -## console.warn -## console.error -## console.debug +## Console object Same as `print`, but output to serial console only, with corresponding log level. -## to_string +### console.log() + +
+ +### console.warn() + +
+ +### console.error() + +
+ +### console.debug() + +
+ +## load() +Runs a JS file and returns value from it. + +**Parameters** +- The path to the file +- An optional object to use as the global scope while running this file + +**Examples** +```js +load("/ext/apps/Scripts/script.js"); +``` +
+ +## chr() +Convert an ASCII character number to string. + +**Examples** +```js +chr(65); // "A" +``` +
+ +## die() +Exit JavaScript with given message. + +**Examples** +```js +die("Some error occurred"); +``` +
+ +## parseInt() +Convert a string to number with an optional base. + +**Examples** +```js +parseInt("123"); // 123 +parseInt("7b", 16); // 123 +``` +
+ +## Number object + +### Number.toString() Convert a number to string with an optional base. -### Examples: +**Examples** ```js -to_string(123) // "123" -to_string(123, 16) // "0x7b" +let num = 123; +num.toString(); // "123" +num.toString(16); // "0x7b" +``` +
+ +## ArrayBuffer object + +**Fields** + +- byteLength: The length of the buffer in bytes +
+ +### ArrayBuffer.slice() +Creates an `ArrayBuffer` that contains a sub-part of the buffer. + +**Parameters** +- The index to start the new buffer at +- An optional non-inclusive index of where to stop the new buffer + +**Examples** +```js +Uint8Array([1, 2, 3]).buffer.slice(0, 1) // ArrayBuffer([1]) +``` +
+ +## DataView objects +Wrappers around `ArrayBuffer` objects, with dedicated types such as: +- `Uint8Array` +- `Int8Array` +- `Uint16Array` +- `Int16Array` +- `Uint32Array` +- `Int32Array` + +**Fields** + +- byteLength: The length of the buffer in bytes +- length: The length of the buffer in typed elements +- buffer: The underlying `ArrayBuffer` +
+ +## Array object + +**Fields** + +- length: How many elements there are in the array +
+ +### Array.splice() +Removes elements from the array and returns them in a new array. + +**Parameters** +- The index to start taking elements from +- An optional count of how many elements to take + +**Examples** +```js +let arr = [1, 2, 3]; +arr.splice(1); // [2, 3] +arr; // [1] +``` +
+ +### Array.push() +Adds a value to the end of the array. + +**Examples** +```js +let arr = [1, 2]; +arr.push(3); +arr; // [1, 2, 3] +``` +
+ +## String object + +**Fields** + +- length: How many characters there are in the string +
+ +### String.charCodeAt() +Returns the character code at an index in the string. + +**Examples** +```js +"A".charCodeAt(0) // 65 +``` +
+ +### String.at() +Same as `String.charCodeAt()`. +
+ +### String.indexOf() +Return index of first occurrence of substr within the string or `-1` if not found. + +**Parameters** +- Substring to search for +- Optional index to start searching from + +**Examples** +```js +"Example".indexOf("amp") // 2 +``` +
+ +### String.slice() +Return a substring between two indices. + +**Parameters** +- The index to start the new string at +- An optional non-inclusive index of where to stop the new string + +**Examples** +```js +"Example".slice(2) // "ample" +``` +
+ +### String.toUpperCase() +Transforms the string to upper case. + +**Examples** +```js +"Example".toUpperCase() // "EXAMPLE" +``` +
+ +### String.toLowerCase() +Transforms the string to lower case. + +**Examples** +```js +"Example".toLowerCase() // "example" +``` +
+ +## __dirname +Path to the directory containing the current script. + +**Examples** +```js +print(__dirname); // /ext/apps/Scripts +``` +
+ +## __filename +Path to the current script file. + +**Examples** +```js +print(__filename); // /ext/apps/Scripts/path.js +``` +
+ +# SDK compatibility methods {#js_builtin_sdk_compatibility} + +## sdkCompatibilityStatus() +Checks compatibility between the script and the JS SDK that the firmware provides. + +**Returns** +- `"compatible"` if the script and the JS SDK are compatible +- `"firmwareTooOld"` if the expected major version is larger than the version of the firmware, or if the expected minor version is larger than the version of the firmware +- `"firmwareTooNew"` if the expected major version is lower than the version of the firmware + +**Examples** +```js +sdkCompatibilityStatus(0, 3); // "compatible" +``` +
+ +## isSdkCompatible() +Checks compatibility between the script and the JS SDK that the firmware provides in a boolean fashion. + +**Examples** +```js +isSdkCompatible(0, 3); // true +``` +
+ +## checkSdkCompatibility() +Asks the user whether to continue executing the script if the versions are not compatible. Does nothing if they are. + +**Examples** +```js +checkSdkCompatibility(0, 3); +``` +
+ +## doesSdkSupport() +Checks whether all of the specified extra features are supported by the interpreter. + +**Examples** +```js +doesSdkSupport(["gui-widget"]); // true +``` +
+ +## checkSdkFeatures() +Checks whether all of the specified extra features are supported by the interpreter, asking the user if they want to continue running the script if they're not. + +**Examples** +```js +checkSdkFeatures(["gui-widget"]); ``` diff --git a/documentation/js/js_data_types.md b/documentation/js/js_data_types.md index bd3bb1f42..2a94ba5d2 100644 --- a/documentation/js/js_data_types.md +++ b/documentation/js/js_data_types.md @@ -7,7 +7,7 @@ Here is a list of common data types used by mJS. - foreign — C function or data pointer - undefined - null -- object — a data structure with named fields -- array — special type of object, all items have indexes and equal types +- Object — a data structure with named fields +- Array — special type of object, all items have indexes and equal types - ArrayBuffer — raw data buffer - DataView — provides interface for accessing ArrayBuffer contents diff --git a/documentation/js/js_developing_apps_using_js_sdk.md b/documentation/js/js_developing_apps_using_js_sdk.md new file mode 100644 index 000000000..1a764e1df --- /dev/null +++ b/documentation/js/js_developing_apps_using_js_sdk.md @@ -0,0 +1,98 @@ +# Developing apps using JavaScript SDK {#js_developing_apps_using_js_sdk} + +In the [previous guide](#js_your_first_js_app), we learned how to create and run a JavaScript app on Flipper Zero. However, when debugging a script, you often need to repeatedly modify the code and test it on the device. While you can use qFlipper for this, it involves a lot of repetitive steps. Fortunately, there's a more efficient alternative — the Flipper Zero JavaScript SDK, a set of tools that simplify app development in JavaScript. + +Main features of the Flipper Zero JavaScript SDK: + +* [Loading and running an app with a single command](#js_sdk_run_app) +* [Code completion](#js_sdk_code_completion) +* [JS code minifier (compressor)](#js_sdk_js_minifier) + +In this guide, we'll install the JavaScript SDK and learn how to run JavaScript apps on Flipper Zero using it. + +## How to get JavaScript SDK + +The JavaScript SDK for Flipper Zero is distributed as an [NPM package](npmjs.com/package/\@flipperdevices/fz-sdk), so you can install it using a package manager like npm, pnpm, or yarn. You'll also need Node.js, a JavaScript runtime environment required for the NPM package manager to work. + +> [!note] +> In this guide, we'll use **npm**, the default package manager for Node.js. + +Follow these steps: + +1. Install **Node.js + npm** on your PC. Check out this [official Downloads page](https://nodejs.org/en/download/package-manager), select your OS and preferences, and run the provided commands in your terminal. + +2. Open a terminal in the folder where you want to store your project. + +3. Run the `npx @flipperdevices/create-fz-app@latest` command to create a JavaScript app template and include the JavaScript SDK into it. This command will launch an interactive wizard. You'll need to specify the project name and choose a package manager (in our case, **npm**). + +You'll now find a JavaScript app template in your project folder, alongside the JavaScript SDK package, all necessary dependencies and configs. The app code will be in the `index.ts` file. + +Now, let's take a look at the main features of the Flipper Zero JavaScript SDK. + +## Running your app {#js_sdk_run_app} + +To run the application: + +1. Connect your Flipper Zero to your PC via USB. + +2. Open a terminal in your app's folder. + +3. Run the `npm start` command to copy the JS file to Flipper Zero and run it. + +\image html js_sdk_npm_start.jpg width=800 + +You'll see output messages from the `print()` function in the terminal. + +## Updating your app {#js_sdk_update_app} + +After making changes to your app's code, simply run `npm start` again. As long as your Flipper Zero is still connected, the updated app will launch, and the old `.js` file on Flipper Zero will be replaced with the new version. + + +## Other JavaScript SDK features + +As you can see, it's quite easy to launch and update your app with a single command. Now let's explore two more important features of the Flipper Zero JavaScript SDK: **code completion** and **JS minifier**. + + +### Code completion {#js_sdk_code_completion} + +Code completion helps speed up the development process by automatically suggesting code as you type, reducing the need to refer to documentation. + +\image html js_sdk_code_completion.jpg width=800 + +> [!note] +> Code completion works in code editors and IDEs that support Language Server, for example, [VS Code](https://code.visualstudio.com/). + + +### JS minifier {#js_sdk_js_minifier} + +The JS minifier reduces the size of JavaScript files by removing unnecessary characters (like spaces, tabs and line breaks) and shortening variable names. This can make your scripts run a bit faster without changing their logic. + +However, it has a drawback — it can make debugging harder, as error messages in minified files are harder to read in larger applications. For this reason, it's recommended to disable the JS minifier during debugging and it's disabled by default. To enable it, set the `minify` parameter to `true` in the `fz-sdk.config.json5` file in your app folder. This will minify your JavaScript app before loading it onto Flipper Zero. + + +## Differences with normal Flipper JavaScript + +With the Flipper JavaScript SDK, you will be developing in **TypeScript**. This means that you get a better development experience, with more accurate code completion and warnings when variable types are incompatible, but it also means your code will be different from basic Flipper JS. + +Some things to look out for: +- Importing modules: + - Instead of `let module = require("module");` + - You will use `import * as module from "@flipperdevices/fz-sdk/module";` +- Multiple source code files: + - The Flipper JavaScript SDK does not yet support having multiple `.ts` files and importing them + - You can use `load()`, but this will not benefit from TypeScript type checking +- Casting values: + - Some Flipper JavaScript functions will return generic types + - For example `eventLoop.subscribe()` will run your callback with a generic `Item` type + - In some cases you might need to cast these values before using them, you can do this by: + - Inline casting: `item` + - Declare with new type: `let text = item as string;` + +When you upload the script to Flipper with `npm start`, it gets transpiled to normal JavaScript and optionally minified (see below). If you're looking to share your script with others, this is what you should give them to run. + + +## What's next? + +You've learned how to run and debug simple JavaScript apps. But how can you access Flipper Zero's hardware from your JS code? For that, you'll need to use JS modules — which we'll cover in the next guide. + +**Next step:** [Using JavaScript modules](#js_using_js_modules) diff --git a/documentation/js/js_event_loop.md b/documentation/js/js_event_loop.md index 9519478c0..7da3f9341 100644 --- a/documentation/js/js_event_loop.md +++ b/documentation/js/js_event_loop.md @@ -1,28 +1,28 @@ -# js_event_loop {#js_event_loop} - -# Event Loop module -```js -let eventLoop = require("event_loop"); -``` +# Event Loop module {#js_event_loop} The event loop is central to event-based programming in many frameworks, and our JS subsystem is no exception. It is a good idea to familiarize yourself with the event loop first before using any of the advanced modules (e.g. GPIO and GUI). +```js +let eventLoop = require("event_loop"); +``` + ## Conceptualizing the event loop -If you ever wrote JavaScript before, you have definitely seen callbacks. It's -when a function accepts another function (usually an anonymous one) as one of -the arguments, which it will call later on, e.g. when an event happens or when +If you've ever written JavaScript code before, you've definitely seen callbacks. It's +when a function takes another function (usually an anonymous one) as one of +the arguments, which it will call later, e.g. when an event happens or when data becomes ready: ```js setTimeout(function() { console.log("Hello, World!") }, 1000); ``` -Many JavaScript engines employ a queue that the runtime fetches events from as +Many JavaScript engines employ a queue from which the runtime fetches events as they occur, subsequently calling the corresponding callbacks. This is done in a long-running loop, hence the name "event loop". Here's the pseudocode for a typical event loop: -```js + +\code{.js} while(loop_is_running()) { if(event_available_in_queue()) { let event = fetch_event_from_queue(); @@ -34,12 +34,14 @@ while(loop_is_running()) { sleep_until_any_event_becomes_available(); } } -``` +\endcode Most JS runtimes enclose the event loop within themselves, so that most JS -programmers does not even need to be aware of its existence. This is not the +programmers don't even need to be aware of its existence. This is not the case with our JS subsystem. +--- + # Example This is how one would write something similar to the `setTimeout` example above: ```js @@ -84,18 +86,22 @@ Because we have two extra arguments, if we return anything other than an array of length 2, the arguments will be kept as-is for the next call. The first two arguments that get passed to our callback are: - - The subscription manager that lets us `.cancel()` our subscription + - The subscription manager that lets us `.cancel()` our subscription. - The event item, used for events that have extra data. Timer events do not, they just produce `undefined`. +--- + # API reference -## `run` +## run() Runs the event loop until it is stopped with `stop`. -## `subscribe` +
+ +## subscribe() Subscribes a function to an event. -### Parameters +**Parameters** - `contract`: an event source identifier - `callback`: the function to call when the event happens - extra arguments: will be passed as extra arguments to the callback @@ -108,35 +114,45 @@ to `undefined`. The callback may return an array of the same length as the count of the extra arguments to modify them for the next time that the event handler is called. Any other returns values are discarded. -### Returns +**Returns** + A `SubscriptionManager` object: - `SubscriptionManager.cancel()`: unsubscribes the callback from the event -### Warning +**Warning** + Each event source may only have one callback associated with it. -## `stop` +
+ +## stop() Stops the event loop. -## `timer` +
+ +## timer() Produces an event source that fires with a constant interval either once or indefinitely. -### Parameters +**Parameters** - `mode`: either `"oneshot"` or `"periodic"` - `interval`: the timeout (for `"oneshot"`) timers or the period (for `"periodic"` timers) -### Returns +**Returns** + A `Contract` object, as expected by `subscribe`'s first parameter. -## `queue` +
+ +## queue() Produces a queue that can be used to exchange messages. -### Parameters +**Parameters** - `length`: the maximum number of items that the queue may contain -### Returns +**Returns** + A `Queue` object: - `Queue.send(message)`: - `message`: a value of any type that will be placed at the end of the queue diff --git a/documentation/js/js_flipper.md b/documentation/js/js_flipper.md new file mode 100644 index 000000000..a4da23868 --- /dev/null +++ b/documentation/js/js_flipper.md @@ -0,0 +1,52 @@ +# Flipper module {#js_flipper} + +The module contains methods and values to query device information and properties. Call the `require` function to load the module before first using its methods: + +```js +let flipper = require("flipper"); +``` + +# Values + +## firmwareVendor +String representing the firmware installed on the device. +Original firmware reports `"flipperdevices"`. +Do **NOT** use this to check the presence or absence of features, refer to [other ways to check SDK compatibility](#js_builtin_sdk_compatibility). + +## jsSdkVersion +Version of the JavaScript SDK. +Do **NOT** use this to check the presence or absence of features, refer to [other ways to check SDK compatibility](#js_builtin_sdk_compatibility). + +
+ +--- + +# Methods + +## getModel() +Returns the device model. + +**Example** +```js +flipper.getModel(); // "Flipper Zero" +``` + +
+ +## getName() +Returns the name of the virtual dolphin. + +**Example** +```js +flipper.getName(); // "Fur1pp44" +``` + +
+ +## getBatteryCharge() +Returns the battery charge percentage. + +**Example** +```js +flipper.getBatteryCharge(); // 100 +``` diff --git a/documentation/js/js_gpio.md b/documentation/js/js_gpio.md index aa444bacd..d058d7329 100644 --- a/documentation/js/js_gpio.md +++ b/documentation/js/js_gpio.md @@ -1,14 +1,12 @@ -# js_gpio {#js_gpio} +# GPIO module {#js_gpio} + +The module allows you to control GPIO pins of the expansion connector on Flipper Zero. Call the `require` function to load the module before first using its methods. This module depends on the `event_loop` module, so it **must** be imported after `event_loop` is imported: -# GPIO module ```js let eventLoop = require("event_loop"); let gpio = require("gpio"); ``` -This module depends on the `event_loop` module, so it _must_ only be imported -after `event_loop` is imported. - # Example ```js let eventLoop = require("event_loop"); @@ -23,21 +21,26 @@ led.write(false); delay(1000); ``` +--- + # API reference -## `get` +## get() Gets a `Pin` object that can be used to manage a pin. -### Parameters +**Parameters** - `pin`: pin identifier (examples: `"pc3"`, `7`, `"pa6"`, `3`) -### Returns -A `Pin` object +**Returns** -## `Pin` object -### `Pin.init()` -Configures a pin +A `Pin` object. -#### Parameters +
+ +## Pin object +### Pin.init() +Configures a pin. + +**Parameters** - `mode`: `Mode` object: - `direction` (required): either `"in"` or `"out"` - `outMode` (required for `direction: "out"`): either `"open_drain"` or @@ -48,30 +51,71 @@ Configures a pin `"rising"`, `"falling"` or `"both"` - `pull` (optional): either `"up"`, `"down"` or unset -### `Pin.write()` -Writes a digital value to a pin configured with `direction: "out"` +
-#### Parameters +### Pin.write() +Writes a digital value to a pin configured with `direction: "out"`. + +**Parameters** - `value`: boolean logic level to write -### `Pin.read()` +
+ +### Pin.read() Reads a digital value from a pin configured with `direction: "in"` and any -`inMode` except `"analog"` +`inMode` except `"analog"`. -#### Returns -Boolean logic level +**Returns** -### `Pin.readAnalog()` +Boolean logic level. + +
+ +### Pin.readAnalog() Reads an analog voltage level in millivolts from a pin configured with -`direction: "in"` and `inMode: "analog"` +`direction: "in"` and `inMode: "analog"`. -#### Returns -Voltage on pin in millivolts +**Returns** -### `Pin.interrupt()` +Voltage on pin in millivolts. + +
+ +### Pin.interrupt() Attaches an interrupt to a pin configured with `direction: "in"` and -`inMode: "interrupt"` or `"event"` +`inMode: "interrupt"` or `"event"`. + +**Returns** -#### Returns An event loop `Contract` object that identifies the interrupt event source. The event does not produce any extra data. + +### Pin.isPwmSupported() +Determines whether this pin supports PWM. +If `false`, all other PWM-related methods on this pin will throw an error when called. + +**Returns** + +Boolean value. + +### Pin.pwmWrite() +Sets PWM parameters and starts the PWM. +Configures the pin with `{ direction: "out", outMode: "push_pull" }`. +Throws an error if PWM is not supported on this pin. + +**Parameters** + - `freq`: Frequency in Hz + - `duty`: Duty cycle in % + +### Pin.isPwmRunning() +Determines whether PWM is running. +Throws an error if PWM is not supported on this pin. + +**Returns** + +Boolean value. + +### Pin.pwmStop() +Stops PWM. +Does not restore previous pin configuration. +Throws an error if PWM is not supported on this pin. diff --git a/documentation/js/js_gui.md b/documentation/js/js_gui.md index 4d2d2497a..b04255008 100644 --- a/documentation/js/js_gui.md +++ b/documentation/js/js_gui.md @@ -1,13 +1,27 @@ -# js_gui {#js_gui} +# GUI module {#js_gui} + +The module allows you to use GUI (graphical user interface) in concepts off the Flipper Zero firmware. Call the `require` function to load the module before first using its methods. This module depends on the `event_loop` module, so it **must** be imported after the `event_loop` import: -# GUI module ```js let eventLoop = require("event_loop"); let gui = require("gui"); ``` +## Submodules -This module depends on the `event_loop` module, so it _must_ only be imported -after `event_loop` is imported. +GUI module has several submodules: + +- @subpage js_gui__byte_input — Keyboard-like hex input +- @subpage js_gui__dialog — Dialog with up to 3 options +- @subpage js_gui__empty_screen — Just empty screen +- @subpage js_gui__file_picker — Displays a file selection prompt +- @subpage js_gui__icon — Retrieves and loads icons for use in GUI +- @subpage js_gui__loading — Displays an animated hourglass icon +- @subpage js_gui__submenu — Displays a scrollable list of clickable textual entries +- @subpage js_gui__text_box — Simple multiline text box +- @subpage js_gui__text_input — Keyboard-like text input +- @subpage js_gui__widget — Displays a combination of custom elements on one screen + +--- ## Conceptualizing GUI ### Event loop @@ -27,23 +41,23 @@ always access the canvas through a viewport. In Flipper's terminology, a "View" is a fullscreen design element that assumes control over the entire viewport and all input events. Different types of views are available (not all of which are unfortunately currently implemented in JS): -| View | Has JS adapter? | -|----------------------|------------------| -| `button_menu` | ❌ | -| `button_panel` | ❌ | -| `byte_input` | ❌ | -| `dialog_ex` | ✅ (as `dialog`) | -| `empty_screen` | ✅ | -| `file_browser` | ❌ | -| `loading` | ✅ | -| `menu` | ❌ | -| `number_input` | ❌ | -| `popup` | ❌ | -| `submenu` | ✅ | -| `text_box` | ✅ | -| `text_input` | ✅ | -| `variable_item_list` | ❌ | -| `widget` | ❌ | +| View | Has JS adapter? | +|----------------------|-----------------------| +| `button_menu` | ❌ | +| `button_panel` | ❌ | +| `byte_input` | ✅ | +| `dialog_ex` | ✅ (as `dialog`) | +| `empty_screen` | ✅ | +| `file_browser` | ✅ (as `file_picker`) | +| `loading` | ✅ | +| `menu` | ❌ | +| `number_input` | ❌ | +| `popup` | ❌ | +| `submenu` | ✅ | +| `text_box` | ✅ | +| `text_input` | ✅ | +| `variable_item_list` | ❌ | +| `widget` | ✅ | In JS, each view has its own set of properties (or just "props"). The programmer can manipulate these properties in two ways: @@ -69,7 +83,9 @@ a GUI application: | ViewDispatcher | Common UI elements that fit with the overall look of the system | ✅ | | SceneManager | Additional navigation flow management for complex applications | ❌ | -# Example +--- + +## Example An example with three different views using the ViewDispatcher approach: ```js let eventLoop = require("event_loop"); @@ -114,48 +130,101 @@ gui.viewDispatcher.switchTo(views.demos); eventLoop.run(); ``` +--- + # API reference -## `viewDispatcher` +## viewDispatcher The `viewDispatcher` constant holds the `ViewDispatcher` singleton. -### `viewDispatcher.switchTo(view)` -Switches to a view, giving it control over the display and input +
-#### Parameters +### viewDispatcher.switchTo(view) +Switches to a view, giving it control over the display and input. + +**Parameters** - `view`: the `View` to switch to -### `viewDispatcher.sendTo(direction)` +
+ +### viewDispatcher.sendTo(direction) Sends the viewport that the dispatcher manages to the front of the stackup (effectively making it visible), or to the back (effectively making it -invisible) +invisible). -#### Parameters +**Parameters** - `direction`: either `"front"` or `"back"` -### `viewDispatcher.sendCustom(event)` -Sends a custom number to the `custom` event handler +
-#### Parameters +### viewDispatcher.sendCustom(event) +Sends a custom number to the `custom` event handler. + +**Parameters** - `event`: number to send -### `viewDispatcher.custom` +
+ +### viewDispatcher.custom An event loop `Contract` object that identifies the custom event source, -triggered by `ViewDispatcher.sendCustom(event)` +triggered by `ViewDispatcher.sendCustom(event)`. -### `viewDispatcher.navigation` +
+ +### viewDispatcher.navigation An event loop `Contract` object that identifies the navigation event source, -triggered when the back key is pressed +triggered when the back key is pressed. -## `ViewFactory` -When you import a module implementing a view, a `ViewFactory` is instantiated. -For example, in the example above, `loadingView`, `submenuView` and `emptyView` -are view factories. +
-### `ViewFactory.make()` -Creates an instance of a `View` +### viewDispatcher.currentView +The `View` object currently being shown. -### `ViewFactory.make(props)` -Creates an instance of a `View` and assigns initial properties from `props` +
-#### Parameters +## ViewFactory +When you import a module implementing a view, a `ViewFactory` is instantiated. For example, in the example above, `loadingView`, `submenuView` and `emptyView` are view factories. + +
+ +### ViewFactory.make() +Creates an instance of a `View`. + +
+ +### ViewFactory.makeWith(props, children) +Creates an instance of a `View` and assigns initial properties from `props` and optionally a list of children. + +**Parameters** - `props`: simple key-value object, e.g. `{ header: "Header" }` + - `children`: optional array of children, e.g. `[ { element: "button", button: "right", text: "Back" } ]` + +## View +When you call `ViewFactory.make()` or `ViewFactory.makeWith()`, a `View` is instantiated. For example, in the example above, `views.loading`, `views.demos` and `views.empty` are views. + +
+ +### View.set(property, value) +Assign value to property by name. + +**Parameters** + - `property`: name of the property to change + - `value`: value to assign to the property + +
+ +### View.addChild(child) +Adds a child to the `View`. + +**Parameters** + - `child`: the child to add, e.g. `{ element: "button", button: "right", text: "Back" }` + +The format of the `child` parameter depends on the type of View that you're working with. Look in the View documentation. + +### View.resetChildren() +Removes all children from the `View`. + +### View.setChildren(children) +Removes all previous children from the `View` and assigns new children. + +**Parameters** + - `children`: the array of new children, e.g. `[ { element: "button", button: "right", text: "Back" } ]` diff --git a/documentation/js/js_gui__byte_input.md b/documentation/js/js_gui__byte_input.md new file mode 100644 index 000000000..abbf1ccad --- /dev/null +++ b/documentation/js/js_gui__byte_input.md @@ -0,0 +1,32 @@ +# Byte input GUI view {#js_gui__byte_input} + +Displays a hexadecimal keyboard. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let byteInputView = require("gui/byte_input"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they **must** be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +## Example +For an example refer to the `gui.js` example script. + +## View props + +| Prop | Type | Description | +|-------------|--------|--------------------------------------------------| +| `length` | `number` | The length in bytes of the buffer to modify. | +| `header` | `string` | A single line of text that appears above the keyboard. | +| `defaultData` | `string` | Data to show by default. | + +## View events + +| Item | Type | Description | +|-------------|--------|--------------------------------------------------| +| `input` | `ArrayBuffer` | Fires when the user selects the "Save" button. | diff --git a/documentation/js/js_gui__dialog.md b/documentation/js/js_gui__dialog.md index 445e71128..4f26cfa1b 100644 --- a/documentation/js/js_gui__dialog.md +++ b/documentation/js/js_gui__dialog.md @@ -1,6 +1,5 @@ -# js_gui__dialog {#js_gui__dialog} +# Dialog GUI view {#js_gui__dialog} -# Dialog GUI view Displays a dialog with up to three options. Sample screenshot of the view @@ -12,42 +11,24 @@ let dialogView = require("gui/dialog"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. # Example -For an example refer to the `gui.js` example script. +For an example, refer to the `gui.js` example script. # View props -## `header` -Text that appears in bold at the top of the screen -Type: `string` - -## `text` -Text that appears in the middle of the screen - -Type: `string` - -## `left` -Text for the left button. If unset, the left button does not show up. - -Type: `string` - -## `center` -Text for the center button. If unset, the center button does not show up. - -Type: `string` - -## `right` -Text for the right button. If unset, the right button does not show up. - -Type: `string` +| **Prop** | **Type** | **Description** | +|------------|-----------|----------------------------------------------------------------| +| `header` | string | Text that appears in bold at the top of the screen. | +| `text` | string | Text that appears in the middle of the screen. | +| `left` | string | Text for the left button. If unset, the left button does not show up. | +| `center` | string | Text for the center button. If unset, the center button does not show up. | +| `right` | string | Text for the right button. If unset, the right button does not show up. | # View events -## `input` -Fires when the user presses on either of the three possible buttons. The item -contains one of the strings `"left"`, `"center"` or `"right"` depending on the -button. -Item type: `string` +| Item | Type | Description | +|----------|--------|-----------------------------------------------------------------------------| +| `input` | `string`| Fires when the user presses on either of the three possible buttons. The item contains one of the strings `"left"`, `"center"`, or `"right"` depending on the button. | diff --git a/documentation/js/js_gui__empty_screen.md b/documentation/js/js_gui__empty_screen.md index f9fd12553..6ee6ba20e 100644 --- a/documentation/js/js_gui__empty_screen.md +++ b/documentation/js/js_gui__empty_screen.md @@ -1,7 +1,6 @@ -# js_gui__empty_screen {#js_gui__empty_screen} +# Empty Screen GUI view {#js_gui__empty_screen} -# Empty Screen GUI View -Displays nothing. +Displays an empty screen. Sample screenshot of the view @@ -12,11 +11,11 @@ let emptyView = require("gui/empty_screen"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. # Example -For an example refer to the GUI example. +For an example, refer to the GUI example. # View props This view does not have any props. diff --git a/documentation/js/js_gui__file_picker.md b/documentation/js/js_gui__file_picker.md new file mode 100644 index 000000000..a0854f0de --- /dev/null +++ b/documentation/js/js_gui__file_picker.md @@ -0,0 +1,20 @@ +# File Picker GUI prompt {#js_gui__file_picker} + +Allows asking the user to select a file. +It is not GUI view like other JS GUI views, rather just a function that shows a prompt. + +# Example +For an example, refer to the `gui.js` example script. + +# API reference + +## pickFile() +Displays a file picker and returns the selected file, or undefined if cancelled. + +**Parameters** + - `basePath`: the path to start at + - `extension`: the file extension(s) to show (like `.sub`, `.iso|.img`, `*`) + +**Returns** + +A `string` path, or `undefined`. diff --git a/documentation/js/js_gui__icon.md b/documentation/js/js_gui__icon.md new file mode 100644 index 000000000..2c6c4c5b7 --- /dev/null +++ b/documentation/js/js_gui__icon.md @@ -0,0 +1,32 @@ +# GUI Icons {#js_gui__icon} + +Retrieves and loads icons for use with GUI views such as [Dialog](#js_gui__dialog). + +# Example +For an example, refer to the `gui.js` example script. + +# API reference + +## getBuiltin() +Gets a built-in firmware icon by its name. +Not all icons are supported, currently only `"DolphinWait_59x54"` and `"js_script_10px"` are available. + +**Parameters** + - `icon`: name of the icon + +**Returns** + +An `IconData` object. + +
+ +## loadFxbm() +Loads a `.fxbm` icon (XBM Flipper sprite, from `flipperzero-game-engine`) from file. +It will be automatically unloaded when the script exits. + +**Parameters** + - `path`: path to the `.fxbm` file + +**Returns** + +An `IconData` object. diff --git a/documentation/js/js_gui__loading.md b/documentation/js/js_gui__loading.md index 52f1cea49..35ea987c1 100644 --- a/documentation/js/js_gui__loading.md +++ b/documentation/js/js_gui__loading.md @@ -1,8 +1,6 @@ -# js_gui__loading {#js_gui__loading} +# Loading GUI view {#js_gui__loading} -# Loading GUI View -Displays an animated hourglass icon. Suppresses all `navigation` events, making -it impossible for the user to exit the view by pressing the back key. +Displays an animated hourglass icon. Suppresses all `navigation` events, making it impossible for the user to exit the view by pressing the BACK key. Sample screenshot of the view @@ -13,7 +11,7 @@ let loadingView = require("gui/loading"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. # Example diff --git a/documentation/js/js_gui__submenu.md b/documentation/js/js_gui__submenu.md index 28c1e65af..9105330a2 100644 --- a/documentation/js/js_gui__submenu.md +++ b/documentation/js/js_gui__submenu.md @@ -1,6 +1,5 @@ -# js_gui__submenu {#js_gui__submenu} +# Submenu GUI view {#js_gui__submenu} -# Submenu GUI view Displays a scrollable list of clickable textual entries. Sample screenshot of the view @@ -12,26 +11,19 @@ let submenuView = require("gui/submenu"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. -# Example -For an example refer to the GUI example. +## View props -# View props -## `header` -Single line of text that appears above the list +| Property | Type | Description | +|----------|-----------|-------------------------------------------| +| `header` | `string` | A single line of text that appears above the list. | +| `items` | `string[]`| The list of options. | -Type: `string` -## `items` -The list of options +## View events -Type: `string[]` - -# View events -## `chosen` -Fires when an entry has been chosen by the user. The item contains the index of -the entry. - -Item type: `number` +| Item | Type | Description | +|----------|---------|---------------------------------------------------------------| +| `chosen` | `number`| Fires when an entry has been chosen by the user. The item contains the index of the entry. | diff --git a/documentation/js/js_gui__text_box.md b/documentation/js/js_gui__text_box.md index bdad8d8b3..4c94b8c3c 100644 --- a/documentation/js/js_gui__text_box.md +++ b/documentation/js/js_gui__text_box.md @@ -1,6 +1,5 @@ -# js_gui__text_box {#js_gui__text_box} +# Text box GUI view {#js_gui__text_box} -# Text box GUI view Displays a scrollable read-only text field. Sample screenshot of the view @@ -12,14 +11,16 @@ let textBoxView = require("gui/text_box"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. -# Example -For an example refer to the `gui.js` example script. +## Example +For an example, refer to the `gui.js` example script. -# View props -## `text` -Text to show in the text box. +## View props -Type: `string` +| Prop | Type | Description | +|----------|---------|------------------------------------| +| `text` | `string`| Text to show in the text box. | +| `font` | `string`| The font to display the text in (`"text"` or `"hex"`). | +| `focus` | `string`| The initial focus of the text box (`"start"` or `"end"`). | diff --git a/documentation/js/js_gui__text_input.md b/documentation/js/js_gui__text_input.md index 030579e2e..c63297c12 100644 --- a/documentation/js/js_gui__text_input.md +++ b/documentation/js/js_gui__text_input.md @@ -1,6 +1,5 @@ -# js_gui__text_input {#js_gui__text_input} +# Text input GUI view {#js_gui__text_input} -# Text input GUI view Displays a keyboard. Sample screenshot of the view @@ -12,33 +11,24 @@ let textInputView = require("gui/text_input"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. -# Example -For an example refer to the `gui.js` example script. +## Example +For an example, refer to the `gui.js` example script. -# View props -## `minLength` -Smallest allowed text length +## View props -Type: `number` +| Prop | Type | Description | +|-------------|--------|--------------------------------------------------| +| `minLength` | `number` | The shortest allowed text length. | +| `maxLength` | `number` | The longest allowed text length.
Default: `32` | +| `header` | `string` | A single line of text that appears above the keyboard. | +| `defaultText` | `string` | Text to show by default. | +| `defaultTextClear` | `boolean` | Whether to clear the default text on next character typed. | -## `maxLength` -Biggest allowed text length +## View events -Type: `number` - -Default: `32` - -## `header` -Single line of text that appears above the keyboard - -Type: `string` - -# View events -## `input` -Fires when the user selects the "save" button and the text matches the length -constrained by `minLength` and `maxLength`. - -Item type: `string` +| Item | Type | Description | +|-------------|--------|--------------------------------------------------| +| `input` | `string` | Fires when the user selects the "Save" button and the text matches the length constrained by `minLength` and `maxLength`. | diff --git a/documentation/js/js_gui__widget.md b/documentation/js/js_gui__widget.md new file mode 100644 index 000000000..9ea3e4dfa --- /dev/null +++ b/documentation/js/js_gui__widget.md @@ -0,0 +1,37 @@ +# Widget GUI view {#js_gui__widget} + +Displays a combination of custom elements on one screen. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let widgetView = require("gui/widget"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they **must** be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +## Example +For an example, refer to the `gui.js` example script. + +## View props +This view does not have any props. + +## Children +This view has the elements as its children. +Elements are objects with properties to define them, in the form `{ element: "type", ...properties }` (e.g. `{ element: "button", button: "right", text: "Back" }`). + +| **Element Type** | **Properties** | **Description** | +|------------------|----------------|------------------------------------------------| +| `string_multiline` | `x` (number), `y` (number)
`align` ((`"t"`, `"c"`, `"b"`) + (`"l"`, `"m"`, `"r"`))
`font` (`"primary"`, `"secondary"`, `"keyboard"`, `"big_numbers"`)
`text` (string) | String of text that can span multiple lines. | +| `string` | `x` (number), `y` (number)
`align` ((`"t"`, `"c"`, `"b"`) + (`"l"`, `"m"`, `"r"`))
`font` (`"primary"`, `"secondary"`, `"keyboard"`, `"big_numbers"`)
`text` (string) | String of text. | +| `text_box` | `x` (number), `y` (number)
`w` (number), `h` (number)
`align` ((`"t"`, `"c"`, `"b"`) + (`"l"`, `"m"`, `"r"`))
`text` (string)
`stripToDots` (boolean) | Box of with text that can be scrolled vertically. | +| `text_scroll` | `x` (number), `y` (number)
`w` (number), `h` (number)
`text` (string) | Text that can be scrolled vertically. | +| `button` | `text` (string)
`button` (`"left"`, `"center"`, `"right"`) | Button at the bottom of the screen. | +| `icon` | `x` (number), `y` (number)
`iconData` ([IconData](#js_gui__icon)) | Display an icon. | +| `rect` | `x` (number), `y` (number)
`w` (number), `h` (number)
`radius` (number), `fill` (boolean) | Draw a rectangle, optionally rounded and filled. | +| `circle` | `x` (number), `y` (number)
`radius` (number), `fill` (boolean) | Draw a circle, optionally filled. | +| `line` | `x1` (number), `y1` (number)
`x2` (number), `y2` (number) | Draw a line between 2 points. | diff --git a/documentation/js/js_math.md b/documentation/js/js_math.md index ca16a9111..fd7bede17 100644 --- a/documentation/js/js_math.md +++ b/documentation/js/js_math.md @@ -1,9 +1,11 @@ -# js_math {#js_math} +# Math module {#js_math} + +The module contains mathematical methods and constants. Call the `require` function to load the module before first using its methods: -# Math module ```js let math = require("math"); ``` + # Constants ## PI @@ -16,204 +18,248 @@ The number e (Euler's number) = 2.71828182845904523536028747135266250. The smallest number that satisfies the condition: 1.0 + EPSILON != 1.0. EPSILON = 2.2204460492503131e-16. +
+ +--- + # Methods -## abs +## abs() Return the absolute value of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The absolute value of `x`. If `x` is negative (including -0), returns `-x`. Otherwise, returns `x`. The result is therefore always a positive number or 0. -### Example +**Example** ```js math.abs(-5); // 5 ``` -## acos +
+ +## acos() Return the inverse cosine (in radians) of a number. -### Parameters +**Parameters** - x: A number between -1 and 1, inclusive, representing the angle's cosine value -### Returns +**Returns** + The inverse cosine (angle in radians between 0 and π, inclusive) of `x`. If `x` is less than -1 or greater than 1, returns `NaN`. -### Example +**Example** ```js math.acos(-1); // 3.141592653589793 ``` -## acosh +
+ +## acosh() Return the inverse hyperbolic cosine of a number. -### Parameters +**Parameters** - x: A number greater than or equal to 1 -### Returns +**Returns** + The inverse hyperbolic cosine of `x`. -### Example +**Example** ```js math.acosh(1); // 0 ``` -## asin +
+ +## asin() Return the inverse sine (in radians) of a number. -### Parameters +**Parameters** - x: A number between -1 and 1, inclusive, representing the angle's sine value -### Returns +**Returns** + The inverse sine (angle in radians between -𝜋/2 and 𝜋/2, inclusive) of `x`. -### Example +**Example** ```js math.asin(0.5); // 0.5235987755982989 ``` -## asinh +
+ +## asinh() Return the inverse hyperbolic sine of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The inverse hyperbolic sine of `x`. -### Example +**Example** ```js math.asinh(1); // 0.881373587019543 ``` -## atan +
+ +## atan() Return the inverse tangent (in radians) of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The inverse tangent (angle in radians between -𝜋/2 and 𝜋/2, inclusive) of `x`. -### Example +**Example** ```js math.atan(1); // 0.7853981633974483 ``` -## atan2 +
+ +## atan2() Return the angle in the plane (in radians) between the positive x-axis and the ray from (0, 0) to the point (x, y), for math.atan2(y, x). -### Parameters +**Parameters** - y: The y coordinate of the point - x: The x coordinate of the point -### Returns +**Returns** + The angle in radians (between -π and π, inclusive) between the positive x-axis and the ray from (0, 0) to the point (x, y). -### Example +**Example** ```js math.atan2(90, 15); // 1.4056476493802699 ``` -## atanh +
+ +## atanh() The method returns the inverse hyperbolic tangent of a number. -### Parameters +**Parameters** - x: A number between -1 and 1, inclusive -### Returns +**Returns** + The inverse hyperbolic tangent of `x`. -### Example +**Example** ```js math.atanh(0.5); // 0.5493061443340548 ``` -## cbrt +
+ +## cbrt() Return the cube root of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The cube root of `x`. -### Example +**Example** ```js math.cbrt(2); // 1.2599210498948732 ``` -## ceil +
+ +## ceil() Round up and return the smallest integer greater than or equal to a given number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The smallest integer greater than or equal to `x`. It's the same value as `-math.floor(-x)`. -### Example +**Example** ```js math.ceil(-7.004); // -7 math.ceil(7.004); // 8 ``` -## clz32 +
+ +## clz32() Return the number of leading zero bits in the 32-bit binary representation of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The number of leading zero bits in the 32-bit binary representation of `x`. -### Example +**Example** ```js math.clz32(1); // 31 math.clz32(1000); // 22 ``` -## cos +
+ +## cos() Return the cosine of a number in radians. -### Parameters +**Parameters** - x: A number representing an angle in radians -### Returns +**Returns** + The cosine of `x`, between -1 and 1, inclusive. -### Example +**Example** ```js math.cos(math.PI); // -1 ``` -## exp +
+ +## exp() Return e raised to the power of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + A nonnegative number representing `e^x`, where `e` is the base of the natural logarithm. -### Example +**Example** ```js math.exp(0); // 1 math.exp(1); // 2.718281828459045 ``` -## floor +
+ +## floor() Round down and return the largest integer less than or equal to a given number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The largest integer smaller than or equal to `x`. It's the same value as `-math.ceil(-x)`. -### Example +**Example** ```js math.floor(-45.95); // -46 math.floor(-45.05); // -46 @@ -223,137 +269,181 @@ math.floor(45.05); // 45 math.floor(45.95); // 45 ``` -## isEqual -Return true if the difference between numbers `a` and `b` is less than the specified parameter `e`. +
-### Parameters +## log() +Return the natural logarithm of x. + +**Parameters** +- x: A number + +**Returns** + +The natural logarithm of `x`, as in `ln(x)` where `e` is the base of the natural logarithm. + +**Example** +```js +math.log(1); // 0 +math.log(3); // 1.0986122886681098 +``` + +## isEqual() +Return true if the difference between numbers `a` and `b` is less than the specified `tolerance`. + +**Parameters** - a: A number a - b: A number b -- e: An epsilon parameter +- tolerance: How much difference is allowed between the numbers to be considered equal + +**Returns** -### Returns True if the difference between numbers `a` and `b` is less than the specified parameter `e`. Otherwise, false. -### Example +**Example** ```js math.isEqual(1.4, 1.6, 0.2); // false math.isEqual(3.556, 3.555, 0.01); // true ``` -## max +
+ +## max() Return the largest of two numbers given as input parameters. -### Parameters +**Parameters** - a: A number a - b: A number b -### Returns +**Returns** + The largest of the given numbers. -### Example +**Example** ```js math.max(10, 20); // 20 math.max(-10, -20); // -10 ``` -## min +
+ +## min() Return the smallest of two numbers given as input parameters. -### Parameters +**Parameters** - a: A number a - b: A number b -### Returns +**Returns** + The smallest of the given numbers. -### Example +**Example** ```js math.min(10, 20); // 10 math.min(-10, -20); // -20 ``` -## pow +
+ +## pow() Return the value of a base raised to a power. -### Parameters +**Parameters** - base: The base number - exponent: The exponent number -### Returns +**Returns** + A number representing base taken to the power of exponent. -### Example +**Example** ```js math.pow(7, 2); // 49 math.pow(7, 3); // 343 math.pow(2, 10); // 1024 ``` -## random +
+ +## random() Return a floating-point, pseudo-random number that's greater than or equal to 0 and less than 1, with approximately uniform distribution over that range — which you can then scale to your desired range. -### Returns +**Returns** + A floating-point, pseudo-random number between 0 (inclusive) and 1 (exclusive). -### Example +**Example** ```js let num = math.random(); ``` -## sign +
+ +## sign() Return 1 or -1, indicating the sign of the number passed as argument. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + -1 if the number is less than 0, and 1 otherwise. -### Example +**Example** ```js math.sign(3); // 1 math.sign(0); // 1 math.sign(-3); // -1 ``` -## sin +
+ +## sin() Return the sine of a number in radians. -### Parameters +**Parameters** - x: A number representing an angle in radians -### Returns +**Returns** + The sine of `x`, between -1 and 1, inclusive. -### Example +**Example** ```js math.sin(math.PI / 2); // 1 ``` -## sqrt +
+ +## sqrt() Return the square root of a number. -### Parameters +**Parameters** - x: A number greater than or equal to 0 -### Returns +**Returns** + + The square root of `x`, a nonnegative number. If `x` < 0, script will fail with an error. -### Example +**Example** ```js math.sqrt(25); // 5 ``` -## trunc +
+ +## trunc() Return the integer part of a number by removing any fractional digits. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The integer part of `x`. -### Example +**Example** ```js math.trunc(-1.123); // -1 math.trunc(0.123); // 0 diff --git a/documentation/js/js_notification.md b/documentation/js/js_notification.md index 100da4414..753f4c9e9 100644 --- a/documentation/js/js_notification.md +++ b/documentation/js/js_notification.md @@ -1,35 +1,38 @@ -# js_notification {#js_notification} +# Notification module {#js_notification} -# Notification module ```js let notify = require("notification"); ``` -# Methods +## Methods -## success -"Success" flipper notification message +### success() +"Success" flipper notification message. -### Examples: +**Example** ```js notify.success(); ``` -## error -"Error" flipper notification message +
-### Examples: +### error() +"Error" flipper notification message. + +**Example** ```js notify.error(); ``` -## blink -Blink notification LED +
-### Parameters +### blink() +Blink notification LED. + +**Parameters** - Blink color (blue/red/green/yellow/cyan/magenta) - Blink type (short/long) -### Examples: +**Examples** ```js notify.blink("red", "short"); // Short blink of red LED notify.blink("green", "short"); // Long blink of green LED diff --git a/documentation/js/js_serial.md b/documentation/js/js_serial.md index 9d7938044..d06c799ac 100644 --- a/documentation/js/js_serial.md +++ b/documentation/js/js_serial.md @@ -1,83 +1,130 @@ -# js_serial {#js_serial} +# Serial module {#js_serial} -# Serial module ```js let serial = require("serial"); ``` # Methods -## setup +## setup() Configure serial port. Should be called before all other methods. -### Parameters -- Serial port name (usart, lpuart) -- Baudrate +**Parameters** + +- Serial port name (`"usart"`, `"lpuart"`) +- Baudrate +- Optional framing configuration object (e.g. `{ dataBits: "8", parity: "even", stopBits: "1" }`): + - `dataBits`: `"6"`, `"7"`, `"8"`, `"9"` + - 6 data bits can only be selected when parity is enabled (even or odd) + - 9 data bits can only be selected when parity is disabled (none) + - `parity`: `"none"`, `"even"`, `"odd"` + - `stopBits`: `"0.5"`, `"1"`, `"1.5"`, `"2"` + - LPUART only supports whole stop bit lengths (i.e. 1 and 2 but not 0.5 and 1.5) + +**Example** -### Examples: ```js // Configure LPUART port with baudrate = 115200 serial.setup("lpuart", 115200); ``` -## write -Write data to serial port +
+ +## write() +Write data to serial port. + +**Parameters** -### Parameters One or more arguments of the following types: - A string - Single number, each number is interpreted as a byte - Array of numbers, each number is interpreted as a byte - ArrayBuffer or DataView -### Examples: +**Example** + ```js serial.write(0x0a); // Write a single byte 0x0A serial.write("Hello, world!"); // Write a string serial.write("Hello, world!", [0x0d, 0x0a]); // Write a string followed by two bytes ``` -## read +
+ +## read() Read a fixed number of characters from serial port. -### Parameters -- Number of bytes to read -- (optional) Timeout value in ms +**Parameters** + +- Number of bytes to read +- *(optional)* Timeout value in ms + +**Returns** -### Returns A sting of received characters or undefined if nothing was received before timeout. -### Examples: +**Example** + ```js serial.read(1); // Read a single byte, without timeout serial.read(10, 5000); // Read 10 bytes, with 5s timeout ``` -## readln -Read from serial port until line break character +
-### Parameters -(optional) Timeout value in ms +## readln() +Read from serial port until line break character. + +**Parameters** + +- *(optional)* Timeout value in ms. + +**Returns** -### Returns A sting of received characters or undefined if nothing was received before timeout. -### Examples: +**Example** + ```js serial.readln(); // Read without timeout serial.readln(5000); // Read with 5s timeout ``` -## readBytes -Read from serial port until line break character +
+ +## readAny() +Read any available amount of data from serial port, can help avoid starving your loop with small reads. + +**Parameters** + +- *(optional)* Timeout value in ms + +**Returns** + +A sting of received characters or undefined if nothing was received before timeout. + +**Example** + +```js +serial.readAny(); // Read without timeout +serial.readAny(5000); // Read with 5s timeout +``` + +
+ +## readBytes() +Read from serial port until line break character. + +**Parameters** -### Parameters - Number of bytes to read -- (optional) Timeout value in ms +- *(optional)* Timeout value in ms + +**Returns** -### Returns ArrayBuffer with received data or undefined if nothing was received before timeout. -### Examples: +**Example** + ```js serial.readBytes(4); // Read 4 bytes, without timeout @@ -85,23 +132,41 @@ serial.readBytes(4); // Read 4 bytes, without timeout serial.readBytes(1, 0); ``` -## expect -Search for a string pattern in received data stream +
+ +## expect() +Search for a string pattern in received data stream. + +**Parameters** -### Parameters - Single argument or array of the following types: - A string - Array of numbers, each number is interpreted as a byte -- (optional) Timeout value in ms +- *(optional)* Timeout value in ms + +**Returns** -### Returns Index of matched pattern in input patterns list, undefined if nothing was found. -### Examples: +**Example** + ```js // Wait for root shell prompt with 1s timeout, returns 0 if it was received before timeout, undefined if not -serial.expect("# ", 1000); +serial.expect("# ", 1000); // Infinitely wait for one of two strings, should return 0 if the first string got matched, 1 if the second one serial.expect([": not found", "Usage: "]); +``` + +
+ +## end() +Deinitializes serial port, allowing multiple initializations per script run. + +**Example** + +```js +serial.end(); +// Configure LPUART port with baudrate = 115200 +serial.setup("lpuart", 115200); ``` \ No newline at end of file diff --git a/documentation/js/js_storage.md b/documentation/js/js_storage.md new file mode 100644 index 000000000..0e435be5b --- /dev/null +++ b/documentation/js/js_storage.md @@ -0,0 +1,475 @@ +# Storage module {#js_storage} + +The module allows you to access files and directories on the Flipper Zero filesystems. Call the `require` function to load the module before first using its methods: + +```js +let storage = require("storage"); +``` + +## Paths + +To work with files and folders, you'll need to specify paths to them. File paths have the following structure: + +``` +/ext/example_subdir_1/example_subdir_2/example_file.txt +\____________________________________/ \__________/ \_/ + dirPath fileName fileExt +``` + +* **dirPath** — directory path starting with `/int/` (small storage in the MCU's flash memory) or `/ext/` (microSD card storage). Specify the sub-directories containing the file using `/` as a separator between directory names. +* **fileName** — file name. +* **fileExt** — file extension (separated from the file name by a period). + +--- + +# Structures + +## FsInfo + +Filesystem information structure. + +**Fields** + +- totalSpace: Total size of the filesystem, in bytes +- freeSpace: Free space in the filesystem, in bytes + +
+ +## FileInfo + +File information structure. + +**Fields** + +- path: Full path (e.g. "/ext/test", returned by `stat`) or file name (e.g. "test", returned by `readDirectory`) +- isDirectory: Returns `true` if path leads to a directory (not a file) +- size: File size in bytes, or 0 in the case of directories +- accessTime: Time of last access as a UNIX timestamp + +--- + +# Classes + +## File + +This class implements methods for working with file. To get an object of the File class, use the `openFile` method. + +
+ +### close() + +Closes the file. After this method is called, all other operations related to this file become unavailable. + +**Returns** + +`true` on success, `false` on failure. + +
+ +### isOpen() + +**Returns** + +`true` if file is currently opened, `false` otherwise. + +
+ +### read() + +Reads bytes from a file opened in read-only or read-write mode. + +**Parameters** + +- mode: The data type to interpret the bytes as: a `string` decoded from ASCII data (`"ascii"`), or an `ArrayBuf` (`"binary"`) +- bytes: How many bytes to read from the file + +**Returns** + +An `ArrayBuf` if the mode is `"binary"`, a `string` if the mode is `ascii`. The number of bytes that was actually read may be fewer than requested. + +
+ +### write() + +Writes bytes to a file opened in write-only or read-write mode. + +**Parameters** + +- data: The data to write: a string that will be ASCII-encoded, or an ArrayBuf + +**Returns** + +The amount of bytes that was actually written. + +
+ +### seekRelative() + +Moves the R/W pointer forward. + +**Parameters** + +- bytes: How many bytes to move the pointer forward by + +**Returns** + +`true` on success, `false` on failure. + +
+ +### seekAbsolute() + +Moves the R/W pointer to an absolute position inside the file. + +**Parameters** + +- bytes: The position inside the file + +**Returns** + +`true` on success, `false` on failure. + +
+ +### tell() + +Gets the absolute position of the R/W pointer in bytes. + +**Returns** + +The absolute current position in the file. + +
+ +### truncate() + +Discards the data after the current position of the R/W pointer in a file opened in either write-only or read-write mode. + +**Returns** + +`true` on success, `false` on failure. + +
+ +### size() + +**Returns** + +The total size of the file in bytes. + +
+ +### eof() + +Detects whether the R/W pointer has reached the end of the file. + +**Returns** + +`true` if end of file reached, `false` otherwise. + +
+ +### copyTo() + +Copies bytes from the R/W pointer in the current file to the R/W pointer in another file. + +**Parameters** + +- dest: The file to copy the bytes into +- bytes: The number of bytes to copy + +**Returns** + +`true` on success, `false` on failure. + +--- + +# Methods + +## openFile() + +Opens a file. + +**Parameters** + +- path: The path to the file +- accessMode: How to access the file (`"r"`, `"w"` or `"rw"`) +- openMode: How to open the file (`"open_existing"`, `"open_always"`, `"open_append"`, `"create_new"` or `"create_always"`) + +**Returns** + +A `File` object on success, `undefined` otherwise. + +
+ +## fileExists() + +Detects whether a file exists. + +**Parameters** + +- path: The path to the file + +**Returns** + +`true` if file exists, `false` otherwise. + +
+ +## arePathsEqual() + +Determines whether the two paths are equivalent. Respects filesystem-defined path equivalence rules. + +**Parameters** + +- path1: The first path for comparison +- path2: The second path for comparison + +**Returns** + +`true` if path1 and path2 are equals, `false` otherwise. + +
+ +## isSubpathOf() + +Determines whether a path is a subpath of another path. Respects filesystem-defined path equivalence rules. + +**Parameters** + +- parentPath: The parent path +- childPath: The child path + +**Returns** + +`true` if path1 and path2 are equals, `false` otherwise. + +
+ +## fileOrDirExists() + +Detects whether a file or a directory exists. + +**Parameters** + +- path: The path to the file or directory + +**Returns** + +`true` if file/directory exists, `false` otherwise. + +**Example** +```js +if (storage.fileOrDirExists("/ext/test_dir")) { + print("Test directory exists"); +} +``` + +
+ +## fsInfo() + +Fetches generic information about a filesystem. + +**Parameters** + +- filesystem: The path to the filesystem (e.g. `"/ext"` or `"/int"`) + +**Returns** + +A `fsInfo` structure or `undefined` on failure. + +**Example** +```js +let fsinfo = storage.fsInfo("/ext"); +if (fsinfo === undefined) { + print("Filesystem access error"); +} else { + print("Free space on the /ext filesystem:", fsinfo.freeSpace); +} +``` + +
+ +## stat() + +Acquires metadata about a file or directory. + +**Parameters** + +- path: The path to the file or directory + +**Returns** + +A `FileInfo` structure or `undefined` on failure. + +**Example** +```js +let finfo = storage.stat("/ext/test_file.txt"); +if (finfo === undefined) { + print("File not found"); +} else { + print("File size:", finfo.size); +} +``` + +
+ +## directoryExists() + +Detects whether a directory exists. + +**Parameters** + +- path: The path to the directory + +**Returns** + +`true` if directory exists, `false` otherwise. + +
+ +## makeDirectory() + +Creates an empty directory. + +**Parameters** + +- path: The path to the new directory + +**Returns** + +`true` on success, `false` on failure. + +
+ +## readDirectory() + +Reads the list of files in a directory. + +**Parameters** + +- path: The path to the directory + +**Returns** + +Array of `FileInfo` structures with directory entries, or `undefined` on failure. + +
+ +## nextAvailableFilename() + +Chooses the next available filename with a numeric suffix in a directory. +``` +/ext/example_dir/example_file123.txt +\______________/ \__________/\_/ \_/ + dirPath fileName | | + | +--- fileExt + +------- suffix selected by this function +``` + +**Parameters** + +- dirPath: The directory to look in +- fileName: The base of the filename (the part before the numeric suffix) +- fileExt: The extension of the filename (the part after the numeric suffix) +- maxLen: The maximum length of the filename with the numeric suffix + +**Returns** + +The base of the filename with the next available numeric suffix, without the extension or the base directory. + +
+ +## copy() + +Copies a file or recursively copies a possibly non-empty directory. + +**Parameters** + +- oldPath: The original path to the file or directory +- newPath: The new path that the copy of the file or directory will be accessible under + +**Returns** + +`true` on success, `false` on failure. + +**Example** +```js +if (storage.copy("/ext/src_file.txt", "/ext/dst_file.txt")) { + print("File copied"); +} +``` + +
+ +## rename() + +Renames or moves a file or directory. + +**Parameters** + +- oldPath: The old path to the file or directory +- newPath: The new path that the file or directory will become accessible + +**Returns** + +`true` on success, `false` on failure. + +**Example** +```js +if (storage.rename("/ext/src_file.txt", "/ext/dst_file.txt")) { + print("File moved"); +} +``` + +
+ +## remove() + +Removes a file or an empty directory. + +**Parameters** + +- path: The path to the file or directory + +**Returns** + +`true` on success, `false` on failure. + +**Example** +```js +let path = "/ext/test_file.txt"; + +file = storage.openFile(path, "w", "create_always"); +file.write("Test"); +file.close(); +print("File created"); + +if (storage.remove(path)) { + print("File removed"); +} +``` + +
+ +## rmrf() + +Removes a file or recursively removes a possibly non-empty directory. + +**Parameters** + +- path: The path to the file or directory + +**Returns** + +`true` on success, `false` on failure. + +**Example** +```js +let path = "/ext/test_dir"; + +if (storage.rmrf(path)) { + print("Directory removed"); +} +``` diff --git a/documentation/js/js_using_js_modules.md b/documentation/js/js_using_js_modules.md new file mode 100644 index 000000000..e7d69ef76 --- /dev/null +++ b/documentation/js/js_using_js_modules.md @@ -0,0 +1,42 @@ +# Using JavaScript modules {#js_using_js_modules} + +In the previous guides, we learned how to write a basic JavaScript app using [built-in functions](#js_builtin). However, the set of built-in functions is limited, so when developing your JS apps, you'll likely want to use external JS modules. These modules offer a wide range of functions (methods) for various tasks. + +For example: +* The `serial` module enables transmitting and receiving data via a serial interface +* The `badusb` module enables USB keyboard emulation and sending key press events via USB +* The `math` module provides mathematical functions + +JS modules are written in C/C++, making them fast and efficient. They come with Flipper Zero firmware and are stored on the microSD card in compiled form as **FAL (Flipper Application File)** files. + +> [!note] +> You can find the implementation of all supported JS modules in the [Flipper Zero firmware repository](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/system/js_app/modules). Also, check out the [docs for JS modules](#js_modules) for more details. + +## How to use JS modules in your app + +Before using any of the JS module methods, you **must** import the module using the `require()` function. This loads the module into RAM, allowing you to access its methods. + +To save RAM and improve performance, avoid loading modules you don't plan to use. Also, all loaded modules will be automatically unloaded from RAM after the app execution ends. + +To load a module, call the `require()` function with the module name in quotes. For example, to load the `notification` module, write this: + +\code{.js} +let notify = require("notification"); +\endcode + +Now you can call methods of the `notification` module using the `notify` variable to access them: + +\code{.js} +let notify = require("notification"); + +notify.success(); +print("success notification"); +\endcode + +## What's next? + +Congratulations, you've completed the **Getting Started** section of our JS docs. You've learned how to run and debug JS apps, and how to use JS modules. Now, we invite you to check out the [main JavaScript page](#js) where you'll find: + +* JavaScript app examples +* Documentation on JS modules +* Additional resources related to JavaScript on Flipper Zero diff --git a/documentation/js/js_your_first_js_app.md b/documentation/js/js_your_first_js_app.md new file mode 100644 index 000000000..ee003ea77 --- /dev/null +++ b/documentation/js/js_your_first_js_app.md @@ -0,0 +1,83 @@ +# Your first JavaScript app {#js_your_first_js_app} + +In this guide, we'll create a simple script that outputs ordinal numbers with a delay and learn how to run it on Flipper Zero in different ways. All you need is your Flipper Zero, a PC, and a USB cable. + +## Step 1. Create the script file + +Create a new text file `first_app.js`. Paste the code below into it and save the file: + +\code{.js} +print("start"); +delay(1000); +print("1"); +delay(500); +print("2"); +delay(500); +print("3"); +delay(500); +print("end"); +\endcode + +What the code does: +* Outputs the text **start**, waits 1 second +* Outputs the numbers **1**, **2** and **3**, with a 0.5-second pause after each number +* Outputs the text **end** + +The `print()` function is used to output text. The string to be output is in the brackets. This is a built-in function, so you don't need to include additional JS modules. You can use this function anywhere in your application. + +Another built-in function, `delay()`, implements delay. The delay time in milliseconds is given in the brackets. Since 1000 milliseconds equals 1 second, a 1-second delay is written as 1000, and a 0.5-second delay as 500. + +> [!note] +> Find the list of built-in functions in [Built-in functions](#js_builtin). + +## Step 2. Copy the file to Flipper Zero + +To copy the JavaScript file to Flipper Zero, follow these steps: +1. Connect your Flipper Zero to your PC via USB. +2. Open the **qFlipper** application. +3. Go to the **File manager** tab and open the path `SD Card/apps/Scripts/`. +4. Drag and drop the file into the qFlipper window. + +> [!note] +> To learn more about qFlipper, visit the [dedicated section in our user documentation](https://docs.flipper.net/qflipper). + +Your script is now ready to run on Flipper Zero. + +## Step 3. Run your script + +You can launch your app in two ways: + +* From the Flipper Zero menu +* Remotely from your PC using the CLI (command-line interface) + +Let's explore them both. + +### How to run a script from Flipper Zero's menu + +1. Go to **Apps → Scripts** in your Flipper Zero's menu. Here, you'll see a list of scripts located in the `SD Card/apps/Scripts/` folder. +2. Select the script you want to run. +3. Press the **OK** button to run the script. + +\image html js_first_app_on_fz.jpg width=500 + +The Flipper Zero screen will display the strings with the specified delay, as defined by the `print()` and `delay()` functions. + +### How to run script using CLI + +The command-line interface (CLI) is a text-based interface that lets you control your Flipper Zero from your computer, including running scripts. Running JavaScript apps via CLI is useful for debugging, as it lets you write and test code remotely, without switching between your PC and the device. + +To run the script via CLI: + +1. Connect your Flipper Zero to your PC via USB. +2. Access the CLI using one of the [recommended methods](https://docs.flipper.net/development/cli#HfXTy). +3. Enter the `js path` command, replacing `path` with the path to the script file on your Flipper Zero: + +\code{.sh} +js /ext/apps/Scripts/first_app.js +\endcode + +\image html js_first_app_on_cli.jpg width=700 + +As you can see, unlike running JavaScript apps from the Flipper Zero UI, all output from the `print()` function is sent to the CLI, not the device screen. + +**Next step:** [Developing apps using JavaScript SDK](#js_developing_apps_using_js_sdk) diff --git a/fbt_options.py b/fbt_options.py index f3396f98c..54569d525 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -76,6 +76,7 @@ FIRMWARE_APPS = { "radio_device_cc1101_ext", "unit_tests", "js_app", + "archive", ], } diff --git a/furi/core/check.c b/furi/core/check.c index ba05a675f..ee8ee4af0 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -102,11 +102,13 @@ static void __furi_print_bt_stack_info(void) { static void __furi_print_heap_info(void) { furi_log_puts("\r\n\t heap total: "); - __furi_put_uint32_as_text(xPortGetTotalHeapSize()); + __furi_put_uint32_as_text(configTOTAL_HEAP_SIZE); furi_log_puts("\r\n\t heap free: "); __furi_put_uint32_as_text(xPortGetFreeHeapSize()); + HeapStats_t heap_stats; + vPortGetHeapStats(&heap_stats); furi_log_puts("\r\n\t heap watermark: "); - __furi_put_uint32_as_text(xPortGetMinimumEverFreeHeapSize()); + __furi_put_uint32_as_text(heap_stats.xMinimumEverFreeBytesRemaining); } static void __furi_print_name(bool isr) { diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h index fa56150ce..2d6ced025 100644 --- a/furi/core/core_defines.h +++ b/furi/core/core_defines.h @@ -41,6 +41,16 @@ extern "C" { #define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower))) #endif +#ifndef CLAMP_WRAPAROUND +#define CLAMP_WRAPAROUND(x, upper, lower) \ + ({ \ + __typeof__(x) _x = (x); \ + __typeof__(upper) _upper = (upper); \ + __typeof__(lower) _lower = (lower); \ + (_x > _upper) ? _lower : ((_x < _lower) ? _upper : _x); \ + }) +#endif + #ifndef COUNT_OF #define COUNT_OF(x) (sizeof(x) / sizeof(x[0])) #endif diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index c0998ea90..800fea86c 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -32,14 +32,6 @@ static void furi_event_loop_item_notify(FuriEventLoopItem* instance); static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance); -static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) { - for(; !PendingQueue_empty_p(instance->pending_queue); - PendingQueue_pop_back(NULL, instance->pending_queue)) { - const FuriEventLoopPendingQueueItem* item = PendingQueue_back(instance->pending_queue); - item->callback(item->context); - } -} - static bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context) { furi_assert(context); FuriEventLoop* instance = context; @@ -86,6 +78,7 @@ void furi_event_loop_free(FuriEventLoop* instance) { furi_event_loop_process_timer_queue(instance); furi_check(TimerList_empty_p(instance->timer_list)); furi_check(WaitingList_empty_p(instance->waiting_list)); + furi_check(!instance->are_thread_flags_subscribed); FuriEventLoopTree_clear(instance->tree); PendingQueue_clear(instance->pending_queue); @@ -130,12 +123,16 @@ static inline FuriEventLoopProcessStatus furi_event_loop_unsubscribe(instance, item->object); } + instance->current_item = item; + if(item->event & FuriEventLoopEventFlagEdge) { status = furi_event_loop_process_edge_event(item); } else { status = furi_event_loop_process_level_event(item); } + instance->current_item = NULL; + if(item->owner == NULL) { status = FuriEventLoopProcessStatusFreeLater; } @@ -193,6 +190,14 @@ static void furi_event_loop_process_waiting_list(FuriEventLoop* instance) { furi_event_loop_sync_flags(instance); } +static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) { + for(; !PendingQueue_empty_p(instance->pending_queue); + PendingQueue_pop_back(NULL, instance->pending_queue)) { + const FuriEventLoopPendingQueueItem* item = PendingQueue_back(instance->pending_queue); + item->callback(item->context); + } +} + static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flags) { if(flags) { xTaskNotifyIndexed( @@ -203,7 +208,6 @@ static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flag void furi_event_loop_run(FuriEventLoop* instance) { furi_check(instance); furi_check(instance->thread_id == furi_thread_get_current_id()); - FuriThread* thread = furi_thread_get_current(); // Set the default signal callback if none was previously set @@ -213,9 +217,9 @@ void furi_event_loop_run(FuriEventLoop* instance) { furi_event_loop_init_tick(instance); - while(true) { - instance->state = FuriEventLoopStateIdle; + instance->state = FuriEventLoopStateRunning; + while(true) { const TickType_t ticks_to_sleep = MIN(furi_event_loop_get_timer_wait_time(instance), furi_event_loop_get_tick_wait_time(instance)); @@ -224,8 +228,6 @@ void furi_event_loop_run(FuriEventLoop* instance) { BaseType_t ret = xTaskNotifyWaitIndexed( FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, ticks_to_sleep); - instance->state = FuriEventLoopStateProcessing; - if(ret == pdTRUE) { if(flags & FuriEventLoopFlagStop) { instance->state = FuriEventLoopStateStopped; @@ -242,6 +244,10 @@ void furi_event_loop_run(FuriEventLoop* instance) { } else if(flags & FuriEventLoopFlagPending) { furi_event_loop_process_pending_callbacks(instance); + } else if(flags & FuriEventLoopFlagThreadFlag) { + if(instance->are_thread_flags_subscribed) + instance->thread_flags_callback(instance->thread_flags_callback_context); + } else { furi_crash(); } @@ -415,6 +421,24 @@ void furi_event_loop_subscribe_mutex( instance, mutex, &furi_mutex_event_loop_contract, event, callback, context); } +void furi_event_loop_subscribe_thread_flags( + FuriEventLoop* instance, + FuriEventLoopThreadFlagsCallback callback, + void* context) { + furi_check(instance); + furi_check(callback); + furi_check(!instance->are_thread_flags_subscribed); + instance->are_thread_flags_subscribed = true; + instance->thread_flags_callback = callback; + instance->thread_flags_callback_context = context; +} + +void furi_event_loop_unsubscribe_thread_flags(FuriEventLoop* instance) { + furi_check(instance); + furi_check(instance->are_thread_flags_subscribed); + instance->are_thread_flags_subscribed = false; +} + /** * Public generic unsubscription API */ @@ -448,7 +472,7 @@ void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* o WaitingList_unlink(item); } - if(instance->state == FuriEventLoopStateProcessing) { + if(instance->current_item == item) { furi_event_loop_item_free_later(item); } else { furi_event_loop_item_free(item); @@ -537,6 +561,25 @@ static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance) { return instance->WaitingList.prev || instance->WaitingList.next; } +void furi_event_loop_thread_flag_callback(FuriThreadId thread_id) { + TaskHandle_t hTask = (TaskHandle_t)thread_id; + BaseType_t yield; + + if(FURI_IS_IRQ_MODE()) { + yield = pdFALSE; + (void)xTaskNotifyIndexedFromISR( + hTask, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + FuriEventLoopFlagThreadFlag, + eSetBits, + &yield); + portYIELD_FROM_ISR(yield); + } else { + (void)xTaskNotifyIndexed( + hTask, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagThreadFlag, eSetBits); + } +} + /* * Internal event loop link API, used by supported primitives */ diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index d5e8710a6..63f020f78 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -203,6 +203,12 @@ typedef void FuriEventLoopObject; */ typedef void (*FuriEventLoopEventCallback)(FuriEventLoopObject* object, void* context); +/** Callback type for event loop thread flag events + * + * @param context The context that was provided upon subscription + */ +typedef void (*FuriEventLoopThreadFlagsCallback)(void* context); + /** Opaque event flag type */ typedef struct FuriEventFlag FuriEventFlag; @@ -304,6 +310,23 @@ void furi_event_loop_subscribe_mutex( FuriEventLoopEventCallback callback, void* context); +/** Subscribe to thread flag events of the current thread + * + * @param instance The Event Loop instance + * @param callback The callback to call when a flag has been set + * @param context The context for callback + */ +void furi_event_loop_subscribe_thread_flags( + FuriEventLoop* instance, + FuriEventLoopThreadFlagsCallback callback, + void* context); + +/** Unsubscribe from thread flag events of the current thread + * + * @param instance The Event Loop instance + */ +void furi_event_loop_unsubscribe_thread_flags(FuriEventLoop* instance); + /** Unsubscribe from events (common) * * @param instance The Event Loop instance diff --git a/furi/core/event_loop_i.h b/furi/core/event_loop_i.h index 7016e1e1b..c2f04a359 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -4,12 +4,14 @@ #include "event_loop_link_i.h" #include "event_loop_timer_i.h" #include "event_loop_tick_i.h" +#include "event_loop_thread_flag_interface.h" #include #include #include #include "thread.h" +#include "thread_i.h" struct FuriEventLoopItem { // Source @@ -50,11 +52,12 @@ typedef enum { FuriEventLoopFlagStop = (1 << 1), FuriEventLoopFlagTimer = (1 << 2), FuriEventLoopFlagPending = (1 << 3), + FuriEventLoopFlagThreadFlag = (1 << 4), } FuriEventLoopFlag; #define FuriEventLoopFlagAll \ (FuriEventLoopFlagEvent | FuriEventLoopFlagStop | FuriEventLoopFlagTimer | \ - FuriEventLoopFlagPending) + FuriEventLoopFlagPending | FuriEventLoopFlagThreadFlag) typedef enum { FuriEventLoopProcessStatusComplete, @@ -64,8 +67,7 @@ typedef enum { typedef enum { FuriEventLoopStateStopped, - FuriEventLoopStateIdle, - FuriEventLoopStateProcessing, + FuriEventLoopStateRunning, } FuriEventLoopState; typedef struct { @@ -81,6 +83,7 @@ struct FuriEventLoop { // Poller state volatile FuriEventLoopState state; + volatile FuriEventLoopItem* current_item; // Event handling FuriEventLoopTree_t tree; @@ -94,4 +97,9 @@ struct FuriEventLoop { PendingQueue_t pending_queue; // Tick event FuriEventLoopTick tick; + + // Thread flags callback + bool are_thread_flags_subscribed; + FuriEventLoopThreadFlagsCallback thread_flags_callback; + void* thread_flags_callback_context; }; diff --git a/furi/core/event_loop_thread_flag_interface.h b/furi/core/event_loop_thread_flag_interface.h new file mode 100644 index 000000000..05fcd47de --- /dev/null +++ b/furi/core/event_loop_thread_flag_interface.h @@ -0,0 +1,10 @@ +#pragma once + +#include "thread.h" + +/** + * @brief Notify `FuriEventLoop` that `furi_thread_flags_set` has been called + * + * @param thread_id Thread id + */ +extern void furi_event_loop_thread_flag_callback(FuriThreadId thread_id); diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 8ee0d1723..a3bbf4556 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -1,6 +1,7 @@ #include "memmgr.h" #include #include +#include extern void* pvPortMalloc(size_t xSize); extern void vPortFree(void* pv); @@ -51,7 +52,7 @@ size_t memmgr_get_free_heap(void) { } size_t memmgr_get_total_heap(void) { - return xPortGetTotalHeapSize(); + return configTOTAL_HEAP_SIZE; } size_t memmgr_get_minimum_free_heap(void) { diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 359d0e3db..3ce0558a3 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -1,6 +1,8 @@ /* - * FreeRTOS Kernel V10.2.1 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * FreeRTOS Kernel V11.1.0 + * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT * * 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 @@ -19,10 +21,9 @@ * 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. * - * http://www.FreeRTOS.org - * http://aws.amazon.com/freertos + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS * - * 1 tab == 4 spaces! */ /* @@ -31,21 +32,25 @@ * limits memory fragmentation. * * See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the - * memory management pages of http://www.FreeRTOS.org for more information. + * memory management pages of https://www.FreeRTOS.org for more information. */ #include "memmgr_heap.h" #include "check.h" #include +#include #include #include #include #include #include +// -V::562 +// -V::650 + /* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining -all the API functions to use the MPU wrappers. That should only be done when -task.h is included from an application file. */ + * all the API functions to use the MPU wrappers. That should only be done when + * task.h is included from an application file. */ #define MPU_WRAPPERS_INCLUDED_FROM_API_FILE #include @@ -53,8 +58,12 @@ task.h is included from an application file. */ #undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE -#ifdef HEAP_PRINT_DEBUG -#error This feature is broken, logging transport must be replaced with RTT +#if(configSUPPORT_DYNAMIC_ALLOCATION == 0) +#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0 +#endif + +#ifndef configHEAP_CLEAR_MEMORY_ON_FREE +#define configHEAP_CLEAR_MEMORY_ON_FREE 0 #endif /* Block sizes must not get too small. */ @@ -63,16 +72,75 @@ task.h is included from an application file. */ /* Assumes 8bit bytes! */ #define heapBITS_PER_BYTE ((size_t)8) +/* Max value that fits in a size_t type. */ +#define heapSIZE_MAX (~((size_t)0)) + +/* Check if multiplying a and b will result in overflow. */ +#define heapMULTIPLY_WILL_OVERFLOW(a, b) (((a) > 0) && ((b) > (heapSIZE_MAX / (a)))) + +/* Check if adding a and b will result in overflow. */ +#define heapADD_WILL_OVERFLOW(a, b) ((a) > (heapSIZE_MAX - (b))) + +/* Check if the subtraction operation ( a - b ) will result in underflow. */ +#define heapSUBTRACT_WILL_UNDERFLOW(a, b) ((a) < (b)) + +/* MSB of the xBlockSize member of an BlockLink_t structure is used to track + * the allocation status of a block. When MSB of the xBlockSize member of + * an BlockLink_t structure is set then the block belongs to the application. + * When the bit is free the block is still part of the free heap space. */ +#define heapBLOCK_ALLOCATED_BITMASK (((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1)) +#define heapBLOCK_SIZE_IS_VALID(xBlockSize) (((xBlockSize) & heapBLOCK_ALLOCATED_BITMASK) == 0) +#define heapBLOCK_IS_ALLOCATED(pxBlock) \ + (((pxBlock->xBlockSize) & heapBLOCK_ALLOCATED_BITMASK) != 0) +#define heapALLOCATE_BLOCK(pxBlock) ((pxBlock->xBlockSize) |= heapBLOCK_ALLOCATED_BITMASK) +#define heapFREE_BLOCK(pxBlock) ((pxBlock->xBlockSize) &= ~heapBLOCK_ALLOCATED_BITMASK) + +/*-----------------------------------------------------------*/ + /* Heap start end symbols provided by linker */ uint8_t* ucHeap = (uint8_t*)&__heap_start__; /* Define the linked list structure. This is used to link free blocks in order -of their memory address. */ + * of their memory address. */ typedef struct A_BLOCK_LINK { - struct A_BLOCK_LINK* pxNextFreeBlock; /*<< The next free block in the list. */ - size_t xBlockSize; /*<< The size of the free block. */ + struct A_BLOCK_LINK* pxNextFreeBlock; /**< The next free block in the list. */ + size_t xBlockSize; /**< The size of the free block. */ } BlockLink_t; +/* Setting configENABLE_HEAP_PROTECTOR to 1 enables heap block pointers + * protection using an application supplied canary value to catch heap + * corruption should a heap buffer overflow occur. + */ +#if(configENABLE_HEAP_PROTECTOR == 1) + +/** + * @brief Application provided function to get a random value to be used as canary. + * + * @param pxHeapCanary [out] Output parameter to return the canary value. + */ +extern void vApplicationGetRandomHeapCanary(portPOINTER_SIZE_TYPE* pxHeapCanary); + +/* Canary value for protecting internal heap pointers. */ +PRIVILEGED_DATA static portPOINTER_SIZE_TYPE xHeapCanary; + +/* Macro to load/store BlockLink_t pointers to memory. By XORing the + * pointers with a random canary value, heap overflows will result + * in randomly unpredictable pointer values which will be caught by + * heapVALIDATE_BLOCK_POINTER assert. */ +#define heapPROTECT_BLOCK_POINTER(pxBlock) \ + ((BlockLink_t*)(((portPOINTER_SIZE_TYPE)(pxBlock)) ^ xHeapCanary)) +#else + +#define heapPROTECT_BLOCK_POINTER(pxBlock) (pxBlock) + +#endif /* configENABLE_HEAP_PROTECTOR */ + +/* Assert that a heap block pointer is within the heap bounds. */ +#define heapVALIDATE_BLOCK_POINTER(pxBlock) \ + configASSERT( \ + ((uint8_t*)(pxBlock) >= &(ucHeap[0])) && \ + ((uint8_t*)(pxBlock) <= &(ucHeap[configTOTAL_HEAP_SIZE - 1]))) + /*-----------------------------------------------------------*/ /* @@ -81,34 +149,31 @@ typedef struct A_BLOCK_LINK { * the block in front it and/or the block behind it if the memory blocks are * adjacent to each other. */ -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert); +static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) PRIVILEGED_FUNCTION; /* * Called automatically to setup the required heap structures the first time * pvPortMalloc() is called. */ -static void prvHeapInit(void); +static void prvHeapInit(void) PRIVILEGED_FUNCTION; /*-----------------------------------------------------------*/ /* The size of the structure placed at the beginning of each allocated memory -block must by correctly byte aligned. */ + * block must by correctly byte aligned. */ static const size_t xHeapStructSize = (sizeof(BlockLink_t) + ((size_t)(portBYTE_ALIGNMENT - 1))) & ~((size_t)portBYTE_ALIGNMENT_MASK); /* Create a couple of list links to mark the start and end of the list. */ -static BlockLink_t xStart, *pxEnd = NULL; +PRIVILEGED_DATA static BlockLink_t xStart; +PRIVILEGED_DATA static BlockLink_t* pxEnd = NULL; -/* Keeps track of the number of free bytes remaining, but says nothing about -fragmentation. */ -static size_t xFreeBytesRemaining = 0U; -static size_t xMinimumEverFreeBytesRemaining = 0U; - -/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize -member of an BlockLink_t structure is set then the block belongs to the -application. When the bit is free the block is still part of the free heap -space. */ -static size_t xBlockAllocatedBit = 0; +/* Keeps track of the number of calls to allocate and free memory as well as the + * number of free bytes remaining, but says nothing about fragmentation. */ +PRIVILEGED_DATA static size_t xFreeBytesRemaining = (size_t)0U; +PRIVILEGED_DATA static size_t xMinimumEverFreeBytesRemaining = (size_t)0U; +PRIVILEGED_DATA static size_t xNumberOfSuccessfulAllocations = (size_t)0U; +PRIVILEGED_DATA static size_t xNumberOfSuccessfulFrees = (size_t)0U; /* Furi heap extension */ #include @@ -175,7 +240,7 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { puc -= xHeapStructSize; BlockLink_t* pxLink = (void*)puc; - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0 && + if((pxLink->xBlockSize & heapBLOCK_ALLOCATED_BITMASK) && pxLink->pxNextFreeBlock == NULL) { leftovers += data->value; } @@ -220,20 +285,9 @@ static inline void traceFREE(void* pointer, size_t size) { } size_t memmgr_heap_get_max_free_block(void) { - size_t max_free_size = 0; - BlockLink_t* pxBlock; - vTaskSuspendAll(); - - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { - if(pxBlock->xBlockSize > max_free_size) { - max_free_size = pxBlock->xBlockSize; - } - pxBlock = pxBlock->pxNextFreeBlock; - } - - xTaskResumeAll(); - return max_free_size; + HeapStats_t heap_stats; + vPortGetHeapStats(&heap_stats); + return heap_stats.xSizeOfLargestFreeBlockInBytes; } void memmgr_heap_printf_free_blocks(void) { @@ -241,195 +295,126 @@ void memmgr_heap_printf_free_blocks(void) { //can be enabled once we can do printf with a locked scheduler //vTaskSuspendAll(); - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { + pxBlock = heapPROTECT_BLOCK_POINTER(xStart.pxNextFreeBlock); + heapVALIDATE_BLOCK_POINTER(pxBlock); + while(pxBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER(NULL)) { printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize); - pxBlock = pxBlock->pxNextFreeBlock; + pxBlock = heapPROTECT_BLOCK_POINTER(pxBlock->pxNextFreeBlock); + heapVALIDATE_BLOCK_POINTER(pxBlock); } //xTaskResumeAll(); } -#ifdef HEAP_PRINT_DEBUG -char* ultoa(unsigned long num, char* str, int radix) { - char temp[33]; // at radix 2 the string is at most 32 + 1 null long. - int temp_loc = 0; - int digit; - int str_loc = 0; - - //construct a backward string of the number. - do { - digit = (unsigned long)num % ((unsigned long)radix); - if(digit < 10) - temp[temp_loc++] = digit + '0'; - else - temp[temp_loc++] = digit - 10 + 'A'; - num = ((unsigned long)num) / ((unsigned long)radix); - } while((unsigned long)num > 0); - - temp_loc--; - - //now reverse the string. - while(temp_loc >= 0) { // while there are still chars - str[str_loc++] = temp[temp_loc--]; - } - str[str_loc] = 0; // add null termination. - - return str; -} - -static void print_heap_init(void) { - char tmp_str[33]; - size_t heap_start = (size_t)&__heap_start__; - size_t heap_end = (size_t)&__heap_end__; - - // {PHStart|heap_start|heap_end} - FURI_CRITICAL_ENTER(); - furi_log_puts("{PHStart|"); - ultoa(heap_start, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - ultoa(heap_end, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} - -static void print_heap_malloc(void* ptr, size_t size) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } - - // {thread name|m|address|size} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|m|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - utoa(size, tmp_str, 10); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} - -static void print_heap_free(void* ptr) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } - - // {thread name|f|address} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|f|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} -#endif /*-----------------------------------------------------------*/ void* pvPortMalloc(size_t xWantedSize) { - BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink; + BlockLink_t* pxBlock; + BlockLink_t* pxPreviousBlock; + BlockLink_t* pxNewBlockLink; void* pvReturn = NULL; - size_t to_wipe = xWantedSize; + size_t xToWipe = xWantedSize; + size_t xAdditionalRequiredSize; + size_t xAllocatedBlockSize = 0; if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } -#ifdef HEAP_PRINT_DEBUG - BlockLink_t* print_heap_block = NULL; -#endif + if(xWantedSize > 0) { + /* The wanted size must be increased so it can contain a BlockLink_t + * structure in addition to the requested amount of bytes. */ + if(heapADD_WILL_OVERFLOW(xWantedSize, xHeapStructSize) == 0) { + xWantedSize += xHeapStructSize; - /* If this is the first call to malloc then the heap will require - initialisation to setup the list of free blocks. */ - if(pxEnd == NULL) { -#ifdef HEAP_PRINT_DEBUG - print_heap_init(); -#endif + /* Ensure that blocks are always aligned to the required number + * of bytes. */ + if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { + /* Byte alignment required. */ + xAdditionalRequiredSize = + portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK); - vTaskSuspendAll(); - { - prvHeapInit(); - memmgr_heap_init(); + if(heapADD_WILL_OVERFLOW(xWantedSize, xAdditionalRequiredSize) == 0) { + xWantedSize += xAdditionalRequiredSize; + } else { + xWantedSize = 0; + } + } else { + mtCOVERAGE_TEST_MARKER(); + } + } else { + xWantedSize = 0; } - (void)xTaskResumeAll(); } else { mtCOVERAGE_TEST_MARKER(); } vTaskSuspendAll(); { - /* Check the requested block size is not so large that the top bit is - set. The top bit of the block size member of the BlockLink_t structure - is used to determine who owns the block - the application or the - kernel, so it must be free. */ - if((xWantedSize & xBlockAllocatedBit) == 0) { - /* The wanted size is increased so it can contain a BlockLink_t - structure in addition to the requested amount of bytes. */ - if(xWantedSize > 0) { - xWantedSize += xHeapStructSize; - - /* Ensure that blocks are always aligned to the required number - of bytes. */ - if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { - /* Byte alignment required. */ - xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK)); - configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0); - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } + /* If this is the first call to malloc then the heap will require + * initialisation to setup the list of free blocks. */ + if(pxEnd == NULL) { + prvHeapInit(); + memmgr_heap_init(); + } else { + mtCOVERAGE_TEST_MARKER(); + } + /* Check the block size we are trying to allocate is not so large that the + * top bit is set. The top bit of the block size member of the BlockLink_t + * structure is used to determine who owns the block - the application or + * the kernel, so it must be free. */ + if(heapBLOCK_SIZE_IS_VALID(xWantedSize) != 0) { if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)) { /* Traverse the list from the start (lowest address) block until - one of adequate size is found. */ + * one of adequate size is found. */ pxPreviousBlock = &xStart; - pxBlock = xStart.pxNextFreeBlock; - while((pxBlock->xBlockSize < xWantedSize) && (pxBlock->pxNextFreeBlock != NULL)) { + pxBlock = heapPROTECT_BLOCK_POINTER(xStart.pxNextFreeBlock); + heapVALIDATE_BLOCK_POINTER(pxBlock); + + while((pxBlock->xBlockSize < xWantedSize) && + (pxBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER(NULL))) { pxPreviousBlock = pxBlock; - pxBlock = pxBlock->pxNextFreeBlock; + pxBlock = heapPROTECT_BLOCK_POINTER(pxBlock->pxNextFreeBlock); + heapVALIDATE_BLOCK_POINTER(pxBlock); } /* If the end marker was reached then a block of adequate size - was not found. */ + * was not found. */ if(pxBlock != pxEnd) { /* Return the memory space pointed to - jumping over the - BlockLink_t structure at its start. */ - pvReturn = - (void*)(((uint8_t*)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize); + * BlockLink_t structure at its start. */ + pvReturn = (void*)(((uint8_t*)heapPROTECT_BLOCK_POINTER( + pxPreviousBlock->pxNextFreeBlock)) + + xHeapStructSize); + heapVALIDATE_BLOCK_POINTER(pvReturn); /* This block is being returned for use so must be taken out - of the list of free blocks. */ + * of the list of free blocks. */ pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; /* If the block is larger than required it can be split into - two. */ + * two. */ + configASSERT( + heapSUBTRACT_WILL_UNDERFLOW(pxBlock->xBlockSize, xWantedSize) == 0); + if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE) { /* This block is to be split into two. Create a new - block following the number of bytes requested. The void - cast is used to prevent byte alignment warnings from the - compiler. */ + * block following the number of bytes requested. The void + * cast is used to prevent byte alignment warnings from the + * compiler. */ pxNewBlockLink = (void*)(((uint8_t*)pxBlock) + xWantedSize); configASSERT((((size_t)pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0); /* Calculate the sizes of two blocks split from the - single block. */ + * single block. */ pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; pxBlock->xBlockSize = xWantedSize; /* Insert the new block into the list of free blocks. */ - prvInsertBlockIntoFreeList(pxNewBlockLink); + pxNewBlockLink->pxNextFreeBlock = pxPreviousBlock->pxNextFreeBlock; + pxPreviousBlock->pxNextFreeBlock = + heapPROTECT_BLOCK_POINTER(pxNewBlockLink); } else { mtCOVERAGE_TEST_MARKER(); } @@ -442,14 +427,13 @@ void* pvPortMalloc(size_t xWantedSize) { mtCOVERAGE_TEST_MARKER(); } - /* The block is being returned - it is allocated and owned - by the application and has no "next" block. */ - pxBlock->xBlockSize |= xBlockAllocatedBit; - pxBlock->pxNextFreeBlock = NULL; + xAllocatedBlockSize = pxBlock->xBlockSize; -#ifdef HEAP_PRINT_DEBUG - print_heap_block = pxBlock; -#endif + /* The block is being returned - it is allocated and owned + * by the application and has no "next" block. */ + heapALLOCATE_BLOCK(pxBlock); + pxBlock->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(NULL); + xNumberOfSuccessfulAllocations++; } else { mtCOVERAGE_TEST_MARKER(); } @@ -460,29 +444,27 @@ void* pvPortMalloc(size_t xWantedSize) { mtCOVERAGE_TEST_MARKER(); } - traceMALLOC(pvReturn, xWantedSize); + traceMALLOC(pvReturn, xAllocatedBlockSize); + + /* Prevent compiler warnings when trace macros are not used. */ + (void)xAllocatedBlockSize; } (void)xTaskResumeAll(); -#ifdef HEAP_PRINT_DEBUG - print_heap_malloc(print_heap_block, print_heap_block->xBlockSize & ~xBlockAllocatedBit); -#endif - #if(configUSE_MALLOC_FAILED_HOOK == 1) { if(pvReturn == NULL) { - extern void vApplicationMallocFailedHook(void); vApplicationMallocFailedHook(); } else { mtCOVERAGE_TEST_MARKER(); } } -#endif +#endif /* if ( configUSE_MALLOC_FAILED_HOOK == 1 ) */ configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0); furi_check(pvReturn, xWantedSize ? "out of memory" : "malloc(0)"); - pvReturn = memset(pvReturn, 0, to_wipe); + pvReturn = memset(pvReturn, 0, xToWipe); return pvReturn; } /*-----------------------------------------------------------*/ @@ -497,24 +479,30 @@ void vPortFree(void* pv) { if(pv != NULL) { /* The memory being freed will have an BlockLink_t structure immediately - before it. */ + * before it. */ puc -= xHeapStructSize; /* This casting is to keep the compiler from issuing warnings. */ pxLink = (void*)puc; - /* Check the block is actually allocated. */ - configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); - configASSERT(pxLink->pxNextFreeBlock == NULL); + heapVALIDATE_BLOCK_POINTER(pxLink); + configASSERT(heapBLOCK_IS_ALLOCATED(pxLink) != 0); + configASSERT(pxLink->pxNextFreeBlock == heapPROTECT_BLOCK_POINTER(NULL)); - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0) { - if(pxLink->pxNextFreeBlock == NULL) { + if(heapBLOCK_IS_ALLOCATED(pxLink) != 0) { + if(pxLink->pxNextFreeBlock == heapPROTECT_BLOCK_POINTER(NULL)) { /* The block is being returned to the heap - it is no longer - allocated. */ - pxLink->xBlockSize &= ~xBlockAllocatedBit; - -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pxLink); + * allocated. */ + heapFREE_BLOCK(pxLink); +#if(configHEAP_CLEAR_MEMORY_ON_FREE == 1) + { + /* Check for underflow as this can occur if xBlockSize is + * overwritten in a heap block. */ + if(heapSUBTRACT_WILL_UNDERFLOW(pxLink->xBlockSize, xHeapStructSize) == 0) { + (void)memset( + puc + xHeapStructSize, 0, pxLink->xBlockSize - xHeapStructSize); + } + } #endif vTaskSuspendAll(); @@ -527,8 +515,8 @@ void vPortFree(void* pv) { /* Add this block to the list of free blocks. */ xFreeBytesRemaining += pxLink->xBlockSize; traceFREE(pv, pxLink->xBlockSize); - memset(pv, 0, pxLink->xBlockSize - xHeapStructSize); - prvInsertBlockIntoFreeList((BlockLink_t*)pxLink); + prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink)); + xNumberOfSuccessfulFrees++; } (void)xTaskResumeAll(); } else { @@ -537,19 +525,10 @@ void vPortFree(void* pv) { } else { mtCOVERAGE_TEST_MARKER(); } - } else { -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pv); -#endif } } /*-----------------------------------------------------------*/ -size_t xPortGetTotalHeapSize(void) { - return (size_t)&__heap_end__ - (size_t)&__heap_start__; -} -/*-----------------------------------------------------------*/ - size_t xPortGetFreeHeapSize(void) { return xFreeBytesRemaining; } @@ -560,71 +539,98 @@ size_t xPortGetMinimumEverFreeHeapSize(void) { } /*-----------------------------------------------------------*/ +void xPortResetHeapMinimumEverFreeHeapSize(void) { + xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; +} +/*-----------------------------------------------------------*/ + void vPortInitialiseBlocks(void) { /* This just exists to keep the linker quiet. */ } /*-----------------------------------------------------------*/ -static void prvHeapInit(void) { - BlockLink_t* pxFirstFreeBlock; - uint8_t* pucAlignedHeap; - size_t uxAddress; - size_t xTotalHeapSize = (size_t)&__heap_end__ - (size_t)&__heap_start__; +void* pvPortCalloc(size_t xNum, size_t xSize) { + void* pv = NULL; - /* Ensure the heap starts on a correctly aligned boundary. */ - uxAddress = (size_t)ucHeap; + if(heapMULTIPLY_WILL_OVERFLOW(xNum, xSize) == 0) { + pv = pvPortMalloc(xNum * xSize); - if((uxAddress & portBYTE_ALIGNMENT_MASK) != 0) { - uxAddress += (portBYTE_ALIGNMENT - 1); - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - xTotalHeapSize -= uxAddress - (size_t)ucHeap; + if(pv != NULL) { + (void)memset(pv, 0, xNum * xSize); + } } - pucAlignedHeap = (uint8_t*)uxAddress; + return pv; +} +/*-----------------------------------------------------------*/ + +static void prvHeapInit(void) /* PRIVILEGED_FUNCTION */ +{ + BlockLink_t* pxFirstFreeBlock; + portPOINTER_SIZE_TYPE uxStartAddress, uxEndAddress; + size_t xTotalHeapSize = configTOTAL_HEAP_SIZE; + + /* Ensure the heap starts on a correctly aligned boundary. */ + uxStartAddress = (portPOINTER_SIZE_TYPE)ucHeap; + + if((uxStartAddress & portBYTE_ALIGNMENT_MASK) != 0) { + uxStartAddress += (portBYTE_ALIGNMENT - 1); + uxStartAddress &= ~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK); + xTotalHeapSize -= (size_t)(uxStartAddress - (portPOINTER_SIZE_TYPE)ucHeap); + } + +#if(configENABLE_HEAP_PROTECTOR == 1) + { vApplicationGetRandomHeapCanary(&(xHeapCanary)); } +#endif /* xStart is used to hold a pointer to the first item in the list of free - blocks. The void cast is used to prevent compiler warnings. */ - xStart.pxNextFreeBlock = (void*)pucAlignedHeap; + * blocks. The void cast is used to prevent compiler warnings. */ + xStart.pxNextFreeBlock = (void*)heapPROTECT_BLOCK_POINTER(uxStartAddress); xStart.xBlockSize = (size_t)0; /* pxEnd is used to mark the end of the list of free blocks and is inserted - at the end of the heap space. */ - uxAddress = ((size_t)pucAlignedHeap) + xTotalHeapSize; - uxAddress -= xHeapStructSize; - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - pxEnd = (void*)uxAddress; + * at the end of the heap space. */ + uxEndAddress = uxStartAddress + (portPOINTER_SIZE_TYPE)xTotalHeapSize; + uxEndAddress -= (portPOINTER_SIZE_TYPE)xHeapStructSize; + uxEndAddress &= ~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK); + pxEnd = (BlockLink_t*)uxEndAddress; pxEnd->xBlockSize = 0; - pxEnd->pxNextFreeBlock = NULL; + pxEnd->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(NULL); /* To start with there is a single free block that is sized to take up the - entire heap space, minus the space taken by pxEnd. */ - pxFirstFreeBlock = (void*)pucAlignedHeap; - pxFirstFreeBlock->xBlockSize = uxAddress - (size_t)pxFirstFreeBlock; - pxFirstFreeBlock->pxNextFreeBlock = pxEnd; + * entire heap space, minus the space taken by pxEnd. */ + pxFirstFreeBlock = (BlockLink_t*)uxStartAddress; + pxFirstFreeBlock->xBlockSize = + (size_t)(uxEndAddress - (portPOINTER_SIZE_TYPE)pxFirstFreeBlock); + pxFirstFreeBlock->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(pxEnd); /* Only one block exists - and it covers the entire usable heap space. */ xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; - - /* Work out the position of the top bit in a size_t variable. */ - xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1); } /*-----------------------------------------------------------*/ -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { +static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) /* PRIVILEGED_FUNCTION */ +{ BlockLink_t* pxIterator; uint8_t* puc; /* Iterate through the list until a block is found that has a higher address - than the block being inserted. */ - for(pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; - pxIterator = pxIterator->pxNextFreeBlock) { + * than the block being inserted. */ + for(pxIterator = &xStart; + heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock) < pxBlockToInsert; + pxIterator = heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)) { /* Nothing to do here, just iterate to the right position. */ } + if(pxIterator != &xStart) { + heapVALIDATE_BLOCK_POINTER(pxIterator); + } + /* Do the block being inserted, and the block it is being inserted after - make a contiguous block of memory? */ + * make a contiguous block of memory? */ puc = (uint8_t*)pxIterator; + if((puc + pxIterator->xBlockSize) == (uint8_t*)pxBlockToInsert) { pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; pxBlockToInsert = pxIterator; @@ -633,27 +639,98 @@ static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { } /* Do the block being inserted, and the block it is being inserted before - make a contiguous block of memory? */ + * make a contiguous block of memory? */ puc = (uint8_t*)pxBlockToInsert; - if((puc + pxBlockToInsert->xBlockSize) == (uint8_t*)pxIterator->pxNextFreeBlock) { - if(pxIterator->pxNextFreeBlock != pxEnd) { + + if((puc + pxBlockToInsert->xBlockSize) == + (uint8_t*)heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)) { + if(heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock) != pxEnd) { /* Form one big block from the two blocks. */ - pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; - pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; + pxBlockToInsert->xBlockSize += + heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)->xBlockSize; + pxBlockToInsert->pxNextFreeBlock = + heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)->pxNextFreeBlock; } else { - pxBlockToInsert->pxNextFreeBlock = pxEnd; + pxBlockToInsert->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(pxEnd); } } else { pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; } - /* If the block being inserted plugged a gab, so was merged with the block - before and the block after, then it's pxNextFreeBlock pointer will have - already been set, and should not be set here as that would make it point - to itself. */ + /* If the block being inserted plugged a gap, so was merged with the block + * before and the block after, then it's pxNextFreeBlock pointer will have + * already been set, and should not be set here as that would make it point + * to itself. */ if(pxIterator != pxBlockToInsert) { - pxIterator->pxNextFreeBlock = pxBlockToInsert; + pxIterator->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(pxBlockToInsert); } else { mtCOVERAGE_TEST_MARKER(); } } +/*-----------------------------------------------------------*/ + +void vPortGetHeapStats(HeapStats_t* pxHeapStats) { + BlockLink_t* pxBlock; + size_t + xBlocks = 0, + xMaxSize = 0, + xMinSize = + portMAX_DELAY; /* portMAX_DELAY used as a portable way of getting the maximum value. */ + + vTaskSuspendAll(); + { + pxBlock = heapPROTECT_BLOCK_POINTER(xStart.pxNextFreeBlock); + + /* pxBlock will be NULL if the heap has not been initialised. The heap + * is initialised automatically when the first allocation is made. */ + if(pxBlock != NULL) { + while(pxBlock != pxEnd) { + /* Increment the number of blocks and record the largest block seen + * so far. */ + xBlocks++; + + if(pxBlock->xBlockSize > xMaxSize) { + xMaxSize = pxBlock->xBlockSize; + } + + if(pxBlock->xBlockSize < xMinSize) { + xMinSize = pxBlock->xBlockSize; + } + + /* Move to the next block in the chain until the last block is + * reached. */ + pxBlock = heapPROTECT_BLOCK_POINTER(pxBlock->pxNextFreeBlock); + } + } + } + (void)xTaskResumeAll(); + + pxHeapStats->xSizeOfLargestFreeBlockInBytes = xMaxSize; + pxHeapStats->xSizeOfSmallestFreeBlockInBytes = xMinSize; + pxHeapStats->xNumberOfFreeBlocks = xBlocks; + + taskENTER_CRITICAL(); + { + pxHeapStats->xAvailableHeapSpaceInBytes = xFreeBytesRemaining; + pxHeapStats->xNumberOfSuccessfulAllocations = xNumberOfSuccessfulAllocations; + pxHeapStats->xNumberOfSuccessfulFrees = xNumberOfSuccessfulFrees; + pxHeapStats->xMinimumEverFreeBytesRemaining = xMinimumEverFreeBytesRemaining; + } + taskEXIT_CRITICAL(); +} +/*-----------------------------------------------------------*/ + +/* + * Reset the state in this file. This state is normally initialized at start up. + * This function must be called by the application before restarting the + * scheduler. + */ +void vPortHeapResetState(void) { + pxEnd = NULL; + + xFreeBytesRemaining = (size_t)0U; + xMinimumEverFreeBytesRemaining = (size_t)0U; + xNumberOfSuccessfulAllocations = (size_t)0U; + xNumberOfSuccessfulFrees = (size_t)0U; +} +/*-----------------------------------------------------------*/ diff --git a/furi/core/record.c b/furi/core/record.c index fa384369a..17c95aa9b 100644 --- a/furi/core/record.c +++ b/furi/core/record.c @@ -80,6 +80,7 @@ bool furi_record_exists(const char* name) { void furi_record_create(const char* name, void* data) { furi_check(furi_record); furi_check(name); + furi_check(data); furi_record_lock(); diff --git a/furi/core/record.h b/furi/core/record.h index a269484f0..1fb20ed6f 100644 --- a/furi/core/record.h +++ b/furi/core/record.h @@ -27,7 +27,7 @@ bool furi_record_exists(const char* name); /** Create record * * @param name record name - * @param data data pointer + * @param data data pointer (not NULL) * @note Thread safe. Create and destroy must be executed from the same * thread. */ diff --git a/furi/core/thread.c b/furi/core/thread.c index 6e5157957..e29a8711e 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -7,6 +7,7 @@ #include "check.h" #include "common_defines.h" #include "string.h" +#include "event_loop_thread_flag_interface.h" #include "log.h" #include @@ -23,6 +24,8 @@ #define THREAD_MAX_STACK_SIZE (UINT16_MAX * sizeof(StackType_t)) +#define THREAD_STACK_WATERMARK_MIN (256u) + typedef struct { FuriThreadStdoutWriteCallback write_callback; FuriString* buffer; @@ -115,6 +118,18 @@ static void furi_thread_body(void* context) { furi_check(!thread->is_service, "Service threads MUST NOT return"); + size_t stack_watermark = furi_thread_get_stack_space(thread); + if(stack_watermark < THREAD_STACK_WATERMARK_MIN) { +#ifdef FURI_DEBUG + furi_crash("Stack watermark is dangerously low"); +#endif + FURI_LOG_E( //-V779 + thread->name ? thread->name : "Thread", + "Stack watermark is too low %zu < " STRINGIFY( + THREAD_STACK_WATERMARK_MIN) ". Increase stack size.", + stack_watermark); + } + if(thread->heap_trace_enabled == true) { furi_delay_ms(33); thread->heap_size = memmgr_heap_get_thread_memory((FuriThreadId)thread); @@ -487,6 +502,9 @@ uint32_t furi_thread_flags_set(FuriThreadId thread_id, uint32_t flags) { (void)xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags); } } + + furi_event_loop_thread_flag_callback(thread_id); + /* Return flags after setting */ return rflags; } @@ -744,16 +762,22 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread) { return 0; } -FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) { +void furi_thread_get_stdout_callback(FuriThreadStdoutWriteCallback* callback, void** context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); - return thread->output.write_callback; + furi_check(callback); + furi_check(context); + *callback = thread->output.write_callback; + *context = thread->output.context; } -FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void) { +void furi_thread_get_stdin_callback(FuriThreadStdinReadCallback* callback, void** context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); - return thread->input.read_callback; + furi_check(callback); + furi_check(context); + *callback = thread->input.read_callback; + *context = thread->input.context; } void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context) { diff --git a/furi/core/thread.h b/furi/core/thread.h index 9abfde5cd..c1f615d16 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -479,16 +479,18 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); /** * @brief Get the standard output callback for the current thead. * - * @return pointer to the standard out callback function + * @param[out] callback where to store the stdout callback + * @param[out] context where to store the context */ -FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void); +void furi_thread_get_stdout_callback(FuriThreadStdoutWriteCallback* callback, void** context); /** * @brief Get the standard input callback for the current thead. * - * @return pointer to the standard in callback function + * @param[out] callback where to store the stdin callback + * @param[out] context where to store the context */ -FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void); +void furi_thread_get_stdin_callback(FuriThreadStdinReadCallback* callback, void** context); /** Set standard output callback for the current thread. * diff --git a/furi/flipper.c b/furi/flipper.c index 6d6215a9d..bafe2ed0f 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -9,6 +9,8 @@ #define TAG "Flipper" +#define HEAP_CANARY_VALUE 0x8BADF00D + static void flipper_print_version(const char* target, const Version* version) { if(version) { FURI_LOG_I( @@ -67,3 +69,7 @@ void vApplicationGetTimerTaskMemory( *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configTIMER_TASK_STACK_DEPTH); *stack_size = configTIMER_TASK_STACK_DEPTH; } + +void vApplicationGetRandomHeapCanary(portPOINTER_SIZE_TYPE* pxHeapCanary) { + *pxHeapCanary = HEAP_CANARY_VALUE; +} diff --git a/lib/SConscript b/lib/SConscript index fb0473f8d..4e6171aba 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -43,6 +43,7 @@ libs = env.BuildModules( "ble_profile", "bit_lib", "datetime", + "ieee754_parse_wrap", ], ) diff --git a/lib/ble_profile/extra_profiles/hid_profile.c b/lib/ble_profile/extra_profiles/hid_profile.c index f559a741a..ed67f44a1 100644 --- a/lib/ble_profile/extra_profiles/hid_profile.c +++ b/lib/ble_profile/extra_profiles/hid_profile.c @@ -9,12 +9,12 @@ #include #include -#define HID_INFO_BASE_USB_SPECIFICATION (0x0101) -#define HID_INFO_COUNTRY_CODE (0x00) -#define BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) +#define HID_INFO_BASE_USB_SPECIFICATION (0x0101) +#define HID_INFO_COUNTRY_CODE (0x00) +#define BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) #define BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) -#define BLE_PROFILE_HID_KB_MAX_KEYS (6) +#define BLE_PROFILE_HID_KB_MAX_KEYS (6) #define BLE_PROFILE_CONSUMER_MAX_KEYS (1) // Report ids cant be 0 @@ -380,7 +380,11 @@ bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta) #define CONNECTION_INTERVAL_MAX (0x24) static GapConfig template_config = { - .adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, + .adv_service = + { + .UUID_Type = UUID_TYPE_16, + .Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, + }, .appearance_char = GAP_APPEARANCE_KEYBOARD, .bonding_mode = true, .pairing_method = GapPairingPinCodeVerifyYesNo, @@ -409,19 +413,17 @@ static void ble_profile_hid_get_config(GapConfig* config, FuriHalBleProfileParam } // Set advertise name - memset(config->adv_name, 0, sizeof(config->adv_name)); - FuriString* name = furi_string_alloc_set(furi_hal_version_get_ble_local_device_name_ptr()); - const char* clicker_str = "Control"; if(hid_profile_params && hid_profile_params->device_name_prefix) { clicker_str = hid_profile_params->device_name_prefix; } - furi_string_replace_str(name, "Flipper", clicker_str); - if(furi_string_size(name) >= sizeof(config->adv_name)) { - furi_string_left(name, sizeof(config->adv_name) - 1); - } - memcpy(config->adv_name, furi_string_get_cstr(name), furi_string_size(name)); - furi_string_free(name); + snprintf( + config->adv_name, + sizeof(config->adv_name), + "%c%s %s", + furi_hal_version_get_ble_local_device_name_ptr()[0], + clicker_str, + furi_hal_version_get_name_ptr()); } static const FuriHalBleProfileTemplate profile_callbacks = { diff --git a/lib/drivers/SK6805.c b/lib/drivers/SK6805.c new file mode 100644 index 000000000..2ad8e18d3 --- /dev/null +++ b/lib/drivers/SK6805.c @@ -0,0 +1,102 @@ +/* + SK6805 FlipperZero driver + Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "SK6805.h" +#include + +/* Настройки */ +#define SK6805_LED_COUNT 3 //Количество светодиодов на плате подсветки +#define SK6805_LED_PIN &led_pin //Порт подключения светодиодов + +#ifdef FURI_DEBUG +#define DEBUG_PIN &gpio_ext_pa7 +#define DEBUG_INIT() \ + furi_hal_gpio_init(DEBUG_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh) +#define DEBUG_SET_HIGH() furi_hal_gpio_write(DEBUG_PIN, true) +#define DEBUG_SET_LOW() furi_hal_gpio_write(DEBUG_PIN, false) +#else +#define DEBUG_INIT() +#define DEBUG_SET_HIGH() +#define DEBUG_SET_LOW() +#endif + +static const GpioPin led_pin = {.port = GPIOA, .pin = LL_GPIO_PIN_8}; +static uint8_t led_buffer[SK6805_LED_COUNT][3]; + +void SK6805_init(void) { + DEBUG_INIT(); + furi_hal_gpio_write(SK6805_LED_PIN, false); + furi_hal_gpio_init(SK6805_LED_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); +} + +uint8_t SK6805_get_led_count(void) { + return (const uint8_t)SK6805_LED_COUNT; +} +void SK6805_set_led_color(uint8_t led_index, uint8_t r, uint8_t g, uint8_t b) { + furi_check(led_index < SK6805_LED_COUNT); + + led_buffer[led_index][0] = g; + led_buffer[led_index][1] = r; + led_buffer[led_index][2] = b; +} + +void SK6805_update(void) { + SK6805_init(); + FURI_CRITICAL_ENTER(); + furi_delay_us(100); + uint32_t end; + /* Последовательная отправка цветов светодиодов */ + for(uint8_t lednumber = 0; lednumber < SK6805_LED_COUNT; lednumber++) { + //Последовательная отправка цветов светодиода + for(uint8_t color = 0; color < 3; color++) { + //Последовательная отправка битов цвета + uint8_t i = 0b10000000; + while(i != 0) { + if(led_buffer[lednumber][color] & (i)) { + furi_hal_gpio_write(SK6805_LED_PIN, true); + DEBUG_SET_HIGH(); + end = DWT->CYCCNT + 30; + //T1H 600 us (615 us) + while(DWT->CYCCNT < end) { + } + furi_hal_gpio_write(SK6805_LED_PIN, false); + DEBUG_SET_LOW(); + end = DWT->CYCCNT + 26; + //T1L 600 us (587 us) + while(DWT->CYCCNT < end) { + } + } else { + furi_hal_gpio_write(SK6805_LED_PIN, true); + DEBUG_SET_HIGH(); + end = DWT->CYCCNT + 11; + //T0H 300 ns (312 ns) + while(DWT->CYCCNT < end) { + } + furi_hal_gpio_write(SK6805_LED_PIN, false); + DEBUG_SET_LOW(); + end = DWT->CYCCNT + 43; + //T0L 900 ns (890 ns) + while(DWT->CYCCNT < end) { + } + } + i >>= 1; + } + } + } + FURI_CRITICAL_EXIT(); +} diff --git a/lib/drivers/SK6805.h b/lib/drivers/SK6805.h new file mode 100644 index 000000000..733f394ad --- /dev/null +++ b/lib/drivers/SK6805.h @@ -0,0 +1,58 @@ +/* + SK6805 FlipperZero driver + Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef SK6805_H_ +#define SK6805_H_ + +#include + +/** + * @brief Инициализация линии управления подсветкой + */ +void SK6805_init(void); + +/** + * @brief Получить количество светодиодов в подсветке + * + * @return Количество светодиодов + */ +uint8_t SK6805_get_led_count(void); + +/** + * @brief Установить цвет свечения светодиода + * + * @param led_index номер светодиода (от 0 до SK6805_get_led_count()) + * @param r значение красного (0-255) + * @param g значение зелёного (0-255) + * @param b значение синего (0-255) + */ +void SK6805_set_led_color(uint8_t led_index, uint8_t r, uint8_t g, uint8_t b); + +/** + * @brief Обновление состояния подсветки дисплея + */ +void SK6805_update(void); + +#endif /* SK6805_H_ */ + +#ifdef __cplusplus +} +#endif diff --git a/lib/drivers/bq25896.c b/lib/drivers/bq25896.c index 76aae5e82..a44ff8c39 100644 --- a/lib/drivers/bq25896.c +++ b/lib/drivers/bq25896.c @@ -35,7 +35,7 @@ typedef struct { static bq25896_regs_t bq25896_regs; -bool bq25896_init(FuriHalI2cBusHandle* handle) { +bool bq25896_init(const FuriHalI2cBusHandle* handle) { bool result = true; bq25896_regs.r14.REG_RST = 1; @@ -78,19 +78,19 @@ bool bq25896_init(FuriHalI2cBusHandle* handle) { return result; } -void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim) { +void bq25896_set_boost_lim(const FuriHalI2cBusHandle* handle, BoostLim boost_lim) { bq25896_regs.r0A.BOOST_LIM = boost_lim; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x0A, *(uint8_t*)&bq25896_regs.r0A, BQ25896_I2C_TIMEOUT); } -void bq25896_poweroff(FuriHalI2cBusHandle* handle) { +void bq25896_poweroff(const FuriHalI2cBusHandle* handle) { bq25896_regs.r09.BATFET_DIS = 1; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x09, *(uint8_t*)&bq25896_regs.r09, BQ25896_I2C_TIMEOUT); } -ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle) { +ChrgStat bq25896_get_charge_status(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_mem( handle, BQ25896_ADDRESS, @@ -103,52 +103,52 @@ ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle) { return bq25896_regs.r0B.CHRG_STAT; } -bool bq25896_is_charging(FuriHalI2cBusHandle* handle) { +bool bq25896_is_charging(const FuriHalI2cBusHandle* handle) { // Include precharge, fast charging, and charging termination done as "charging" return bq25896_get_charge_status(handle) != ChrgStatNo; } -bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle) { +bool bq25896_is_charging_done(const FuriHalI2cBusHandle* handle) { return bq25896_get_charge_status(handle) == ChrgStatDone; } -void bq25896_enable_charging(FuriHalI2cBusHandle* handle) { +void bq25896_enable_charging(const FuriHalI2cBusHandle* handle) { bq25896_regs.r03.CHG_CONFIG = 1; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT); } -void bq25896_disable_charging(FuriHalI2cBusHandle* handle) { +void bq25896_disable_charging(const FuriHalI2cBusHandle* handle) { bq25896_regs.r03.CHG_CONFIG = 0; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT); } -void bq25896_enable_otg(FuriHalI2cBusHandle* handle) { +void bq25896_enable_otg(const FuriHalI2cBusHandle* handle) { bq25896_regs.r03.OTG_CONFIG = 1; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT); } -void bq25896_disable_otg(FuriHalI2cBusHandle* handle) { +void bq25896_disable_otg(const FuriHalI2cBusHandle* handle) { bq25896_regs.r03.OTG_CONFIG = 0; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT); } -bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle) { +bool bq25896_is_otg_enabled(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x03, (uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT); return bq25896_regs.r03.OTG_CONFIG; } -uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle) { +uint16_t bq25896_get_vreg_voltage(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x06, (uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT); return (uint16_t)bq25896_regs.r06.VREG * 16 + 3840; } -void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) { +void bq25896_set_vreg_voltage(const FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) { if(vreg_voltage < 3840) { // Minimum valid value is 3840 mV vreg_voltage = 3840; @@ -166,13 +166,13 @@ void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage handle, BQ25896_ADDRESS, 0x06, *(uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT); } -bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle) { +bool bq25896_check_otg_fault(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x0C, (uint8_t*)&bq25896_regs.r0C, BQ25896_I2C_TIMEOUT); return bq25896_regs.r0C.BOOST_FAULT; } -uint16_t bq25896_get_vbus_voltage(FuriHalI2cBusHandle* handle) { +uint16_t bq25896_get_vbus_voltage(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x11, (uint8_t*)&bq25896_regs.r11, BQ25896_I2C_TIMEOUT); if(bq25896_regs.r11.VBUS_GD) { @@ -182,25 +182,25 @@ uint16_t bq25896_get_vbus_voltage(FuriHalI2cBusHandle* handle) { } } -uint16_t bq25896_get_vsys_voltage(FuriHalI2cBusHandle* handle) { +uint16_t bq25896_get_vsys_voltage(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x0F, (uint8_t*)&bq25896_regs.r0F, BQ25896_I2C_TIMEOUT); return (uint16_t)bq25896_regs.r0F.SYSV * 20 + 2304; } -uint16_t bq25896_get_vbat_voltage(FuriHalI2cBusHandle* handle) { +uint16_t bq25896_get_vbat_voltage(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x0E, (uint8_t*)&bq25896_regs.r0E, BQ25896_I2C_TIMEOUT); return (uint16_t)bq25896_regs.r0E.BATV * 20 + 2304; } -uint16_t bq25896_get_vbat_current(FuriHalI2cBusHandle* handle) { +uint16_t bq25896_get_vbat_current(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x12, (uint8_t*)&bq25896_regs.r12, BQ25896_I2C_TIMEOUT); return (uint16_t)bq25896_regs.r12.ICHGR * 50; } -uint32_t bq25896_get_ntc_mpct(FuriHalI2cBusHandle* handle) { +uint32_t bq25896_get_ntc_mpct(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x10, (uint8_t*)&bq25896_regs.r10, BQ25896_I2C_TIMEOUT); return (uint32_t)bq25896_regs.r10.TSPCT * 465 + 21000; diff --git a/lib/drivers/bq25896.h b/lib/drivers/bq25896.h index d35625ab3..69c19868c 100644 --- a/lib/drivers/bq25896.h +++ b/lib/drivers/bq25896.h @@ -7,61 +7,61 @@ #include /** Initialize Driver */ -bool bq25896_init(FuriHalI2cBusHandle* handle); +bool bq25896_init(const FuriHalI2cBusHandle* handle); /** Set boost lim*/ -void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim); +void bq25896_set_boost_lim(const FuriHalI2cBusHandle* handle, BoostLim boost_lim); /** Send device into shipping mode */ -void bq25896_poweroff(FuriHalI2cBusHandle* handle); +void bq25896_poweroff(const FuriHalI2cBusHandle* handle); /** Get charging status */ -ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle); +ChrgStat bq25896_get_charge_status(const FuriHalI2cBusHandle* handle); /** Is currently charging */ -bool bq25896_is_charging(FuriHalI2cBusHandle* handle); +bool bq25896_is_charging(const FuriHalI2cBusHandle* handle); /** Is charging completed while connected to charger */ -bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle); +bool bq25896_is_charging_done(const FuriHalI2cBusHandle* handle); /** Enable charging */ -void bq25896_enable_charging(FuriHalI2cBusHandle* handle); +void bq25896_enable_charging(const FuriHalI2cBusHandle* handle); /** Disable charging */ -void bq25896_disable_charging(FuriHalI2cBusHandle* handle); +void bq25896_disable_charging(const FuriHalI2cBusHandle* handle); /** Enable otg */ -void bq25896_enable_otg(FuriHalI2cBusHandle* handle); +void bq25896_enable_otg(const FuriHalI2cBusHandle* handle); /** Disable otg */ -void bq25896_disable_otg(FuriHalI2cBusHandle* handle); +void bq25896_disable_otg(const FuriHalI2cBusHandle* handle); /** Is otg enabled */ -bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle); +bool bq25896_is_otg_enabled(const FuriHalI2cBusHandle* handle); /** Get VREG (charging limit) voltage in mV */ -uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle); +uint16_t bq25896_get_vreg_voltage(const FuriHalI2cBusHandle* handle); /** Set VREG (charging limit) voltage in mV * * Valid range: 3840mV - 4208mV, in steps of 16mV */ -void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage); +void bq25896_set_vreg_voltage(const FuriHalI2cBusHandle* handle, uint16_t vreg_voltage); /** Check OTG BOOST Fault status */ -bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle); +bool bq25896_check_otg_fault(const FuriHalI2cBusHandle* handle); /** Get VBUS Voltage in mV */ -uint16_t bq25896_get_vbus_voltage(FuriHalI2cBusHandle* handle); +uint16_t bq25896_get_vbus_voltage(const FuriHalI2cBusHandle* handle); /** Get VSYS Voltage in mV */ -uint16_t bq25896_get_vsys_voltage(FuriHalI2cBusHandle* handle); +uint16_t bq25896_get_vsys_voltage(const FuriHalI2cBusHandle* handle); /** Get VBAT Voltage in mV */ -uint16_t bq25896_get_vbat_voltage(FuriHalI2cBusHandle* handle); +uint16_t bq25896_get_vbat_voltage(const FuriHalI2cBusHandle* handle); /** Get VBAT current in mA */ -uint16_t bq25896_get_vbat_current(FuriHalI2cBusHandle* handle); +uint16_t bq25896_get_vbat_current(const FuriHalI2cBusHandle* handle); /** Get NTC voltage in mpct of REGN */ -uint32_t bq25896_get_ntc_mpct(FuriHalI2cBusHandle* handle); +uint32_t bq25896_get_ntc_mpct(const FuriHalI2cBusHandle* handle); diff --git a/lib/drivers/bq27220.c b/lib/drivers/bq27220.c index d60e287da..127f7c6b9 100644 --- a/lib/drivers/bq27220.c +++ b/lib/drivers/bq27220.c @@ -43,7 +43,7 @@ #endif static inline bool bq27220_read_reg( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, uint8_t* buffer, size_t buffer_size) { @@ -52,7 +52,7 @@ static inline bool bq27220_read_reg( } static inline bool bq27220_write( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* buffer, size_t buffer_size) { @@ -60,11 +60,11 @@ static inline bool bq27220_write( handle, BQ27220_ADDRESS, address, buffer, buffer_size, BQ27220_I2C_TIMEOUT); } -static inline bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) { +static inline bool bq27220_control(const FuriHalI2cBusHandle* handle, uint16_t control) { return bq27220_write(handle, CommandControl, (uint8_t*)&control, 2); } -static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { +static uint16_t bq27220_read_word(const FuriHalI2cBusHandle* handle, uint8_t address) { uint16_t buf = BQ27220_ERROR; if(!bq27220_read_reg(handle, address, (uint8_t*)&buf, 2)) { @@ -83,7 +83,7 @@ static uint8_t bq27220_get_checksum(uint8_t* data, uint16_t len) { } static bool bq27220_parameter_check( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint16_t address, uint32_t value, size_t size, @@ -163,7 +163,7 @@ static bool bq27220_parameter_check( } static bool bq27220_data_memory_check( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory, bool update) { if(update) { @@ -268,7 +268,7 @@ static bool bq27220_data_memory_check( return result; } -bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { +bool bq27220_init(const FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { bool result = false; bool reset_and_provisioning_required = false; @@ -365,7 +365,7 @@ bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) return result; } -bool bq27220_reset(FuriHalI2cBusHandle* handle) { +bool bq27220_reset(const FuriHalI2cBusHandle* handle) { bool result = false; do { if(!bq27220_control(handle, Control_RESET)) { @@ -396,7 +396,7 @@ bool bq27220_reset(FuriHalI2cBusHandle* handle) { return result; } -bool bq27220_seal(FuriHalI2cBusHandle* handle) { +bool bq27220_seal(const FuriHalI2cBusHandle* handle) { Bq27220OperationStatus operation_status = {0}; bool result = false; do { @@ -431,7 +431,7 @@ bool bq27220_seal(FuriHalI2cBusHandle* handle) { return result; } -bool bq27220_unseal(FuriHalI2cBusHandle* handle) { +bool bq27220_unseal(const FuriHalI2cBusHandle* handle) { Bq27220OperationStatus operation_status = {0}; bool result = false; do { @@ -465,7 +465,7 @@ bool bq27220_unseal(FuriHalI2cBusHandle* handle) { return result; } -bool bq27220_full_access(FuriHalI2cBusHandle* handle) { +bool bq27220_full_access(const FuriHalI2cBusHandle* handle) { bool result = false; do { @@ -518,29 +518,35 @@ bool bq27220_full_access(FuriHalI2cBusHandle* handle) { return result; } -uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_voltage(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandVoltage); } -int16_t bq27220_get_current(FuriHalI2cBusHandle* handle) { +int16_t bq27220_get_current(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandCurrent); } -bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status) { +bool bq27220_get_control_status( + const FuriHalI2cBusHandle* handle, + Bq27220ControlStatus* control_status) { return bq27220_read_reg(handle, CommandControl, (uint8_t*)control_status, 2); } -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status) { +bool bq27220_get_battery_status( + const FuriHalI2cBusHandle* handle, + Bq27220BatteryStatus* battery_status) { return bq27220_read_reg(handle, CommandBatteryStatus, (uint8_t*)battery_status, 2); } bool bq27220_get_operation_status( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, Bq27220OperationStatus* operation_status) { return bq27220_read_reg(handle, CommandOperationStatus, (uint8_t*)operation_status, 2); } -bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status) { +bool bq27220_get_gauging_status( + const FuriHalI2cBusHandle* handle, + Bq27220GaugingStatus* gauging_status) { // Request gauging data to be loaded to MAC if(!bq27220_control(handle, Control_GAUGING_STATUS)) { FURI_LOG_E(TAG, "DM SelectSubclass for read failed"); @@ -552,26 +558,26 @@ bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatu return bq27220_read_reg(handle, CommandMACData, (uint8_t*)gauging_status, 2); } -uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_temperature(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandTemperature); } -uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_full_charge_capacity(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandFullChargeCapacity); } -uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_design_capacity(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandDesignCapacity); } -uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_remaining_capacity(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandRemainingCapacity); } -uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_state_of_charge(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandStateOfCharge); } -uint16_t bq27220_get_state_of_health(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_state_of_health(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandStateOfHealth); } diff --git a/lib/drivers/bq27220.h b/lib/drivers/bq27220.h index cdfcb20b1..addbf08f5 100644 --- a/lib/drivers/bq27220.h +++ b/lib/drivers/bq27220.h @@ -136,7 +136,7 @@ typedef struct BQ27220DMData BQ27220DMData; * * @return true on success, false otherwise */ -bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory); +bool bq27220_init(const FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory); /** Reset gauge * @@ -144,7 +144,7 @@ bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) * * @return true on success, false otherwise */ -bool bq27220_reset(FuriHalI2cBusHandle* handle); +bool bq27220_reset(const FuriHalI2cBusHandle* handle); /** Seal gauge access * @@ -152,7 +152,7 @@ bool bq27220_reset(FuriHalI2cBusHandle* handle); * * @return true on success, false otherwise */ -bool bq27220_seal(FuriHalI2cBusHandle* handle); +bool bq27220_seal(const FuriHalI2cBusHandle* handle); /** Unseal gauge access * @@ -160,7 +160,7 @@ bool bq27220_seal(FuriHalI2cBusHandle* handle); * * @return true on success, false otherwise */ -bool bq27220_unseal(FuriHalI2cBusHandle* handle); +bool bq27220_unseal(const FuriHalI2cBusHandle* handle); /** Get full access * @@ -170,7 +170,7 @@ bool bq27220_unseal(FuriHalI2cBusHandle* handle); * * @return true on success, false otherwise */ -bool bq27220_full_access(FuriHalI2cBusHandle* handle); +bool bq27220_full_access(const FuriHalI2cBusHandle* handle); /** Get battery voltage * @@ -178,7 +178,7 @@ bool bq27220_full_access(FuriHalI2cBusHandle* handle); * * @return voltage in mV or BQ27220_ERROR */ -uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_voltage(const FuriHalI2cBusHandle* handle); /** Get current * @@ -186,7 +186,7 @@ uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle); * * @return current in mA or BQ27220_ERROR */ -int16_t bq27220_get_current(FuriHalI2cBusHandle* handle); +int16_t bq27220_get_current(const FuriHalI2cBusHandle* handle); /** Get control status * @@ -195,7 +195,9 @@ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle); * * @return true on success, false otherwise */ -bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status); +bool bq27220_get_control_status( + const FuriHalI2cBusHandle* handle, + Bq27220ControlStatus* control_status); /** Get battery status * @@ -204,7 +206,9 @@ bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatu * * @return true on success, false otherwise */ -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status); +bool bq27220_get_battery_status( + const FuriHalI2cBusHandle* handle, + Bq27220BatteryStatus* battery_status); /** Get operation status * @@ -214,7 +218,7 @@ bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatu * @return true on success, false otherwise */ bool bq27220_get_operation_status( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, Bq27220OperationStatus* operation_status); /** Get gauging status @@ -224,7 +228,9 @@ bool bq27220_get_operation_status( * * @return true on success, false otherwise */ -bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status); +bool bq27220_get_gauging_status( + const FuriHalI2cBusHandle* handle, + Bq27220GaugingStatus* gauging_status); /** Get temperature * @@ -232,7 +238,7 @@ bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatu * * @return temperature in units of 0.1°K */ -uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_temperature(const FuriHalI2cBusHandle* handle); /** Get compensated full charge capacity * @@ -240,7 +246,7 @@ uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle); * * @return full charge capacity in mAh or BQ27220_ERROR */ -uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_full_charge_capacity(const FuriHalI2cBusHandle* handle); /** Get design capacity * @@ -248,7 +254,7 @@ uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle); * * @return design capacity in mAh or BQ27220_ERROR */ -uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_design_capacity(const FuriHalI2cBusHandle* handle); /** Get remaining capacity * @@ -256,7 +262,7 @@ uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle); * * @return remaining capacity in mAh or BQ27220_ERROR */ -uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_remaining_capacity(const FuriHalI2cBusHandle* handle); /** Get predicted remaining battery capacity * @@ -264,7 +270,7 @@ uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle); * * @return state of charge in percents or BQ27220_ERROR */ -uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_state_of_charge(const FuriHalI2cBusHandle* handle); /** Get ratio of full charge capacity over design capacity * @@ -272,4 +278,4 @@ uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle); * * @return state of health in percents or BQ27220_ERROR */ -uint16_t bq27220_get_state_of_health(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_state_of_health(const FuriHalI2cBusHandle* handle); diff --git a/lib/drivers/cc1101.c b/lib/drivers/cc1101.c index 40b286a9b..ff2f0d610 100644 --- a/lib/drivers/cc1101.c +++ b/lib/drivers/cc1101.c @@ -3,7 +3,8 @@ #include #include -static bool cc1101_spi_trx(FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx, uint8_t size) { +static bool + cc1101_spi_trx(const FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx, uint8_t size) { FuriHalCortexTimer timer = furi_hal_cortex_timer_get(CC1101_TIMEOUT * 1000); while(furi_hal_gpio_read(handle->miso)) { @@ -16,7 +17,7 @@ static bool cc1101_spi_trx(FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx return true; } -CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) { +CC1101Status cc1101_strobe(const FuriHalSpiBusHandle* handle, uint8_t strobe) { uint8_t tx[1] = {strobe}; CC1101Status rx[1] = {0}; rx[0].CHIP_RDYn = 1; @@ -27,7 +28,7 @@ CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) { return rx[0]; } -CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) { +CC1101Status cc1101_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) { uint8_t tx[2] = {reg, data}; CC1101Status rx[2] = {0}; rx[0].CHIP_RDYn = 1; @@ -39,7 +40,7 @@ CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t return rx[1]; } -CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data) { +CC1101Status cc1101_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data) { assert(sizeof(CC1101Status) == 1); uint8_t tx[2] = {reg | CC1101_READ, 0}; CC1101Status rx[2] = {0}; @@ -52,33 +53,36 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* return rx[0]; } -uint8_t cc1101_get_partnumber(FuriHalSpiBusHandle* handle) { +uint8_t cc1101_get_partnumber(const FuriHalSpiBusHandle* handle) { uint8_t partnumber = 0; cc1101_read_reg(handle, CC1101_STATUS_PARTNUM | CC1101_BURST, &partnumber); return partnumber; } -uint8_t cc1101_get_version(FuriHalSpiBusHandle* handle) { +uint8_t cc1101_get_version(const FuriHalSpiBusHandle* handle) { uint8_t version = 0; cc1101_read_reg(handle, CC1101_STATUS_VERSION | CC1101_BURST, &version); return version; } -uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle) { +uint8_t cc1101_get_rssi(const FuriHalSpiBusHandle* handle) { uint8_t rssi = 0; cc1101_read_reg(handle, CC1101_STATUS_RSSI | CC1101_BURST, &rssi); return rssi; } -CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_reset(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SRES); } -CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_get_status(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SNOP); } -bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, uint32_t timeout_us) { +bool cc1101_wait_status_state( + const FuriHalSpiBusHandle* handle, + CC1101State state, + uint32_t timeout_us) { bool result = false; CC1101Status status = {0}; FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout_us); @@ -92,35 +96,35 @@ bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, ui return result; } -CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_shutdown(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SPWD); } -CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_calibrate(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SCAL); } -CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_switch_to_idle(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SIDLE); } -CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_switch_to_rx(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SRX); } -CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_switch_to_tx(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_STX); } -CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_flush_rx(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SFRX); } -CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_flush_tx(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SFTX); } -uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value) { +uint32_t cc1101_set_frequency(const FuriHalSpiBusHandle* handle, uint32_t value) { uint64_t real_value = (uint64_t)value * CC1101_FDIV / CC1101_QUARTZ; // Sanity check @@ -135,7 +139,7 @@ uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value) { return (uint32_t)real_frequency; } -uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t value) { +uint32_t cc1101_set_intermediate_frequency(const FuriHalSpiBusHandle* handle, uint32_t value) { uint64_t real_value = value * CC1101_IFDIV / CC1101_QUARTZ; assert((real_value & 0xFF) == real_value); @@ -146,7 +150,7 @@ uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t return (uint32_t)real_frequency; } -void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]) { +void cc1101_set_pa_table(const FuriHalSpiBusHandle* handle, const uint8_t value[8]) { uint8_t tx[9] = {CC1101_PATABLE | CC1101_BURST}; //-V1009 CC1101Status rx[9] = {0}; rx[0].CHIP_RDYn = 1; @@ -159,7 +163,7 @@ void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]) { assert((rx[0].CHIP_RDYn | rx[8].CHIP_RDYn) == 0); } -uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size) { +uint8_t cc1101_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size) { uint8_t buff_tx[64]; uint8_t buff_rx[64]; buff_tx[0] = CC1101_FIFO | CC1101_BURST; @@ -170,7 +174,7 @@ uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint return size; } -uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size) { +uint8_t cc1101_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size) { uint8_t buff_trx[2]; buff_trx[0] = CC1101_FIFO | CC1101_READ | CC1101_BURST; diff --git a/lib/drivers/cc1101.h b/lib/drivers/cc1101.h index c8c552bec..2828f1cdf 100644 --- a/lib/drivers/cc1101.h +++ b/lib/drivers/cc1101.h @@ -19,7 +19,7 @@ extern "C" { * * @return device status */ -CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe); +CC1101Status cc1101_strobe(const FuriHalSpiBusHandle* handle, uint8_t strobe); /** Write device register * @@ -29,7 +29,7 @@ CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe); * * @return device status */ -CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data); +CC1101Status cc1101_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data); /** Read device register * @@ -39,7 +39,7 @@ CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t * * @return device status */ -CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data); +CC1101Status cc1101_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data); /* High level API */ @@ -49,7 +49,7 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* * * @return CC1101Status structure */ -CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_reset(const FuriHalSpiBusHandle* handle); /** Get status * @@ -57,7 +57,7 @@ CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle); * * @return CC1101Status structure */ -CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_get_status(const FuriHalSpiBusHandle* handle); /** Wait specific chip state * @@ -67,7 +67,10 @@ CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle); * * @return true on success, false otherwise */ -bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, uint32_t timeout_us); +bool cc1101_wait_status_state( + const FuriHalSpiBusHandle* handle, + CC1101State state, + uint32_t timeout_us); /** Enable shutdown mode * @@ -75,7 +78,7 @@ bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, ui * * @return CC1101Status structure */ -CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_shutdown(const FuriHalSpiBusHandle* handle); /** Get Partnumber * @@ -83,7 +86,7 @@ CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle); * * @return part number id */ -uint8_t cc1101_get_partnumber(FuriHalSpiBusHandle* handle); +uint8_t cc1101_get_partnumber(const FuriHalSpiBusHandle* handle); /** Get Version * @@ -91,7 +94,7 @@ uint8_t cc1101_get_partnumber(FuriHalSpiBusHandle* handle); * * @return version */ -uint8_t cc1101_get_version(FuriHalSpiBusHandle* handle); +uint8_t cc1101_get_version(const FuriHalSpiBusHandle* handle); /** Get raw RSSI value * @@ -99,7 +102,7 @@ uint8_t cc1101_get_version(FuriHalSpiBusHandle* handle); * * @return rssi value */ -uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle); +uint8_t cc1101_get_rssi(const FuriHalSpiBusHandle* handle); /** Calibrate oscillator * @@ -107,13 +110,13 @@ uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle); * * @return CC1101Status structure */ -CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_calibrate(const FuriHalSpiBusHandle* handle); /** Switch to idle * * @param handle - pointer to FuriHalSpiHandle */ -CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_idle(const FuriHalSpiBusHandle* handle); /** Switch to RX * @@ -121,7 +124,7 @@ CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle); * * @return CC1101Status structure */ -CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_rx(const FuriHalSpiBusHandle* handle); /** Switch to TX * @@ -129,7 +132,7 @@ CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle); * * @return CC1101Status structure */ -CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_tx(const FuriHalSpiBusHandle* handle); /** Flush RX FIFO * @@ -137,13 +140,13 @@ CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle); * * @return CC1101Status structure */ -CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_flush_rx(const FuriHalSpiBusHandle* handle); /** Flush TX FIFO * * @param handle - pointer to FuriHalSpiHandle */ -CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_flush_tx(const FuriHalSpiBusHandle* handle); /** Set Frequency * @@ -152,7 +155,7 @@ CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle); * * @return real frequency that were synthesized */ -uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value); +uint32_t cc1101_set_frequency(const FuriHalSpiBusHandle* handle, uint32_t value); /** Set Intermediate Frequency * @@ -161,14 +164,14 @@ uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value); * * @return real inermediate frequency that were synthesized */ -uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t value); +uint32_t cc1101_set_intermediate_frequency(const FuriHalSpiBusHandle* handle, uint32_t value); /** Set Power Amplifier level table, ramp * * @param handle - pointer to FuriHalSpiHandle * @param value - array of power level values */ -void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]); +void cc1101_set_pa_table(const FuriHalSpiBusHandle* handle, const uint8_t value[8]); /** Write FIFO * @@ -178,7 +181,7 @@ void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]); * * @return size, written bytes count */ -uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size); +uint8_t cc1101_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size); /** Read FIFO * @@ -188,7 +191,7 @@ uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint * * @return size, read bytes count */ -uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size); +uint8_t cc1101_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size); #ifdef __cplusplus } diff --git a/lib/drivers/lp5562.c b/lib/drivers/lp5562.c index 30a5b559a..7db9bbce4 100644 --- a/lib/drivers/lp5562.c +++ b/lib/drivers/lp5562.c @@ -3,12 +3,12 @@ #include "lp5562_reg.h" #include -void lp5562_reset(FuriHalI2cBusHandle* handle) { +void lp5562_reset(const FuriHalI2cBusHandle* handle) { Reg0D_Reset reg = {.value = 0xFF}; furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x0D, *(uint8_t*)®, LP5562_I2C_TIMEOUT); } -void lp5562_configure(FuriHalI2cBusHandle* handle) { +void lp5562_configure(const FuriHalI2cBusHandle* handle) { Reg08_Config config = {.INT_CLK_EN = true, .PS_EN = true, .PWM_HF = true}; furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x08, *(uint8_t*)&config, LP5562_I2C_TIMEOUT); @@ -21,14 +21,17 @@ void lp5562_configure(FuriHalI2cBusHandle* handle) { furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x70, *(uint8_t*)&map, LP5562_I2C_TIMEOUT); } -void lp5562_enable(FuriHalI2cBusHandle* handle) { +void lp5562_enable(const FuriHalI2cBusHandle* handle) { Reg00_Enable reg = {.CHIP_EN = true, .LOG_EN = true}; furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x00, *(uint8_t*)®, LP5562_I2C_TIMEOUT); //>488μs delay is required after writing to 0x00 register, otherwise program engine will not work furi_delay_us(500); } -void lp5562_set_channel_current(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value) { +void lp5562_set_channel_current( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + uint8_t value) { uint8_t reg_no; if(channel == LP5562ChannelRed) { reg_no = LP5562_CHANNEL_RED_CURRENT_REGISTER; @@ -44,7 +47,10 @@ void lp5562_set_channel_current(FuriHalI2cBusHandle* handle, LP5562Channel chann furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, reg_no, value, LP5562_I2C_TIMEOUT); } -void lp5562_set_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value) { +void lp5562_set_channel_value( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + uint8_t value) { uint8_t reg_no; if(channel == LP5562ChannelRed) { reg_no = LP5562_CHANNEL_RED_VALUE_REGISTER; @@ -60,7 +66,7 @@ void lp5562_set_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, reg_no, value, LP5562_I2C_TIMEOUT); } -uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel) { +uint8_t lp5562_get_channel_value(const FuriHalI2cBusHandle* handle, LP5562Channel channel) { uint8_t reg_no; uint8_t value; if(channel == LP5562ChannelRed) { @@ -78,7 +84,10 @@ uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel chan return value; } -void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, LP5562Engine src) { +void lp5562_set_channel_src( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + LP5562Engine src) { uint8_t reg_val = 0; uint8_t bit_offset = 0; @@ -107,7 +116,7 @@ void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, } void lp5562_execute_program( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint16_t* program) { @@ -155,7 +164,7 @@ void lp5562_execute_program( furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x00, enable_reg, LP5562_I2C_TIMEOUT); } -void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng) { +void lp5562_stop_program(const FuriHalI2cBusHandle* handle, LP5562Engine eng) { if((eng < LP5562Engine1) || (eng > LP5562Engine3)) return; uint8_t reg_val = 0; uint8_t bit_offset = 0; @@ -169,7 +178,7 @@ void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng) { } void lp5562_execute_ramp( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint8_t val_start, @@ -213,7 +222,7 @@ void lp5562_execute_ramp( } void lp5562_execute_blink( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint16_t on_time, diff --git a/lib/drivers/lp5562.h b/lib/drivers/lp5562.h index f5ebeeae2..2e54e1ce3 100644 --- a/lib/drivers/lp5562.h +++ b/lib/drivers/lp5562.h @@ -20,39 +20,48 @@ typedef enum { } LP5562Engine; /** Initialize Driver */ -void lp5562_reset(FuriHalI2cBusHandle* handle); +void lp5562_reset(const FuriHalI2cBusHandle* handle); /** Configure Driver */ -void lp5562_configure(FuriHalI2cBusHandle* handle); +void lp5562_configure(const FuriHalI2cBusHandle* handle); /** Enable Driver */ -void lp5562_enable(FuriHalI2cBusHandle* handle); +void lp5562_enable(const FuriHalI2cBusHandle* handle); /** Set channel current */ -void lp5562_set_channel_current(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value); +void lp5562_set_channel_current( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + uint8_t value); /** Set channel PWM value */ -void lp5562_set_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value); +void lp5562_set_channel_value( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + uint8_t value); /** Get channel PWM value */ -uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel); +uint8_t lp5562_get_channel_value(const FuriHalI2cBusHandle* handle, LP5562Channel channel); /** Set channel source */ -void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, LP5562Engine src); +void lp5562_set_channel_src( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + LP5562Engine src); /** Execute program sequence */ void lp5562_execute_program( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint16_t* program); /** Stop program sequence */ -void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng); +void lp5562_stop_program(const FuriHalI2cBusHandle* handle, LP5562Engine eng); /** Execute ramp program sequence */ void lp5562_execute_ramp( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint8_t val_start, @@ -61,7 +70,7 @@ void lp5562_execute_ramp( /** Start blink program sequence */ void lp5562_execute_blink( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint16_t on_time, diff --git a/lib/drivers/st25r3916.c b/lib/drivers/st25r3916.c index f8dc9a5eb..0721a52c7 100644 --- a/lib/drivers/st25r3916.c +++ b/lib/drivers/st25r3916.c @@ -2,7 +2,7 @@ #include -void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask) { +void st25r3916_mask_irq(const FuriHalSpiBusHandle* handle, uint32_t mask) { furi_assert(handle); uint8_t irq_mask_regs[4] = { @@ -14,7 +14,7 @@ void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask) { st25r3916_write_burst_regs(handle, ST25R3916_REG_IRQ_MASK_MAIN, irq_mask_regs, 4); } -uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle) { +uint32_t st25r3916_get_irq(const FuriHalSpiBusHandle* handle) { furi_assert(handle); uint8_t irq_regs[4] = {}; @@ -32,7 +32,7 @@ uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle) { return irq; } -void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits) { +void st25r3916_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits) { furi_assert(handle); furi_assert(buff); @@ -45,7 +45,7 @@ void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size } bool st25r3916_read_fifo( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t buff_size, size_t* buff_bits) { diff --git a/lib/drivers/st25r3916.h b/lib/drivers/st25r3916.h index 0e77b6317..3eddaa430 100644 --- a/lib/drivers/st25r3916.h +++ b/lib/drivers/st25r3916.h @@ -75,7 +75,7 @@ extern "C" { * @param handle - pointer to FuriHalSpiBusHandle instance * @param mask - mask of interrupts to be disabled */ -void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask); +void st25r3916_mask_irq(const FuriHalSpiBusHandle* handle, uint32_t mask); /** Get st25r3916 interrupts * @@ -83,7 +83,7 @@ void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask); * * @return received interrupts */ -uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle); +uint32_t st25r3916_get_irq(const FuriHalSpiBusHandle* handle); /** Write FIFO * @@ -91,7 +91,7 @@ uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle); * @param buff - buffer to write to FIFO * @param bits - number of bits to write */ -void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits); +void st25r3916_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits); /** Read FIFO * @@ -103,7 +103,7 @@ void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size * @return true if read success, false otherwise */ bool st25r3916_read_fifo( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t buff_size, size_t* buff_bits); diff --git a/lib/drivers/st25r3916_reg.c b/lib/drivers/st25r3916_reg.c index f7a47d463..cdbf0fd3d 100644 --- a/lib/drivers/st25r3916_reg.c +++ b/lib/drivers/st25r3916_reg.c @@ -28,18 +28,18 @@ (ST25R3916_CMD_LEN + \ ST25R3916_FIFO_DEPTH) /*!< ST25R3916 communication buffer: CMD + FIFO length */ -static void st25r3916_reg_tx_byte(FuriHalSpiBusHandle* handle, uint8_t byte) { +static void st25r3916_reg_tx_byte(const FuriHalSpiBusHandle* handle, uint8_t byte) { uint8_t val = byte; furi_hal_spi_bus_tx(handle, &val, 1, 5); } -void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { +void st25r3916_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { furi_check(handle); st25r3916_read_burst_regs(handle, reg, val, 1); } void st25r3916_read_burst_regs( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg_start, uint8_t* values, uint8_t length) { @@ -59,14 +59,14 @@ void st25r3916_read_burst_regs( furi_hal_gpio_write(handle->cs, true); } -void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { +void st25r3916_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { furi_check(handle); uint8_t reg_val = val; st25r3916_write_burst_regs(handle, reg, ®_val, 1); } void st25r3916_write_burst_regs( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg_start, const uint8_t* values, uint8_t length) { @@ -86,7 +86,10 @@ void st25r3916_write_burst_regs( furi_hal_gpio_write(handle->cs, true); } -void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t length) { +void st25r3916_reg_write_fifo( + const FuriHalSpiBusHandle* handle, + const uint8_t* buff, + size_t length) { furi_check(handle); furi_check(buff); furi_check(length); @@ -98,7 +101,7 @@ void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, furi_hal_gpio_write(handle->cs, true); } -void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { +void st25r3916_reg_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { furi_check(handle); furi_check(buff); furi_check(length); @@ -110,7 +113,10 @@ void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t furi_hal_gpio_write(handle->cs, true); } -void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length) { +void st25r3916_write_pta_mem( + const FuriHalSpiBusHandle* handle, + const uint8_t* values, + size_t length) { furi_check(handle); furi_check(values); furi_check(length); @@ -122,7 +128,7 @@ void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, furi_hal_gpio_write(handle->cs, true); } -void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { +void st25r3916_read_pta_mem(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { furi_check(handle); furi_check(buff); furi_check(length); @@ -136,7 +142,10 @@ void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t l memcpy(buff, tmp_buff + 1, length); } -void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length) { +void st25r3916_write_ptf_mem( + const FuriHalSpiBusHandle* handle, + const uint8_t* values, + size_t length) { furi_check(handle); furi_check(values); @@ -146,7 +155,7 @@ void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, furi_hal_gpio_write(handle->cs, true); } -void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { +void st25r3916_write_pttsn_mem(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { furi_check(handle); furi_check(buff); @@ -156,7 +165,7 @@ void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_ furi_hal_gpio_write(handle->cs, true); } -void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd) { +void st25r3916_direct_cmd(const FuriHalSpiBusHandle* handle, uint8_t cmd) { furi_check(handle); furi_hal_gpio_write(handle->cs, false); @@ -164,7 +173,7 @@ void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd) { furi_hal_gpio_write(handle->cs, true); } -void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { +void st25r3916_read_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { furi_check(handle); furi_hal_gpio_write(handle->cs, false); @@ -174,7 +183,7 @@ void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* furi_hal_gpio_write(handle->cs, true); } -void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { +void st25r3916_write_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { furi_check(handle); furi_hal_gpio_write(handle->cs, false); @@ -184,7 +193,7 @@ void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t furi_hal_gpio_write(handle->cs, true); } -void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask) { +void st25r3916_clear_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask) { furi_check(handle); uint8_t reg_val = 0; @@ -195,7 +204,7 @@ void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t } } -void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask) { +void st25r3916_set_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask) { furi_check(handle); uint8_t reg_val = 0; @@ -207,7 +216,7 @@ void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t se } void st25r3916_change_reg_bits( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t value) { @@ -217,7 +226,7 @@ void st25r3916_change_reg_bits( } void st25r3916_modify_reg( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask, uint8_t set_mask) { @@ -233,7 +242,7 @@ void st25r3916_modify_reg( } void st25r3916_change_test_reg_bits( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t value) { @@ -248,7 +257,7 @@ void st25r3916_change_test_reg_bits( } } -bool st25r3916_check_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val) { +bool st25r3916_check_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val) { furi_check(handle); uint8_t reg_val = 0; diff --git a/lib/drivers/st25r3916_reg.h b/lib/drivers/st25r3916_reg.h index 5163c4423..524f93cc7 100644 --- a/lib/drivers/st25r3916_reg.h +++ b/lib/drivers/st25r3916_reg.h @@ -967,7 +967,7 @@ extern "C" { * @param reg - register address * @param val - pointer to the variable to store the read value */ -void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); +void st25r3916_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); /** Read multiple registers * @@ -977,7 +977,7 @@ void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); * @param length - number of registers to read */ void st25r3916_read_burst_regs( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg_start, uint8_t* values, uint8_t length); @@ -988,7 +988,7 @@ void st25r3916_read_burst_regs( * @param reg - register address * @param val - value to write */ -void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); +void st25r3916_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); /** Write multiple registers * @@ -998,7 +998,7 @@ void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); * @param length - number of registers to write */ void st25r3916_write_burst_regs( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg_start, const uint8_t* values, uint8_t length); @@ -1009,7 +1009,10 @@ void st25r3916_write_burst_regs( * @param buff - buffer to write to FIFO * @param length - number of bytes to write */ -void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t length); +void st25r3916_reg_write_fifo( + const FuriHalSpiBusHandle* handle, + const uint8_t* buff, + size_t length); /** Read fifo register * @@ -1017,7 +1020,7 @@ void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, * @param buff - buffer to store the read values * @param length - number of bytes to read */ -void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length); +void st25r3916_reg_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length); /** Write PTA memory register * @@ -1025,7 +1028,10 @@ void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t * @param values - pointer to buffer to write * @param length - number of bytes to write */ -void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length); +void st25r3916_write_pta_mem( + const FuriHalSpiBusHandle* handle, + const uint8_t* values, + size_t length); /** Read PTA memory register * @@ -1033,7 +1039,7 @@ void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, * @param values - buffer to store the read values * @param length - number of bytes to read */ -void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); +void st25r3916_read_pta_mem(const FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); /** Write PTF memory register * @@ -1041,7 +1047,10 @@ void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t * @param values - pointer to buffer to write * @param length - number of bytes to write */ -void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length); +void st25r3916_write_ptf_mem( + const FuriHalSpiBusHandle* handle, + const uint8_t* values, + size_t length); /** Read PTTSN memory register * @@ -1049,21 +1058,21 @@ void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, * @param values - pointer to buffer to write * @param length - number of bytes to write */ -void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); +void st25r3916_write_pttsn_mem(const FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); /** Send Direct command * * @param handle - pointer to FuriHalSpiBusHandle instance * @param cmd - direct command */ -void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd); +void st25r3916_direct_cmd(const FuriHalSpiBusHandle* handle, uint8_t cmd); /** Read test register * @param handle - pointer to FuriHalSpiBusHandle instance * @param reg - register address * @param val - pointer to the variable to store the read value */ -void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); +void st25r3916_read_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); /** Write test register * @@ -1071,7 +1080,7 @@ void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* * @param reg - register address * @param val - value to write */ -void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); +void st25r3916_write_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); /** Clear register bits * @@ -1079,7 +1088,7 @@ void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t * @param reg - register address * @param clr_mask - bit mask to clear */ -void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask); +void st25r3916_clear_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask); /** Set register bits * @@ -1087,7 +1096,7 @@ void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t * @param reg - register address * @param set_mask - bit mask to set */ -void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask); +void st25r3916_set_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask); /** Change register bits * @@ -1097,7 +1106,7 @@ void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t se * @param value - new register value to write */ void st25r3916_change_reg_bits( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t value); @@ -1110,7 +1119,7 @@ void st25r3916_change_reg_bits( * @param set_mask - bit mask to set */ void st25r3916_modify_reg( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask, uint8_t set_mask); @@ -1123,7 +1132,7 @@ void st25r3916_modify_reg( * @param value - new register value to write */ void st25r3916_change_test_reg_bits( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t value); @@ -1137,7 +1146,7 @@ void st25r3916_change_test_reg_bits( * * @return true if register value matches the expected value, false otherwise */ -bool st25r3916_check_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val); +bool st25r3916_check_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val); #ifdef __cplusplus } diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index d07022e12..8aebf853f 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -8,6 +8,11 @@ #include "flipper_format_stream.h" #include "flipper_format_stream_i.h" +// permits direct casting between `FlipperFormatOffset` and `StreamOffset` +static_assert((size_t)FlipperFormatOffsetFromCurrent == (size_t)StreamOffsetFromCurrent); +static_assert((size_t)FlipperFormatOffsetFromStart == (size_t)StreamOffsetFromStart); +static_assert((size_t)FlipperFormatOffsetFromEnd == (size_t)StreamOffsetFromEnd); + /********************************** Private **********************************/ struct FlipperFormat { Stream* stream; @@ -127,6 +132,17 @@ bool flipper_format_rewind(FlipperFormat* flipper_format) { return stream_rewind(flipper_format->stream); } +size_t flipper_format_tell(FlipperFormat* flipper_format) { + furi_check(flipper_format); + return stream_tell(flipper_format->stream); +} + +bool flipper_format_seek(FlipperFormat* flipper_format, int32_t offset, FlipperFormatOffset anchor) { + furi_check(flipper_format); + // direct usage of `anchor` made valid by `static_assert`s at the top of this file + return stream_seek(flipper_format->stream, offset, (StreamOffset)anchor); +} + bool flipper_format_seek_to_end(FlipperFormat* flipper_format) { furi_check(flipper_format); return stream_seek(flipper_format->stream, 0, StreamOffsetFromEnd); diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index 4a1bb767b..5b13496e1 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -94,6 +94,12 @@ extern "C" { typedef struct FlipperFormat FlipperFormat; +typedef enum { + FlipperFormatOffsetFromCurrent, + FlipperFormatOffsetFromStart, + FlipperFormatOffsetFromEnd, +} FlipperFormatOffset; + /** Allocate FlipperFormat as string. * * @return FlipperFormat* pointer to a FlipperFormat instance @@ -216,6 +222,24 @@ void flipper_format_set_strict_mode(FlipperFormat* flipper_format, bool strict_m */ bool flipper_format_rewind(FlipperFormat* flipper_format); +/** Get the RW pointer position + * + * @param flipper_format Pointer to a FlipperFormat instance + * + * @return RW pointer position + */ +size_t flipper_format_tell(FlipperFormat* flipper_format); + +/** Set the RW pointer position to an arbitrary value + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param offset Offset relative to the anchor point + * @param anchor Anchor point (e.g. start of file) + * + * @return True on success + */ +bool flipper_format_seek(FlipperFormat* flipper_format, int32_t offset, FlipperFormatOffset anchor); + /** Move the RW pointer at the end. Can be useful if you want to add some data * after reading. * diff --git a/lib/ibutton/ibutton_worker_modes.c b/lib/ibutton/ibutton_worker_modes.c index 8efb78f03..ff76e784d 100644 --- a/lib/ibutton/ibutton_worker_modes.c +++ b/lib/ibutton/ibutton_worker_modes.c @@ -1,9 +1,10 @@ #include "ibutton_worker_i.h" #include +#include #include -#include +#include #include "ibutton_protocols.h" @@ -75,7 +76,9 @@ void ibutton_worker_mode_idle_stop(iButtonWorker* worker) { void ibutton_worker_mode_read_start(iButtonWorker* worker) { UNUSED(worker); - if(!furi_hal_power_is_otg_enabled()) furi_hal_power_enable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } void ibutton_worker_mode_read_tick(iButtonWorker* worker) { @@ -90,7 +93,9 @@ void ibutton_worker_mode_read_tick(iButtonWorker* worker) { void ibutton_worker_mode_read_stop(iButtonWorker* worker) { UNUSED(worker); - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } /*********************** EMULATE ***********************/ @@ -120,7 +125,9 @@ void ibutton_worker_mode_emulate_stop(iButtonWorker* worker) { void ibutton_worker_mode_write_common_start(iButtonWorker* worker) { //-V524 UNUSED(worker); - if(!furi_hal_power_is_otg_enabled()) furi_hal_power_enable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } void ibutton_worker_mode_write_id_tick(iButtonWorker* worker) { @@ -149,5 +156,7 @@ void ibutton_worker_mode_write_copy_tick(iButtonWorker* worker) { void ibutton_worker_mode_write_common_stop(iButtonWorker* worker) { //-V524 UNUSED(worker); - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } diff --git a/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c index a33db5143..55462ae2c 100644 --- a/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c +++ b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c @@ -7,7 +7,7 @@ #include "protocol_ds1420.h" #include "protocol_ds_generic.h" -const iButtonProtocolDallasBase* ibutton_protocols_dallas[] = { +const iButtonProtocolDallasBase* const ibutton_protocols_dallas[] = { [iButtonProtocolDS1990] = &ibutton_protocol_ds1990, [iButtonProtocolDS1992] = &ibutton_protocol_ds1992, [iButtonProtocolDS1996] = &ibutton_protocol_ds1996, diff --git a/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h index ca789a10c..2d7fa53ef 100644 --- a/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h +++ b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h @@ -15,4 +15,4 @@ typedef enum { iButtonProtocolDSMax, } iButtonProtocolDallas; -extern const iButtonProtocolDallasBase* ibutton_protocols_dallas[]; +extern const iButtonProtocolDallasBase* const ibutton_protocols_dallas[]; diff --git a/lib/ibutton/protocols/misc/protocol_group_misc_defs.c b/lib/ibutton/protocols/misc/protocol_group_misc_defs.c index 09ae0bdc7..f8cae0463 100644 --- a/lib/ibutton/protocols/misc/protocol_group_misc_defs.c +++ b/lib/ibutton/protocols/misc/protocol_group_misc_defs.c @@ -3,7 +3,7 @@ #include "protocol_cyfral.h" #include "protocol_metakom.h" -const ProtocolBase* ibutton_protocols_misc[] = { +const ProtocolBase* const ibutton_protocols_misc[] = { [iButtonProtocolMiscCyfral] = &ibutton_protocol_misc_cyfral, [iButtonProtocolMiscMetakom] = &ibutton_protocol_misc_metakom, /* Add new misc protocols here */ diff --git a/lib/ibutton/protocols/misc/protocol_group_misc_defs.h b/lib/ibutton/protocols/misc/protocol_group_misc_defs.h index 0a7f92847..cde6b0aa9 100644 --- a/lib/ibutton/protocols/misc/protocol_group_misc_defs.h +++ b/lib/ibutton/protocols/misc/protocol_group_misc_defs.h @@ -8,4 +8,4 @@ typedef enum { iButtonProtocolMiscMax, } iButtonProtocolMisc; -extern const ProtocolBase* ibutton_protocols_misc[]; +extern const ProtocolBase* const ibutton_protocols_misc[]; diff --git a/lib/ibutton/protocols/protocol_group_defs.c b/lib/ibutton/protocols/protocol_group_defs.c index 40a360f0e..ac428f410 100644 --- a/lib/ibutton/protocols/protocol_group_defs.c +++ b/lib/ibutton/protocols/protocol_group_defs.c @@ -3,7 +3,7 @@ #include "dallas/protocol_group_dallas.h" #include "misc/protocol_group_misc.h" -const iButtonProtocolGroupBase* ibutton_protocol_groups[] = { +const iButtonProtocolGroupBase* const ibutton_protocol_groups[] = { [iButtonProtocolGroupDallas] = &ibutton_protocol_group_dallas, [iButtonProtocolGroupMisc] = &ibutton_protocol_group_misc, }; diff --git a/lib/ibutton/protocols/protocol_group_defs.h b/lib/ibutton/protocols/protocol_group_defs.h index 2d41e3cb8..2c00dfab4 100644 --- a/lib/ibutton/protocols/protocol_group_defs.h +++ b/lib/ibutton/protocols/protocol_group_defs.h @@ -8,4 +8,4 @@ typedef enum { iButtonProtocolGroupMax } iButtonProtocolGroup; -extern const iButtonProtocolGroupBase* ibutton_protocol_groups[]; +extern const iButtonProtocolGroupBase* const ibutton_protocol_groups[]; diff --git a/lib/ieee754_parse_wrap/SConscript b/lib/ieee754_parse_wrap/SConscript new file mode 100644 index 000000000..dc60036e0 --- /dev/null +++ b/lib/ieee754_parse_wrap/SConscript @@ -0,0 +1,31 @@ +Import("env") + +wrapped_fn_list = [ + "strtof", + "strtod", +] + +for wrapped_fn in wrapped_fn_list: + env.Append( + LINKFLAGS=[ + "-Wl,--wrap," + wrapped_fn, + ] + ) + +env.Append( + SDK_HEADERS=[ + File("wrappers.h"), + ], + LINT_SOURCES=[ + Dir("."), + ], +) + +libenv = env.Clone(FW_LIB_NAME="ieee754_parse_wrap") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c*", ".") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/ieee754_parse_wrap/wrappers.c b/lib/ieee754_parse_wrap/wrappers.c new file mode 100644 index 000000000..33bd38c35 --- /dev/null +++ b/lib/ieee754_parse_wrap/wrappers.c @@ -0,0 +1,14 @@ +#include "wrappers.h" + +// Based on the disassembly, providing NULL as `locale` is fine. +// The default `strtof` and `strtod` provided in the same libc_nano also just +// call these functions, but with an actual locale structure which was taking up +// lots of .data space (364 bytes). + +float __wrap_strtof(const char* in, char** tail) { + return strtof_l(in, tail, NULL); +} + +double __wrap_strtod(const char* in, char** tail) { + return strtod_l(in, tail, NULL); +} diff --git a/lib/ieee754_parse_wrap/wrappers.h b/lib/ieee754_parse_wrap/wrappers.h new file mode 100644 index 000000000..17e7acf83 --- /dev/null +++ b/lib/ieee754_parse_wrap/wrappers.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +float __wrap_strtof(const char* in, char** tail); +double __wrap_strtod(const char* in, char** tail); + +#ifdef __cplusplus +} +#endif diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index d0569b300..645d2bd82 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -510,9 +510,6 @@ static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) { LFRFIDProtocol protocol = worker->protocol; LFRFIDWriteRequest* request = malloc(sizeof(LFRFIDWriteRequest)); - request->write_type = LFRFIDWriteTypeT5577; - - bool can_be_written = protocol_dict_get_write_data(worker->protocols, protocol, request); uint32_t write_start_time = furi_get_tick(); bool too_long = false; @@ -521,63 +518,88 @@ static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) { size_t data_size = protocol_dict_get_data_size(worker->protocols, protocol); uint8_t* verify_data = malloc(data_size); uint8_t* read_data = malloc(data_size); + protocol_dict_get_data(worker->protocols, protocol, verify_data, data_size); - if(can_be_written) { - while(!lfrfid_worker_check_for_stop(worker)) { - FURI_LOG_D(TAG, "Data write"); - t5577_write(&request->t5577); + while(!lfrfid_worker_check_for_stop(worker)) { + FURI_LOG_D(TAG, "Data write"); + uint16_t skips = 0; + for(size_t i = 0; i < LFRFIDWriteTypeMax; i++) { + memset(request, 0, sizeof(LFRFIDWriteRequest)); + LFRFIDWriteType write_type = i; + request->write_type = write_type; - ProtocolId read_result = PROTOCOL_NO; - LFRFIDWorkerReadState state = lfrfid_worker_read_internal( - worker, - protocol_dict_get_features(worker->protocols, protocol), - LFRFID_WORKER_WRITE_VERIFY_TIME_MS, - &read_result); + protocol_dict_set_data(worker->protocols, protocol, verify_data, data_size); - if(state == LFRFIDWorkerReadOK) { - bool read_success = false; + bool can_be_written = + protocol_dict_get_write_data(worker->protocols, protocol, request); - if(read_result == protocol) { - protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); - - if(memcmp(read_data, verify_data, data_size) == 0) { - read_success = true; - } - } - - if(read_success) { + if(!can_be_written) { + skips++; + if(skips == LFRFIDWriteTypeMax) { if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx); + worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx); } break; - } else { - unsuccessful_reads++; + } + continue; + } - if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) { - if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx); - } + memset(read_data, 0, data_size); + + if(request->write_type == LFRFIDWriteTypeT5577) { + t5577_write(&request->t5577); + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + em4305_write(&request->em4305); + } else { + furi_crash("Unknown write type"); + } + } + ProtocolId read_result = PROTOCOL_NO; + LFRFIDWorkerReadState state = lfrfid_worker_read_internal( + worker, + protocol_dict_get_features(worker->protocols, protocol), + LFRFID_WORKER_WRITE_VERIFY_TIME_MS, + &read_result); + + if(state == LFRFIDWorkerReadOK) { + bool read_success = false; + + if(read_result == protocol) { + protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); + + if(memcmp(read_data, verify_data, data_size) == 0) { + read_success = true; + } + } + + if(read_success) { + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx); + } + break; + } else { + unsuccessful_reads++; + + if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) { + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx); } } - } else if(state == LFRFIDWorkerReadExit) { - break; } + } else if(state == LFRFIDWorkerReadExit) { + break; + } - if(!too_long && - (furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) { - too_long = true; - if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx); - } + if(!too_long && + (furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) { + too_long = true; + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx); } + } - lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS); - } - } else { - if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx); - } + lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS); } free(request); diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 0e65dc045..22533c4a0 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -20,8 +20,9 @@ #include "protocol_nexwatch.h" #include "protocol_securakey.h" #include "protocol_gproxii.h" +#include "protocol_noralsy.h" -const ProtocolBase* lfrfid_protocols[] = { +const ProtocolBase* const lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, [LFRFIDProtocolEM4100_32] = &protocol_em4100_32, [LFRFIDProtocolEM4100_16] = &protocol_em4100_16, @@ -45,4 +46,5 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolNexwatch] = &protocol_nexwatch, [LFRFIDProtocolSecurakey] = &protocol_securakey, [LFRFIDProtocolGProxII] = &protocol_gproxii, + [LFRFIDProtocolNoralsy] = &protocol_noralsy, }; diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 12ac2dddd..616e1c807 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -1,6 +1,7 @@ #pragma once #include #include "../tools/t5577.h" +#include "../tools/em4305.h" typedef enum { LFRFIDFeatureASK = 1 << 0, /** ASK Demodulation */ @@ -31,18 +32,24 @@ typedef enum { LFRFIDProtocolNexwatch, LFRFIDProtocolSecurakey, LFRFIDProtocolGProxII, + LFRFIDProtocolNoralsy, + LFRFIDProtocolMax, } LFRFIDProtocol; -extern const ProtocolBase* lfrfid_protocols[]; +extern const ProtocolBase* const lfrfid_protocols[]; typedef enum { LFRFIDWriteTypeT5577, + LFRFIDWriteTypeEM4305, + + LFRFIDWriteTypeMax, } LFRFIDWriteType; typedef struct { LFRFIDWriteType write_type; union { LFRFIDT5577 t5577; + LFRFIDEM4305 em4305; }; } LFRFIDWriteRequest; diff --git a/lib/lfrfid/protocols/protocol_electra.c b/lib/lfrfid/protocols/protocol_electra.c index 25fcdc514..50d82c8f1 100644 --- a/lib/lfrfid/protocols/protocol_electra.c +++ b/lib/lfrfid/protocols/protocol_electra.c @@ -407,6 +407,24 @@ bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) { request->t5577.blocks_to_write = 5; result = true; } + if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(64) | (8 << EM4x05_MAXBLOCK_SHIFT)); + uint64_t encoded_data_reversed = 0; + uint64_t encoded_epilogue_reversed = 0; + for(uint8_t i = 0; i < 64; i++) { + encoded_data_reversed = (encoded_data_reversed << 1) | + ((protocol->encoded_base_data >> i) & 1); + encoded_epilogue_reversed = (encoded_epilogue_reversed << 1) | + ((protocol->encoded_epilogue >> i) & 1); + } + request->em4305.word[5] = encoded_data_reversed & 0xFFFFFFFF; + request->em4305.word[6] = encoded_data_reversed >> 32; + request->em4305.word[7] = encoded_epilogue_reversed & 0xFFFFFFFF; + request->em4305.word[8] = encoded_epilogue_reversed >> 32; + request->em4305.mask = 0x01F0; + result = true; + } return result; } diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index e68bc0e2e..ed18133dc 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -69,6 +69,19 @@ uint32_t protocol_em4100_get_t5577_bitrate(ProtocolEM4100* proto) { } } +uint32_t protocol_em4100_get_em4305_bitrate(ProtocolEM4100* proto) { + switch(proto->clock_per_bit) { + case 64: + return EM4x05_SET_BITRATE(64); + case 32: + return EM4x05_SET_BITRATE(32); + case 16: + return EM4x05_SET_BITRATE(16); + default: + return EM4x05_SET_BITRATE(64); + } +} + uint16_t protocol_em4100_get_short_time_low(ProtocolEM4100* proto) { return EM_READ_SHORT_TIME_BASE / protocol_em4100_get_time_divisor(proto) - EM_READ_JITTER_TIME_BASE / protocol_em4100_get_time_divisor(proto); @@ -339,6 +352,19 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { request->t5577.block[2] = protocol->encoded_data; request->t5577.blocks_to_write = 3; result = true; + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_MANCHESTER | protocol_em4100_get_em4305_bitrate(protocol) | + (6 << EM4x05_MAXBLOCK_SHIFT)); + uint64_t encoded_data_reversed = 0; + for(uint8_t i = 0; i < 64; i++) { + encoded_data_reversed = (encoded_data_reversed << 1) | + ((protocol->encoded_data >> i) & 1); + } + request->em4305.word[5] = encoded_data_reversed; + request->em4305.word[6] = encoded_data_reversed >> 32; + request->em4305.mask = 0x70; + result = true; } return result; } diff --git a/lib/lfrfid/protocols/protocol_gallagher.c b/lib/lfrfid/protocols/protocol_gallagher.c index 9ae0cf80a..bbed99706 100644 --- a/lib/lfrfid/protocols/protocol_gallagher.c +++ b/lib/lfrfid/protocols/protocol_gallagher.c @@ -264,6 +264,20 @@ bool protocol_gallagher_write_data(ProtocolGallagher* protocol, void* data) { request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); request->t5577.blocks_to_write = 4; result = true; + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(32) | (7 << EM4x05_MAXBLOCK_SHIFT)); + uint32_t encoded_data_reversed[3] = {0}; + for(uint8_t i = 0; i < (32 * 3); i++) { + encoded_data_reversed[i / 32] = + (encoded_data_reversed[i / 32] << 1) | + (bit_lib_get_bit(protocol->encoded_data, ((32 * 3) - i)) & 1); + } + request->em4305.word[5] = encoded_data_reversed[2]; + request->em4305.word[6] = encoded_data_reversed[1]; + request->em4305.word[7] = encoded_data_reversed[0]; + request->em4305.mask = 0xF0; + result = true; } return result; } diff --git a/lib/lfrfid/protocols/protocol_nexwatch.c b/lib/lfrfid/protocols/protocol_nexwatch.c index a794a4a8e..08324803f 100644 --- a/lib/lfrfid/protocols/protocol_nexwatch.c +++ b/lib/lfrfid/protocols/protocol_nexwatch.c @@ -21,7 +21,7 @@ typedef struct { uint8_t chk; } ProtocolNexwatchMagic; -ProtocolNexwatchMagic magic_items[] = { +static ProtocolNexwatchMagic magic_items[] = { {0xBE, "Quadrakey", 0}, {0x88, "Nexkey", 0}, {0x86, "Honeywell", 0}}; diff --git a/lib/lfrfid/protocols/protocol_noralsy.c b/lib/lfrfid/protocols/protocol_noralsy.c new file mode 100644 index 000000000..27cf8cb6b --- /dev/null +++ b/lib/lfrfid/protocols/protocol_noralsy.c @@ -0,0 +1,220 @@ +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define NORALSY_CLOCK_PER_BIT (32) + +#define NORALSY_ENCODED_BIT_SIZE (96) +#define NORALSY_ENCODED_BYTE_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8) +#define NORALSY_PREAMBLE_BIT_SIZE (32) +#define NORALSY_PREAMBLE_BYTE_SIZE ((NORALSY_PREAMBLE_BIT_SIZE) / 8) +#define NORALSY_ENCODED_BYTE_FULL_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8) +#define NORALSY_DECODED_DATA_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8) + +#define NORALSY_READ_SHORT_TIME (128) +#define NORALSY_READ_LONG_TIME (256) +#define NORALSY_READ_JITTER_TIME (60) + +#define NORALSY_READ_SHORT_TIME_LOW (NORALSY_READ_SHORT_TIME - NORALSY_READ_JITTER_TIME) +#define NORALSY_READ_SHORT_TIME_HIGH (NORALSY_READ_SHORT_TIME + NORALSY_READ_JITTER_TIME) +#define NORALSY_READ_LONG_TIME_LOW (NORALSY_READ_LONG_TIME - NORALSY_READ_JITTER_TIME) +#define NORALSY_READ_LONG_TIME_HIGH (NORALSY_READ_LONG_TIME + NORALSY_READ_JITTER_TIME) + +#define TAG "NORALSY" + +typedef struct { + uint8_t data[NORALSY_ENCODED_BYTE_SIZE]; + uint8_t encoded_data[NORALSY_ENCODED_BYTE_SIZE]; + + uint8_t encoded_data_index; + bool encoded_polarity; + + ManchesterState decoder_manchester_state; +} ProtocolNoralsy; + +ProtocolNoralsy* protocol_noralsy_alloc(void) { + ProtocolNoralsy* protocol = malloc(sizeof(ProtocolNoralsy)); + return (void*)protocol; +} + +void protocol_noralsy_free(ProtocolNoralsy* protocol) { + free(protocol); +} + +static uint8_t noralsy_chksum(uint8_t* bits, uint8_t len) { + uint8_t sum = 0; + for(uint8_t i = 0; i < len; i += 4) + sum ^= bit_lib_get_bits(bits, i, 4); + return sum & 0x0F; +} + +uint8_t* protocol_noralsy_get_data(ProtocolNoralsy* protocol) { + return protocol->data; +} + +static void protocol_noralsy_decode(ProtocolNoralsy* protocol) { + bit_lib_copy_bits(protocol->data, 0, NORALSY_ENCODED_BIT_SIZE, protocol->encoded_data, 0); +} + +static bool protocol_noralsy_can_be_decoded(ProtocolNoralsy* protocol) { + // check 12 bits preamble + // If necessary, use 0xBB0214FF for 32 bit preamble check + // However, it is not confirmed the 13-16 bit are static. + if(bit_lib_get_bits_16(protocol->encoded_data, 0, 12) != 0b101110110000) return false; + uint8_t calc1 = noralsy_chksum(&protocol->encoded_data[4], 40); + uint8_t calc2 = noralsy_chksum(&protocol->encoded_data[0], 76); + uint8_t chk1 = bit_lib_get_bits(protocol->encoded_data, 72, 4); + uint8_t chk2 = bit_lib_get_bits(protocol->encoded_data, 76, 4); + if(calc1 != chk1 || calc2 != chk2) return false; + + return true; +} + +void protocol_noralsy_decoder_start(ProtocolNoralsy* protocol) { + memset(protocol->encoded_data, 0, NORALSY_ENCODED_BYTE_FULL_SIZE); + manchester_advance( + protocol->decoder_manchester_state, + ManchesterEventReset, + &protocol->decoder_manchester_state, + NULL); +} + +bool protocol_noralsy_decoder_feed(ProtocolNoralsy* protocol, bool level, uint32_t duration) { + bool result = false; + + ManchesterEvent event = ManchesterEventReset; + + if(duration > NORALSY_READ_SHORT_TIME_LOW && duration < NORALSY_READ_SHORT_TIME_HIGH) { + if(!level) { + event = ManchesterEventShortHigh; + } else { + event = ManchesterEventShortLow; + } + } else if(duration > NORALSY_READ_LONG_TIME_LOW && duration < NORALSY_READ_LONG_TIME_HIGH) { + if(!level) { + event = ManchesterEventLongHigh; + } else { + event = ManchesterEventLongLow; + } + } + + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + protocol->decoder_manchester_state, event, &protocol->decoder_manchester_state, &data); + + if(data_ok) { + bit_lib_push_bit(protocol->encoded_data, NORALSY_ENCODED_BYTE_FULL_SIZE, data); + + if(protocol_noralsy_can_be_decoded(protocol)) { + protocol_noralsy_decode(protocol); + result = true; + } + } + } + + return result; +} + +bool protocol_noralsy_encoder_start(ProtocolNoralsy* protocol) { + bit_lib_copy_bits(protocol->encoded_data, 0, NORALSY_ENCODED_BIT_SIZE, protocol->data, 0); + + return true; +} + +LevelDuration protocol_noralsy_encoder_yield(ProtocolNoralsy* protocol) { + bool level = bit_lib_get_bit(protocol->encoded_data, protocol->encoded_data_index); + uint32_t duration = NORALSY_CLOCK_PER_BIT / 2; + + if(protocol->encoded_polarity) { + protocol->encoded_polarity = false; + } else { + level = !level; + + protocol->encoded_polarity = true; + bit_lib_increment_index(protocol->encoded_data_index, NORALSY_ENCODED_BIT_SIZE); + } + + return level_duration_make(level, duration); +} + +bool protocol_noralsy_write_data(ProtocolNoralsy* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + // Correct protocol data by redecoding + protocol_noralsy_encoder_start(protocol); + protocol_noralsy_decode(protocol); + + protocol_noralsy_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = + (LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_32 | + (3 << LFRFID_T5577_MAXBLOCK_SHIFT) | LFRFID_T5577_ST_TERMINATOR); + // In fact, base on the current two dump samples from Iceman server, + // Noralsy are usually T5577s with config = 0x00088C6A + // But the `C` and `A` are not explainable by the ATA5577C datasheet + // and they don't affect reading whatsoever. + // So we are mimicing Proxmark's solution here. Leave those nibbles as zero. + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.blocks_to_write = 4; + result = true; + } + return result; +} + +static void protocol_noralsy_render_data_internal(ProtocolNoralsy* protocol, FuriString* result) { + UNUSED(protocol); + uint32_t raw2 = bit_lib_get_bits_32(protocol->data, 32, 32); + uint32_t raw3 = bit_lib_get_bits_32(protocol->data, 64, 32); + uint32_t cardid = ((raw2 & 0xFFF00000) >> 20) << 16; + cardid |= (raw2 & 0xFF) << 8; + cardid |= ((raw3 & 0xFF000000) >> 24); + + uint8_t year = (raw2 & 0x000ff000) >> 12; + bool tag_is_gen_z = (year > 0x60); + furi_string_printf( + result, + "Card ID: %07lx\n" + "Year: %s%02x", + cardid, + tag_is_gen_z ? "19" : "20", + year); +} + +void protocol_noralsy_render_data(ProtocolNoralsy* protocol, FuriString* result) { + protocol_noralsy_render_data_internal(protocol, result); +} + +void protocol_noralsy_render_brief_data(ProtocolNoralsy* protocol, FuriString* result) { + protocol_noralsy_render_data_internal(protocol, result); +} + +const ProtocolBase protocol_noralsy = { + .name = "Noralsy", + .manufacturer = "Noralsy", + .data_size = NORALSY_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_noralsy_alloc, + .free = (ProtocolFree)protocol_noralsy_free, + .get_data = (ProtocolGetData)protocol_noralsy_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_noralsy_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_noralsy_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_noralsy_encoder_start, + .yield = (ProtocolEncoderYield)protocol_noralsy_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_noralsy_render_data, + .render_brief_data = (ProtocolRenderData)protocol_noralsy_render_brief_data, + .write_data = (ProtocolWriteData)protocol_noralsy_write_data, +}; diff --git a/lib/lfrfid/protocols/protocol_noralsy.h b/lib/lfrfid/protocols/protocol_noralsy.h new file mode 100644 index 000000000..b1ee51a2e --- /dev/null +++ b/lib/lfrfid/protocols/protocol_noralsy.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_noralsy; diff --git a/lib/lfrfid/protocols/protocol_securakey.c b/lib/lfrfid/protocols/protocol_securakey.c index a4bfeca60..947b68e72 100644 --- a/lib/lfrfid/protocols/protocol_securakey.c +++ b/lib/lfrfid/protocols/protocol_securakey.c @@ -61,17 +61,22 @@ uint8_t* protocol_securakey_get_data(ProtocolSecurakey* protocol) { static bool protocol_securakey_can_be_decoded(ProtocolSecurakey* protocol) { // check 19 bits preamble + format flag if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111000000000) { - protocol->bit_format = 0; - return true; + if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 54, BitLibParityAlways0, 9)) { + protocol->bit_format = 0; + return true; + } } else if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111001011010) { - protocol->bit_format = 26; - return true; + if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 90, BitLibParityAlways0, 9)) { + protocol->bit_format = 26; + return true; + } } else if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111001100000) { - protocol->bit_format = 32; - return true; - } else { - return false; + if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 90, BitLibParityAlways0, 9)) { + protocol->bit_format = 32; + return true; + } } + return false; } static void protocol_securakey_decode(ProtocolSecurakey* protocol) { diff --git a/lib/lfrfid/protocols/protocol_viking.c b/lib/lfrfid/protocols/protocol_viking.c index 78499a415..7e9009fde 100644 --- a/lib/lfrfid/protocols/protocol_viking.c +++ b/lib/lfrfid/protocols/protocol_viking.c @@ -171,6 +171,19 @@ bool protocol_viking_write_data(ProtocolViking* protocol, void* data) { request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); request->t5577.blocks_to_write = 3; result = true; + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(32) | (6 << EM4x05_MAXBLOCK_SHIFT)); + uint32_t encoded_data_reversed[2] = {0}; + for(uint8_t i = 0; i < 64; i++) { + encoded_data_reversed[i / 32] = + (encoded_data_reversed[i / 32] << 1) | + (bit_lib_get_bit(protocol->encoded_data, (63 - i)) & 1); + } + request->em4305.word[5] = encoded_data_reversed[1]; + request->em4305.word[6] = encoded_data_reversed[0]; + request->em4305.mask = 0x70; + result = true; } return result; } diff --git a/lib/lfrfid/tools/em4305.c b/lib/lfrfid/tools/em4305.c new file mode 100644 index 000000000..3b072d38d --- /dev/null +++ b/lib/lfrfid/tools/em4305.c @@ -0,0 +1,152 @@ +#include "em4305.h" +#include +#include + +#define TAG "EM4305" + +#define EM4305_TIMING_1 (32) +#define EM4305_TIMING_0_OFF (23) +#define EM4305_TIMING_0_ON (18) + +#define EM4305_FIELD_STOP_OFF_CYCLES (55) +#define EM4305_FIELD_STOP_ON_CYCLES (18) + +#define EM4305_TIMING_POWER_CHECK (1480) +#define EM4305_TIMING_EEPROM_WRITE (9340) + +static bool em4305_line_parity(uint8_t data) { + uint8_t parity = 0; + for(uint8_t i = 0; i < 8; i++) { + parity ^= (data >> i) & 1; + } + return parity; +} + +static uint64_t em4305_prepare_data(uint32_t data) { + uint8_t i, j; + uint64_t data_with_parity = 0; + + // 4 lines of 8 bits of data + // line even parity at bits 8 17 26 35 + // column even parity at bits 36-43 + // bit 44 is always 0 + // final table is 5 lines of 9 bits + + // line parity + for(i = 0; i < 4; i++) { + for(j = 0; j < 8; j++) { + data_with_parity = (data_with_parity << 1) | ((data >> (i * 8 + j)) & 1); + } + data_with_parity = (data_with_parity << 1) | (uint64_t)em4305_line_parity(data >> (i * 8)); + } + + // column parity + for(i = 0; i < 8; i++) { + uint8_t column_parity = 0; + for(j = 0; j < 4; j++) { + column_parity ^= (data >> (j * 8 + i)) & 1; + } + data_with_parity = (data_with_parity << 1) | column_parity; + } + + // bit 44 + data_with_parity = (data_with_parity << 1) | 0; + + return data_with_parity; +} + +static void em4305_start(void) { + furi_hal_rfid_tim_read_start(125000, 0.5); + + // do not ground the antenna + furi_hal_rfid_pin_pull_release(); +} + +static void em4305_stop(void) { + furi_hal_rfid_tim_read_stop(); + furi_hal_rfid_pins_reset(); +} + +static void em4305_write_bit(bool value) { + if(value) { + furi_delay_us(EM4305_TIMING_1 * 8); + } else { + furi_hal_rfid_tim_read_pause(); + furi_delay_us(EM4305_TIMING_0_OFF * 8); + furi_hal_rfid_tim_read_continue(); + furi_delay_us(EM4305_TIMING_0_ON * 8); + } +} + +static void em4305_write_opcode(uint8_t value) { + // 3 bit opcode + for(uint8_t i = 0; i < 3; i++) { + em4305_write_bit((value >> i) & 1); + } + + // parity + bool parity = 0; + for(uint8_t i = 0; i < 3; i++) { + parity ^= (value >> i) & 1; + } + em4305_write_bit(parity); +} + +static void em4305_field_stop() { + furi_hal_rfid_tim_read_pause(); + furi_delay_us(EM4305_FIELD_STOP_OFF_CYCLES * 8); + furi_hal_rfid_tim_read_continue(); + furi_delay_us(EM4305_FIELD_STOP_ON_CYCLES * 8); +} + +static void em4305_write_word(uint8_t address, uint32_t data) { + // parity + uint64_t data_with_parity = em4305_prepare_data(data); + + // power up the tag + furi_delay_us(8000); + + // field stop + em4305_field_stop(); + + // start bit + em4305_write_bit(0); + + // opcode + em4305_write_opcode(EM4x05_OPCODE_WRITE); + + // address + bool address_parity = 0; + for(uint8_t i = 0; i < 4; i++) { + em4305_write_bit((address >> (i)) & 1); + address_parity ^= (address >> (i)) & 1; + } + em4305_write_bit(0); + em4305_write_bit(0); + em4305_write_bit(address_parity); + + // data + for(uint8_t i = 0; i < 45; i++) { + em4305_write_bit((data_with_parity >> (44 - i)) & 1); + } + + // wait for power check and eeprom write + furi_delay_us(EM4305_TIMING_POWER_CHECK); + furi_delay_us(EM4305_TIMING_EEPROM_WRITE); +} + +void em4305_write(LFRFIDEM4305* data) { + furi_check(data); + + em4305_start(); + FURI_CRITICAL_ENTER(); + + for(uint8_t i = 0; i < EM4x05_WORD_COUNT; i++) { + if(data->mask & (1 << i)) { + em4305_write_word(i, data->word[i]); + } + } + + FURI_CRITICAL_EXIT(); + em4305_stop(); +} diff --git a/lib/lfrfid/tools/em4305.h b/lib/lfrfid/tools/em4305.h new file mode 100644 index 000000000..0cec00254 --- /dev/null +++ b/lib/lfrfid/tools/em4305.h @@ -0,0 +1,61 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// EM4305/4205 chip config definitions, thanks proxmark3! +#define EM4x05_GET_BITRATE(x) ((((x) & 0x3F) * 2) + 2) +// Note: only data rates 8, 16, 32, 40(*) and 64 are supported. (*) only with EM4305 330pF +#define EM4x05_SET_BITRATE(x) (((x) - 2) / 2) +#define EM4x05_MODULATION_NRZ (0x00000000) +#define EM4x05_MODULATION_MANCHESTER (0x00000040) +#define EM4x05_MODULATION_BIPHASE (0x00000080) +#define EM4x05_MODULATION_MILLER (0x000000C0) // not supported by all 4x05/4x69 chips +#define EM4x05_MODULATION_PSK1 (0x00000100) // not supported by all 4x05/4x69 chips +#define EM4x05_MODULATION_PSK2 (0x00000140) // not supported by all 4x05/4x69 chips +#define EM4x05_MODULATION_PSK3 (0x00000180) // not supported by all 4x05/4x69 chips +#define EM4x05_MODULATION_FSK1 (0x00000200) // not supported by all 4x05/4x69 chips +#define EM4x05_MODULATION_FSK2 (0x00000240) // not supported by all 4x05/4x69 chips +#define EM4x05_PSK_RF_2 (0) +#define EM4x05_PSK_RF_4 (0x00000400) +#define EM4x05_PSK_RF_8 (0x00000800) +#define EM4x05_MAXBLOCK_SHIFT (14) +#define EM4x05_FIRST_USER_BLOCK (5) +#define EM4x05_SET_NUM_BLOCKS(x) \ + (((x) + 4) << 14) // number of blocks sent during default read mode +#define EM4x05_GET_NUM_BLOCKS(x) ((((x) >> 14) & 0xF) - 4) +#define EM4x05_READ_LOGIN_REQ (1 << 18) +#define EM4x05_READ_HK_LOGIN_REQ (1 << 19) +#define EM4x05_WRITE_LOGIN_REQ (1 << 20) +#define EM4x05_WRITE_HK_LOGIN_REQ (1 << 21) +#define EM4x05_READ_AFTER_WRITE (1 << 22) +#define EM4x05_DISABLE_ALLOWED (1 << 23) +#define EM4x05_READER_TALK_FIRST (1 << 24) +#define EM4x05_INVERT (1 << 25) +#define EM4x05_PIGEON (1 << 26) + +#define EM4x05_WORD_COUNT (16) + +#define EM4x05_OPCODE_LOGIN (0b001) +#define EM4x05_OPCODE_WRITE (0b010) +#define EM4x05_OPCODE_READ (0b100) +#define EM4x05_OPCODE_PROTECT (0b110) +#define EM4x05_OPCODE_DISABLE (0b101) + +typedef struct { + uint32_t word[EM4x05_WORD_COUNT]; /**< Word data to write */ + uint16_t mask; /**< Word mask */ +} LFRFIDEM4305; + +/** Write EM4305 tag data to tag + * + * @param data The data to write (mask is taken from that data) + */ +void em4305_write(LFRFIDEM4305* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/mbedtls b/lib/mbedtls index edb8fec98..107ea89da 160000 --- a/lib/mbedtls +++ b/lib/mbedtls @@ -1 +1 @@ -Subproject commit edb8fec9882084344a314368ac7fd957a187519c +Subproject commit 107ea89daaefb9867ea9121002fbbdf926780e98 diff --git a/lib/mbedtls.scons b/lib/mbedtls.scons index 77add7696..759f263af 100644 --- a/lib/mbedtls.scons +++ b/lib/mbedtls.scons @@ -6,6 +6,7 @@ env.Append( "#/lib/mbedtls/include", ], SDK_HEADERS=[ + File("mbedtls/include/mbedtls/aes.h"), File("mbedtls/include/mbedtls/des.h"), File("mbedtls/include/mbedtls/sha1.h"), File("mbedtls/include/mbedtls/sha256.h"), @@ -37,6 +38,7 @@ libenv.AppendUnique( # sources = libenv.GlobRecursive("*.c*", "mbedtls/library") # Otherwise, we can just use the files we need: sources = [ + File("mbedtls/library/aes.c"), File("mbedtls/library/bignum.c"), File("mbedtls/library/bignum_core.c"), File("mbedtls/library/ecdsa.c"), diff --git a/lib/mjs/mjs_exec.c b/lib/mjs/mjs_exec.c index 8fdb2d7e5..273c38a34 100644 --- a/lib/mjs/mjs_exec.c +++ b/lib/mjs/mjs_exec.c @@ -584,7 +584,8 @@ static void mjs_apply_(struct mjs* mjs) { if(mjs_is_array(v)) { nargs = mjs_array_length(mjs, v); args = calloc(nargs, sizeof(args[0])); - for(i = 0; i < nargs; i++) args[i] = mjs_array_get(mjs, v, i); + for(i = 0; i < nargs; i++) + args[i] = mjs_array_get(mjs, v, i); } mjs_apply(mjs, &res, func, mjs_arg(mjs, 0), nargs, args); free(args); diff --git a/lib/mjs/mjs_parser.c b/lib/mjs/mjs_parser.c index 212804a86..98f60459d 100644 --- a/lib/mjs/mjs_parser.c +++ b/lib/mjs/mjs_parser.c @@ -47,7 +47,7 @@ static int ptest(struct pstate* p) { return tok; } -static int s_unary_ops[] = { +static const int s_unary_ops[] = { TOK_NOT, TOK_TILDA, TOK_PLUS_PLUS, @@ -56,10 +56,10 @@ static int s_unary_ops[] = { TOK_MINUS, TOK_PLUS, TOK_EOF}; -static int s_comparison_ops[] = {TOK_LT, TOK_LE, TOK_GT, TOK_GE, TOK_EOF}; -static int s_postfix_ops[] = {TOK_PLUS_PLUS, TOK_MINUS_MINUS, TOK_EOF}; -static int s_equality_ops[] = {TOK_EQ, TOK_NE, TOK_EQ_EQ, TOK_NE_NE, TOK_EOF}; -static int s_assign_ops[] = { +static const int s_comparison_ops[] = {TOK_LT, TOK_LE, TOK_GT, TOK_GE, TOK_EOF}; +static const int s_postfix_ops[] = {TOK_PLUS_PLUS, TOK_MINUS_MINUS, TOK_EOF}; +static const int s_equality_ops[] = {TOK_EQ, TOK_NE, TOK_EQ_EQ, TOK_NE_NE, TOK_EOF}; +static const int s_assign_ops[] = { TOK_ASSIGN, TOK_PLUS_ASSIGN, TOK_MINUS_ASSIGN, @@ -74,7 +74,7 @@ static int s_assign_ops[] = { TOK_OR_ASSIGN, TOK_EOF}; -static int findtok(int* toks, int tok) { +static int findtok(int const* toks, int tok) { int i = 0; while(tok != toks[i] && toks[i] != TOK_EOF) i++; diff --git a/lib/mjs/mjs_primitive.c b/lib/mjs/mjs_primitive.c index e73ae892d..20d2a8c66 100644 --- a/lib/mjs/mjs_primitive.c +++ b/lib/mjs/mjs_primitive.c @@ -9,6 +9,8 @@ #include "mjs_string_public.h" #include "mjs_util.h" +#include + mjs_val_t mjs_mk_null(void) { return MJS_NULL; } @@ -94,7 +96,7 @@ int mjs_is_boolean(mjs_val_t v) { } #define MJS_IS_POINTER_LEGIT(n) \ - (((n)&MJS_TAG_MASK) == 0 || ((n)&MJS_TAG_MASK) == (~0 & MJS_TAG_MASK)) + (((n) & MJS_TAG_MASK) == 0 || ((n) & MJS_TAG_MASK) == (~0 & MJS_TAG_MASK)) MJS_PRIVATE mjs_val_t mjs_pointer_to_value(struct mjs* mjs, void* p) { uint64_t n = ((uint64_t)(uintptr_t)p); @@ -165,13 +167,13 @@ MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs) { mjs_val_t ret = MJS_UNDEFINED; mjs_val_t base_v = MJS_UNDEFINED; int32_t base = 10; - int32_t num; + double num; /* get number from `this` */ if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_NUMBER, NULL)) { goto clean; } - num = mjs_get_int32(mjs, mjs->vals.this_obj); + num = mjs_get_double(mjs, mjs->vals.this_obj); if(mjs_nargs(mjs) >= 1) { /* get base from arg 0 */ @@ -181,9 +183,20 @@ MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs) { base = mjs_get_int(mjs, base_v); } - char tmp_str[] = "-2147483648"; - itoa(num, tmp_str, base); - ret = mjs_mk_string(mjs, tmp_str, ~0, true); + if(base != 10 || floor(num) == num) { + char tmp_str[] = "-2147483648"; + itoa((int32_t)num, tmp_str, base); + ret = mjs_mk_string(mjs, tmp_str, ~0, true); + } else { + char tmp_str[] = "2.22514337200000e-308"; + snprintf(tmp_str, sizeof(tmp_str), "%.*g", DBL_DIG, num); + size_t len = strlen(tmp_str); + while(len && tmp_str[len - 1] == '0') { + len--; + } + tmp_str[len] = '\0'; + ret = mjs_mk_string(mjs, tmp_str, ~0, true); + } clean: mjs_return(mjs, ret); diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c index 7914c1f7f..2143f0f5f 100644 --- a/lib/nfc/helpers/nfc_data_generator.c +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -392,37 +392,15 @@ static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfCl mf_classic_set_block_read(mfc_data, 0, &mfc_data->block[0]); + // Set every block to 0x00 uint16_t block_num = mf_classic_get_total_block_num(type); - if(type == MfClassicType4k) { - // Set every block to 0x00 - for(uint16_t i = 1; i < block_num; i++) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc_data, i); - } else { - memset(&mfc_data->block[i].data, 0x00, 16); - } - mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); - } - } else if(type == MfClassicType1k) { - // Set every block to 0x00 - for(uint16_t i = 1; i < block_num; i++) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc_data, i); - } else { - memset(&mfc_data->block[i].data, 0x00, 16); - } - mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); - } - } else if(type == MfClassicTypeMini) { - // Set every block to 0x00 - for(uint16_t i = 1; i < block_num; i++) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc_data, i); - } else { - memset(&mfc_data->block[i].data, 0x00, 16); - } - mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); + for(uint16_t i = 1; i < block_num; i++) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc_data, i); + } else { + memset(&mfc_data->block[i].data, 0x00, MF_CLASSIC_BLOCK_SIZE); } + mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); } nfc_generate_mf_classic_block_0( diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 720daf238..cf00cbc09 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -118,7 +118,8 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { blocks[1] = FELICA_BLOCK_INDEX_WCNT; blocks[2] = FELICA_BLOCK_INDEX_MAC_A; FelicaPollerReadCommandResponse* rx_resp; - error = felica_poller_read_blocks(instance, sizeof(blocks), blocks, &rx_resp); + error = felica_poller_read_blocks( + instance, sizeof(blocks), blocks, FELICA_SERVICE_RO_ACCESS, &rx_resp); if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; if(felica_check_mac( @@ -174,7 +175,7 @@ NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) { if(error != FelicaErrorNone || tx_resp->SF1 != 0 || tx_resp->SF2 != 0) break; FelicaPollerReadCommandResponse* rx_resp; - error = felica_poller_read_blocks(instance, 1, blocks, &rx_resp); + error = felica_poller_read_blocks(instance, 1, blocks, FELICA_SERVICE_RO_ACCESS, &rx_resp); if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; instance->data->data.fs.state.SF1 = 0; @@ -203,7 +204,8 @@ NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) { } FelicaPollerReadCommandResponse* response; - FelicaError error = felica_poller_read_blocks(instance, block_count, block_list, &response); + FelicaError error = felica_poller_read_blocks( + instance, block_count, block_list, FELICA_SERVICE_RO_ACCESS, &response); if(error == FelicaErrorNone) { block_count = (response->SF1 == 0) ? response->block_count : block_count; uint8_t* data_ptr = diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index b386f4b4b..541442df2 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -56,6 +56,23 @@ typedef struct { */ FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data); +/** + * @brief Performs felica read operation for blocks provided as parameters + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_count Amount of blocks involved in reading procedure + * @param[in] block_numbers Array with block indexes according to felica docs + * @param[in] service_code Service code for the read operation + * @param[out] response_ptr Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure. +*/ +FelicaError felica_poller_read_blocks( + FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + uint16_t service_code, + FelicaPollerReadCommandResponse** const response_ptr); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index 8ec4b2889..49112debd 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -134,6 +134,7 @@ FelicaError felica_poller_read_blocks( FelicaPoller* instance, const uint8_t block_count, const uint8_t* const block_numbers, + uint16_t service_code, FelicaPollerReadCommandResponse** const response_ptr) { furi_assert(instance); furi_assert(block_count <= 4); @@ -143,7 +144,7 @@ FelicaError felica_poller_read_blocks( felica_poller_prepare_tx_buffer( instance, FELICA_CMD_READ_WITHOUT_ENCRYPTION, - FELICA_SERVICE_RO_ACCESS, + service_code, block_count, block_numbers, 0, diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index df205ba67..df4990a4e 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -70,21 +70,6 @@ FelicaError felica_poller_polling( const FelicaPollerPollingCommand* cmd, FelicaPollerPollingResponse* resp); -/** - * @brief Performs felica read operation for blocks provided as parameters - * - * @param[in, out] instance pointer to the instance to be used in the transaction. - * @param[in] block_count Amount of blocks involved in reading procedure - * @param[in] block_numbers Array with block indexes according to felica docs - * @param[out] response_ptr Pointer to the response structure - * @return FelicaErrorNone on success, an error code on failure. -*/ -FelicaError felica_poller_read_blocks( - FelicaPoller* instance, - const uint8_t block_count, - const uint8_t* const block_numbers, - FelicaPollerReadCommandResponse** const response_ptr); - /** * @brief Performs felica write operation with data provided as parameters * diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3.c b/lib/nfc/protocols/iso15693_3/iso15693_3.c index 04a9b3412..576177b5f 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3.c @@ -173,33 +173,35 @@ bool iso15693_3_load(Iso15693_3Data* data, FlipperFormat* ff, uint32_t version) if(flipper_format_key_exist(ff, ISO15693_3_BLOCK_COUNT_KEY) && flipper_format_key_exist(ff, ISO15693_3_BLOCK_SIZE_KEY)) { + data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_MEMORY; + uint32_t block_count; if(!flipper_format_read_uint32(ff, ISO15693_3_BLOCK_COUNT_KEY, &block_count, 1)) break; - data->system_info.block_count = block_count; - data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_MEMORY; if(!flipper_format_read_hex( ff, ISO15693_3_BLOCK_SIZE_KEY, &(data->system_info.block_size), 1)) break; - simple_array_init( - data->block_data, data->system_info.block_size * data->system_info.block_count); - - if(!flipper_format_read_hex( - ff, - ISO15693_3_DATA_CONTENT_KEY, - simple_array_get_data(data->block_data), - simple_array_get_count(data->block_data))) - break; - - if(flipper_format_key_exist(ff, ISO15693_3_SECURITY_STATUS_KEY)) { + if(data->system_info.block_count > 0 && data->system_info.block_size > 0) { + simple_array_init( + data->block_data, + data->system_info.block_size * data->system_info.block_count); simple_array_init(data->block_security, data->system_info.block_count); - const bool security_loaded = has_lock_bits ? - iso15693_3_load_security(data, ff) : - iso15693_3_load_security_legacy(data, ff); - if(!security_loaded) break; + if(!flipper_format_read_hex( + ff, + ISO15693_3_DATA_CONTENT_KEY, + simple_array_get_data(data->block_data), + simple_array_get_count(data->block_data))) + break; + + if(flipper_format_key_exist(ff, ISO15693_3_SECURITY_STATUS_KEY)) { + const bool security_loaded = has_lock_bits ? + iso15693_3_load_security(data, ff) : + iso15693_3_load_security_legacy(data, ff); + if(!security_loaded) break; + } } } @@ -260,22 +262,24 @@ bool iso15693_3_save(const Iso15693_3Data* data, FlipperFormat* ff) { ff, ISO15693_3_BLOCK_SIZE_KEY, &data->system_info.block_size, 1)) break; - if(!flipper_format_write_hex( - ff, - ISO15693_3_DATA_CONTENT_KEY, - simple_array_cget_data(data->block_data), - simple_array_get_count(data->block_data))) - break; + if(data->system_info.block_count > 0 && data->system_info.block_size > 0) { + if(!flipper_format_write_hex( + ff, + ISO15693_3_DATA_CONTENT_KEY, + simple_array_cget_data(data->block_data), + simple_array_get_count(data->block_data))) + break; - if(!flipper_format_write_comment_cstr( - ff, "Block Security Status: 01 = locked, 00 = not locked")) - break; - if(!flipper_format_write_hex( - ff, - ISO15693_3_SECURITY_STATUS_KEY, - simple_array_cget_data(data->block_security), - simple_array_get_count(data->block_security))) - break; + if(!flipper_format_write_comment_cstr( + ff, "Block Security Status: 01 = locked, 00 = not locked")) + break; + if(!flipper_format_write_hex( + ff, + ISO15693_3_SECURITY_STATUS_KEY, + simple_array_cget_data(data->block_security), + simple_array_get_count(data->block_security))) + break; + } } saved = true; } while(false); diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c index 6aee84a3f..bc677ce67 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -100,10 +100,12 @@ Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_ break; } - if(system_info->block_count > 0) { - // Read blocks: Optional command + if(system_info->block_count > 0 && system_info->block_size > 0) { simple_array_init( data->block_data, system_info->block_count * system_info->block_size); + simple_array_init(data->block_security, system_info->block_count); + + // Read blocks: Optional command ret = iso15693_3_poller_read_blocks( instance, simple_array_get_data(data->block_data), @@ -115,8 +117,6 @@ Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_ } // Get block security status: Optional command - simple_array_init(data->block_security, system_info->block_count); - ret = iso15693_3_poller_get_blocks_security( instance, simple_array_get_data(data->block_security), system_info->block_count); if(ret != Iso15693_3ErrorNone) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index ef571117a..1f5eea271 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -19,7 +19,7 @@ typedef struct { uint8_t cmd_start_byte; size_t cmd_len_bits; size_t command_num; - MfClassicListenerCommandHandler* handler; + const MfClassicListenerCommandHandler* handler; } MfClassicListenerCmd; static void mf_classic_listener_prepare_emulation(MfClassicListener* instance) { @@ -441,42 +441,42 @@ static MfClassicListenerCommand return command; } -static MfClassicListenerCommandHandler mf_classic_listener_halt_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_halt_handlers[] = { mf_classic_listener_halt_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_auth_key_a_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_auth_key_a_handlers[] = { mf_classic_listener_auth_key_a_handler, mf_classic_listener_auth_second_part_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_auth_key_b_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_auth_key_b_handlers[] = { mf_classic_listener_auth_key_b_handler, mf_classic_listener_auth_second_part_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_read_block_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_read_block_handlers[] = { mf_classic_listener_read_block_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_write_block_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_write_block_handlers[] = { mf_classic_listener_write_block_first_part_handler, mf_classic_listener_write_block_second_part_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_value_dec_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_value_dec_handlers[] = { mf_classic_listener_value_dec_handler, mf_classic_listener_value_data_receive_handler, mf_classic_listener_value_transfer_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_value_inc_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_value_inc_handlers[] = { mf_classic_listener_value_inc_handler, mf_classic_listener_value_data_receive_handler, mf_classic_listener_value_transfer_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_value_restore_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_value_restore_handlers[] = { mf_classic_listener_value_restore_handler, mf_classic_listener_value_data_receive_handler, mf_classic_listener_value_transfer_handler, diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index ec37c8015..b2d9b114a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1921,7 +1921,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance sizeof(MfClassicKey)) : NULL; } - if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { + if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) || + (is_last_iter_for_hard_key && (dict_attack_ctx->nested_nonce.count == 8))) { // Key verify and reuse dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackVerify; dict_attack_ctx->auth_passed = false; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.c b/lib/nfc/protocols/mf_desfire/mf_desfire.c index 4d54a2c0e..42ad4634b 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.c @@ -4,6 +4,46 @@ #define MF_DESFIRE_PROTOCOL_NAME "Mifare DESFire" +#define MF_DESFIRE_HW_MINOR_TYPE (0x00) +#define MF_DESFIRE_HW_MINOR_TYPE_MF3ICD40 (0x02) + +#define MF_DESFIRE_HW_MAJOR_TYPE_EV1 (0x01) +#define MF_DESFIRE_HW_MAJOR_TYPE_EV2 (0x12) +#define MF_DESFIRE_HW_MAJOR_TYPE_EV2_XL (0x22) +#define MF_DESFIRE_HW_MAJOR_TYPE_EV3 (0x33) +#define MF_DESFIRE_HW_MAJOR_TYPE_MF3ICD40 (0x00) + +#define MF_DESFIRE_STORAGE_SIZE_2K (0x16) +#define MF_DESFIRE_STORAGE_SIZE_4K (0x18) +#define MF_DESFIRE_STORAGE_SIZE_8K (0x1A) +#define MF_DESFIRE_STORAGE_SIZE_16K (0x1C) +#define MF_DESFIRE_STORAGE_SIZE_32K (0x1E) +#define MF_DESFIRE_STORAGE_SIZE_MF3ICD40 (0xFF) +#define MF_DESFIRE_STORAGE_SIZE_UNKNOWN (0xFF) + +#define MF_DESFIRE_TEST_TYPE_MF3ICD40(major, minor, storage) \ + (((major) == MF_DESFIRE_HW_MAJOR_TYPE_MF3ICD40) && \ + ((minor) == MF_DESFIRE_HW_MINOR_TYPE_MF3ICD40) && \ + ((storage) == MF_DESFIRE_STORAGE_SIZE_MF3ICD40)) + +static const char* mf_desfire_type_strings[] = { + [MfDesfireTypeMF3ICD40] = "(MF3ICD40)", + [MfDesfireTypeEV1] = "EV1", + [MfDesfireTypeEV2] = "EV2", + [MfDesfireTypeEV2XL] = "EV2 XL", + [MfDesfireTypeEV3] = "EV3", + [MfDesfireTypeUnknown] = "UNK", +}; + +static const char* mf_desfire_size_strings[] = { + [MfDesfireSize2k] = "2K", + [MfDesfireSize4k] = "4K", + [MfDesfireSize8k] = "8K", + [MfDesfireSize16k] = "16K", + [MfDesfireSize32k] = "32K", + [MfDesfireSizeUnknown] = "", +}; + const NfcDeviceBase nfc_device_mf_desfire = { .protocol_name = MF_DESFIRE_PROTOCOL_NAME, .alloc = (NfcDeviceAlloc)mf_desfire_alloc, @@ -26,7 +66,7 @@ MfDesfireData* mf_desfire_alloc(void) { data->master_key_versions = simple_array_alloc(&mf_desfire_key_version_array_config); data->application_ids = simple_array_alloc(&mf_desfire_app_id_array_config); data->applications = simple_array_alloc(&mf_desfire_application_array_config); - + data->device_name = furi_string_alloc(); return data; } @@ -38,6 +78,7 @@ void mf_desfire_free(MfDesfireData* data) { simple_array_free(data->application_ids); simple_array_free(data->master_key_versions); iso14443_4a_free(data->iso14443_4a_data); + furi_string_free(data->device_name); free(data); } @@ -228,10 +269,83 @@ bool mf_desfire_is_equal(const MfDesfireData* data, const MfDesfireData* other) simple_array_is_equal(data->applications, other->applications); } +static MfDesfireType mf_desfire_get_type_from_version(const MfDesfireVersion* const version) { + MfDesfireType type = MfDesfireTypeUnknown; + + switch(version->hw_major) { + case MF_DESFIRE_HW_MAJOR_TYPE_EV1: + type = MfDesfireTypeEV1; + break; + case MF_DESFIRE_HW_MAJOR_TYPE_EV2: + type = MfDesfireTypeEV2; + break; + case MF_DESFIRE_HW_MAJOR_TYPE_EV2_XL: + type = MfDesfireTypeEV2XL; + break; + case MF_DESFIRE_HW_MAJOR_TYPE_EV3: + type = MfDesfireTypeEV3; + break; + default: + if(MF_DESFIRE_TEST_TYPE_MF3ICD40(version->hw_major, version->hw_minor, version->hw_storage)) + type = MfDesfireTypeMF3ICD40; + break; + } + + return type; +} + +static MfDesfireSize mf_desfire_get_size_from_version(const MfDesfireVersion* const version) { + MfDesfireSize size = MfDesfireSizeUnknown; + + switch(version->hw_storage) { + case MF_DESFIRE_STORAGE_SIZE_2K: + size = MfDesfireSize2k; + break; + case MF_DESFIRE_STORAGE_SIZE_4K: + size = MfDesfireSize4k; + break; + case MF_DESFIRE_STORAGE_SIZE_8K: + size = MfDesfireSize8k; + break; + case MF_DESFIRE_STORAGE_SIZE_16K: + size = MfDesfireSize16k; + break; + case MF_DESFIRE_STORAGE_SIZE_32K: + size = MfDesfireSize32k; + break; + default: + if(MF_DESFIRE_TEST_TYPE_MF3ICD40(version->hw_major, version->hw_minor, version->hw_storage)) + size = MfDesfireSize4k; + break; + } + + return size; +} + const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type) { - UNUSED(data); - UNUSED(name_type); - return MF_DESFIRE_PROTOCOL_NAME; + furi_check(data); + + const MfDesfireType type = mf_desfire_get_type_from_version(&data->version); + const MfDesfireSize size = mf_desfire_get_size_from_version(&data->version); + + if(type == MfDesfireTypeUnknown) { + furi_string_printf(data->device_name, "Unknown %s", MF_DESFIRE_PROTOCOL_NAME); + } else if(name_type == NfcDeviceNameTypeFull) { + furi_string_printf( + data->device_name, + "%s %s %s", + MF_DESFIRE_PROTOCOL_NAME, + mf_desfire_type_strings[type], + mf_desfire_size_strings[size]); + } else { + furi_string_printf( + data->device_name, + "%s %s", + mf_desfire_type_strings[type], + mf_desfire_size_strings[size]); + } + + return furi_string_get_cstr(data->device_name); } const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len) { diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index dd2009276..ec60b336b 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -29,6 +29,28 @@ extern "C" { #define MF_DESFIRE_APP_ID_SIZE (3) #define MF_DESFIRE_VALUE_SIZE (4) +typedef enum { + MfDesfireTypeMF3ICD40, + MfDesfireTypeEV1, + MfDesfireTypeEV2, + MfDesfireTypeEV2XL, + MfDesfireTypeEV3, + + MfDesfireTypeUnknown, + MfDesfireTypeNum, +} MfDesfireType; + +typedef enum { + MfDesfireSize2k, + MfDesfireSize4k, + MfDesfireSize8k, + MfDesfireSize16k, + MfDesfireSize32k, + + MfDesfireSizeUnknown, + MfDesfireSizeNum, +} MfDesfireSize; + typedef struct { uint8_t hw_vendor; uint8_t hw_type; @@ -75,6 +97,7 @@ typedef enum { MfDesfireFileTypeValue = 2, MfDesfireFileTypeLinearRecord = 3, MfDesfireFileTypeCyclicRecord = 4, + MfDesfireFileTypeTransactionMac = 5, } MfDesfireFileType; typedef enum { @@ -106,6 +129,11 @@ typedef struct { uint32_t max; uint32_t cur; } record; + struct { + uint8_t key_option; + uint8_t key_version; + uint32_t counter_limit; + } transaction_mac; }; } MfDesfireFileSettings; @@ -131,6 +159,7 @@ typedef enum { MfDesfireErrorProtocol, MfDesfireErrorTimeout, MfDesfireErrorAuthentication, + MfDesfireErrorCommandNotSupported, } MfDesfireError; typedef struct { @@ -141,6 +170,7 @@ typedef struct { SimpleArray* master_key_versions; SimpleArray* application_ids; SimpleArray* applications; + FuriString* device_name; } MfDesfireData; extern const NfcDeviceBase nfc_device_mf_desfire; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index d83a91ad1..eba9c4312 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -1,5 +1,7 @@ #include "mf_desfire_i.h" +#include + #define TAG "MfDesfire" #define BITS_IN_BYTE (8U) @@ -47,6 +49,10 @@ #define MF_DESFIRE_FFF_FILE_MAX_KEY "Max" #define MF_DESFIRE_FFF_FILE_CUR_KEY "Cur" +#define MF_DESFIRE_FFF_FILE_KEY_OPTION_KEY "Key Option" +#define MF_DESFIRE_FFF_FILE_KEY_VERSION_KEY "Key Version" +#define MF_DESFIRE_FFF_FILE_COUNTER_LIMIT_KEY "Counter Limit" + bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) { const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireVersion); @@ -168,12 +174,19 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer uint32_t cur : 3 * BITS_IN_BYTE; } MfDesfireFileSettingsRecord; + typedef struct FURI_PACKED { + uint8_t key_option; + uint8_t key_version; + uint8_t counter_limit[]; + } MfDesfireFileSettingsTransactionMac; + typedef struct FURI_PACKED { MfDesfireFileSettingsHeader header; union { MfDesfireFileSettingsData data; MfDesfireFileSettingsValue value; MfDesfireFileSettingsRecord record; + MfDesfireFileSettingsTransactionMac transaction_mac; }; } MfDesfireFileSettingsLayout; @@ -182,7 +195,7 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer const size_t data_size = bit_buffer_get_size_bytes(buf); const uint8_t* data_ptr = bit_buffer_get_data(buf); const size_t min_data_size = - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData); + sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsTransactionMac); if(data_size < min_data_size) { FURI_LOG_E( @@ -202,17 +215,11 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer if(file_settings_temp.type == MfDesfireFileTypeStandard || file_settings_temp.type == MfDesfireFileTypeBackup) { - memcpy( - &layout.data, - &data_ptr[sizeof(MfDesfireFileSettingsHeader)], - sizeof(MfDesfireFileSettingsData)); + memcpy(&layout.data, &data_ptr[bytes_processed], sizeof(MfDesfireFileSettingsData)); file_settings_temp.data.size = layout.data.size; bytes_processed += sizeof(MfDesfireFileSettingsData); } else if(file_settings_temp.type == MfDesfireFileTypeValue) { - memcpy( - &layout.value, - &data_ptr[sizeof(MfDesfireFileSettingsHeader)], - sizeof(MfDesfireFileSettingsValue)); + memcpy(&layout.value, &data_ptr[bytes_processed], sizeof(MfDesfireFileSettingsValue)); file_settings_temp.value.lo_limit = layout.value.lo_limit; file_settings_temp.value.hi_limit = layout.value.hi_limit; file_settings_temp.value.limited_credit_value = layout.value.limited_credit_value; @@ -222,13 +229,34 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer file_settings_temp.type == MfDesfireFileTypeLinearRecord || file_settings_temp.type == MfDesfireFileTypeCyclicRecord) { memcpy( - &layout.record, - &data_ptr[sizeof(MfDesfireFileSettingsHeader)], - sizeof(MfDesfireFileSettingsRecord)); + &layout.record, &data_ptr[bytes_processed], sizeof(MfDesfireFileSettingsRecord)); file_settings_temp.record.size = layout.record.size; file_settings_temp.record.max = layout.record.max; file_settings_temp.record.cur = layout.record.cur; bytes_processed += sizeof(MfDesfireFileSettingsRecord); + } else if(file_settings_temp.type == MfDesfireFileTypeTransactionMac) { + const bool has_counter_limit = (layout.header.comm & 0x20) != 0; + memcpy( + &layout.transaction_mac, + &data_ptr[bytes_processed], + sizeof(MfDesfireFileSettingsTransactionMac)); + file_settings_temp.transaction_mac.key_option = layout.transaction_mac.key_option; + file_settings_temp.transaction_mac.key_version = layout.transaction_mac.key_version; + if(!has_counter_limit) { + file_settings_temp.transaction_mac.counter_limit = 0; + } else { + // AES (4b) or LRP (2b) + const size_t counter_limit_size = (layout.transaction_mac.key_option & 0x02) ? 4 : + 2; + memcpy( + &layout.transaction_mac, + &data_ptr[bytes_processed], + sizeof(MfDesfireFileSettingsTransactionMac) + counter_limit_size); + file_settings_temp.transaction_mac.counter_limit = bit_lib_bytes_to_num_be( + layout.transaction_mac.counter_limit, counter_limit_size); + bytes_processed += counter_limit_size; + } + bytes_processed += sizeof(MfDesfireFileSettingsTransactionMac); } else { FURI_LOG_W(TAG, "Unknown file type: %02x", file_settings_temp.type); break; @@ -468,6 +496,21 @@ bool mf_desfire_file_settings_load( furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY); if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1)) break; + } else if(data->type == MfDesfireFileTypeTransactionMac) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_OPTION_KEY); + if(!flipper_format_read_hex( + ff, furi_string_get_cstr(key), &data->transaction_mac.key_option, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_VERSION_KEY); + if(!flipper_format_read_hex( + ff, furi_string_get_cstr(key), &data->transaction_mac.key_version, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COUNTER_LIMIT_KEY); + if(!flipper_format_read_uint32( + ff, furi_string_get_cstr(key), &data->transaction_mac.counter_limit, 1)) + break; } success = true; @@ -716,6 +759,21 @@ bool mf_desfire_file_settings_save( furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY); if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1)) break; + } else if(data->type == MfDesfireFileTypeTransactionMac) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_OPTION_KEY); + if(!flipper_format_write_hex( + ff, furi_string_get_cstr(key), &data->transaction_mac.key_option, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_VERSION_KEY); + if(!flipper_format_write_hex( + ff, furi_string_get_cstr(key), &data->transaction_mac.key_version, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COUNTER_LIMIT_KEY); + if(!flipper_format_write_uint32( + ff, furi_string_get_cstr(key), &data->transaction_mac.counter_limit, 1)) + break; } success = true; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index bd8ecfaee..45e5a27f9 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -82,9 +82,12 @@ static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* in FURI_LOG_D(TAG, "Read free memory success"); instance->state = MfDesfirePollerStateReadMasterKeySettings; } else if(instance->error == MfDesfireErrorNotPresent) { - FURI_LOG_D(TAG, "Read free memoty is unsupported"); + FURI_LOG_D(TAG, "Read free memory is not present"); instance->state = MfDesfirePollerStateReadMasterKeySettings; command = NfcCommandReset; + } else if(instance->error == MfDesfireErrorCommandNotSupported) { + FURI_LOG_D(TAG, "Read free memory is unsupported"); + instance->state = MfDesfirePollerStateReadMasterKeySettings; } else { FURI_LOG_E(TAG, "Failed to read free memory"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 1dd6c50e1..8b57fcc4c 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -25,6 +25,8 @@ MfDesfireError mf_desfire_process_status_code(uint8_t status_code) { return MfDesfireErrorNone; case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR: return MfDesfireErrorAuthentication; + case MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE: + return MfDesfireErrorCommandNotSupported; default: return MfDesfireErrorProtocol; } @@ -466,7 +468,7 @@ MfDesfireError mf_desfire_poller_read_file_data_multi( file_type == MfDesfireFileTypeLinearRecord || file_type == MfDesfireFileTypeCyclicRecord) { error = mf_desfire_poller_read_file_records( - instance, file_id, 0, file_settings_cur->data.size, file_data); + instance, file_id, 0, file_settings_cur->record.size, file_data); } } diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h index caf25ceee..191deeda6 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h @@ -38,6 +38,7 @@ extern "C" { #define MF_ULTRALIGHT_TEARING_FLAG_NUM (3) #define MF_ULTRALIGHT_AUTH_PASSWORD_SIZE (4) #define MF_ULTRALIGHT_AUTH_PACK_SIZE (2) +#define MF_ULTRALIGHT_DEFAULT_PASSWORD (0xffffffffUL) #define MF_ULTRALIGHT_C_AUTH_RESPONSE_SIZE (9) #define MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE (16) @@ -47,6 +48,11 @@ extern "C" { #define MF_ULTRALIGHT_C_AUTH_RND_A_BLOCK_OFFSET (0) #define MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET (8) #define MF_ULTRALIGHT_C_ENCRYPTED_PACK_SIZE (MF_ULTRALIGHT_C_AUTH_DATA_SIZE + 1) +#define MF_ULTRALIGHT_C_DEFAULT_KEY \ + (uint8_t[]) { \ + 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42, 0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0x59, \ + 0x46 \ + } typedef enum { MfUltralightErrorNone, diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index b35c49aea..6880a0c43 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -11,8 +11,6 @@ extern "C" { #define MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC (60000) #define MF_ULTRALIGHT_MAX_BUFF_SIZE (64) -#define MF_ULTRALIGHT_DEFAULT_PASSWORD (0xffffffffUL) - #define MF_ULTRALIGHT_IS_NTAG_I2C(type) \ (((type) == MfUltralightTypeNTAGI2C1K) || ((type) == MfUltralightTypeNTAGI2C2K) || \ ((type) == MfUltralightTypeNTAGI2CPlus1K) || ((type) == MfUltralightTypeNTAGI2CPlus2K)) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c index 9958dc50d..252c46399 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c @@ -23,6 +23,7 @@ typedef struct { FuriThreadId thread_id; MfUltralightError error; MfUltralightPollerContextData data; + const MfUltralightPollerAuthContext* auth_context; } MfUltralightPollerContext; typedef MfUltralightError (*MfUltralightPollerCmdHandler)( @@ -250,12 +251,17 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void poller_context->error = mfu_event->data->error; command = NfcCommandStop; } else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) { - mfu_event->data->auth_context.skip_auth = true; - if(mf_ultralight_support_feature( - mfu_poller->feature_set, MfUltralightFeatureSupportAuthenticate)) { - mfu_event->data->auth_context.skip_auth = false; - memset( - mfu_poller->auth_context.tdes_key.data, 0x00, MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + if(poller_context->auth_context != NULL) { + mfu_event->data->auth_context = *poller_context->auth_context; + } else { + mfu_event->data->auth_context.skip_auth = true; + if(mfu_poller->data->type == MfUltralightTypeMfulC) { + mfu_event->data->auth_context.skip_auth = false; + memset( + mfu_poller->auth_context.tdes_key.data, + 0x00, + MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + } } } @@ -266,13 +272,17 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void return command; } -MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data) { +MfUltralightError mf_ultralight_poller_sync_read_card( + Nfc* nfc, + MfUltralightData* data, + const MfUltralightPollerAuthContext* auth_context) { furi_check(nfc); furi_check(data); MfUltralightPollerContext poller_context = {}; poller_context.thread_id = furi_thread_get_current_id(); poller_context.data.data = mf_ultralight_alloc(); + poller_context.auth_context = auth_context; NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfUltralight); nfc_poller_start(poller, mf_ultralight_poller_read_callback, &poller_context); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h index ac585aad7..3f63203a7 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h @@ -1,6 +1,7 @@ #pragma once #include "mf_ultralight.h" +#include "mf_ultralight_poller.h" #include #ifdef __cplusplus @@ -27,7 +28,10 @@ MfUltralightError mf_ultralight_poller_sync_read_tearing_flag( uint8_t flag_num, MfUltralightTearingFlag* data); -MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data); +MfUltralightError mf_ultralight_poller_sync_read_card( + Nfc* nfc, + MfUltralightData* data, + const MfUltralightPollerAuthContext* auth_context); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c index 36a58a243..2b9714613 100644 --- a/lib/nfc/protocols/nfc_device_defs.c +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -32,7 +32,7 @@ * When implementing a new protocol, add its implementation * here under its own index defined in nfc_protocol.h. */ -const NfcDeviceBase* nfc_devices[NfcProtocolNum] = { +const NfcDeviceBase* const nfc_devices[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_device_iso14443_3a, [NfcProtocolIso14443_3b] = &nfc_device_iso14443_3b, [NfcProtocolIso14443_4a] = &nfc_device_iso14443_4a, diff --git a/lib/nfc/protocols/nfc_device_defs.h b/lib/nfc/protocols/nfc_device_defs.h index f5ba2563f..e5d2707fd 100644 --- a/lib/nfc/protocols/nfc_device_defs.h +++ b/lib/nfc/protocols/nfc_device_defs.h @@ -6,7 +6,7 @@ extern "C" { #endif -extern const NfcDeviceBase* nfc_devices[]; +extern const NfcDeviceBase* const nfc_devices[]; #ifdef __cplusplus } diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c index ecfe98c10..5ad73f6fe 100644 --- a/lib/nfc/protocols/nfc_listener_defs.c +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -8,7 +8,7 @@ #include #include -const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { +const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a, [NfcProtocolIso14443_3b] = NULL, [NfcProtocolIso14443_4a] = &nfc_listener_iso14443_4a, diff --git a/lib/nfc/protocols/nfc_listener_defs.h b/lib/nfc/protocols/nfc_listener_defs.h index 4d88cc098..7bd4b49b0 100644 --- a/lib/nfc/protocols/nfc_listener_defs.h +++ b/lib/nfc/protocols/nfc_listener_defs.h @@ -7,7 +7,7 @@ extern "C" { #endif -extern const NfcListenerBase* nfc_listeners_api[NfcProtocolNum]; +extern const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum]; #ifdef __cplusplus } diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c index a4717f1b1..21eef26ba 100644 --- a/lib/nfc/protocols/nfc_poller_defs.c +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -14,7 +14,7 @@ #include #include -const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = { +const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a, [NfcProtocolIso14443_3b] = &nfc_poller_iso14443_3b, [NfcProtocolIso14443_4a] = &nfc_poller_iso14443_4a, diff --git a/lib/nfc/protocols/nfc_poller_defs.h b/lib/nfc/protocols/nfc_poller_defs.h index a406a5f08..0156cc8b2 100644 --- a/lib/nfc/protocols/nfc_poller_defs.h +++ b/lib/nfc/protocols/nfc_poller_defs.h @@ -7,7 +7,7 @@ extern "C" { #endif -extern const NfcPollerBase* nfc_pollers_api[NfcProtocolNum]; +extern const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum]; #ifdef __cplusplus } diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.c b/lib/nfc/protocols/st25tb/st25tb_poller.c index fd6dc4f09..674a439cd 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller.c +++ b/lib/nfc/protocols/st25tb/st25tb_poller.c @@ -88,7 +88,7 @@ static NfcCommand st25tb_poller_request_mode_handler(St25tbPoller* instance) { St25tbPollerEventDataModeRequest* mode_request_data = &instance->st25tb_event_data.mode_request; - furi_assert(mode_request_data->mode < St25tbPollerModeNum); + furi_check(mode_request_data->mode < St25tbPollerModeNum); if(mode_request_data->mode == St25tbPollerModeRead) { instance->state = St25tbPollerStateRead; diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 71e1aca32..873f61ba9 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -279,7 +279,7 @@ static bool subghz_protocol_alutech_at_4n_gen_data( } else { instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if(instance->generic.cnt >= 0xFFFF) { + } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; } crc = subghz_protocol_alutech_at_4n_decrypt_data_crc((uint8_t)(instance->generic.cnt & 0xFF)); diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 3370f2cdc..e4d0c732c 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -317,6 +317,7 @@ SubGhzProtocolStatus res = SubGhzProtocolStatusErrorEncoderGetUpload; break; } + instance->encoder.is_running = true; res = SubGhzProtocolStatusOk; diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 645f5eee1..bbe3e487f 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -14,12 +14,13 @@ #define TAG "SubGhzProtocolCame" -#define CAME_12_COUNT_BIT 12 -#define CAME_24_COUNT_BIT 24 -#define PRASTEL_COUNT_BIT 25 -#define PRASTEL_NAME "Prastel" -#define AIRFORCE_COUNT_BIT 18 -#define AIRFORCE_NAME "Airforce" +#define CAME_12_COUNT_BIT 12 +#define CAME_24_COUNT_BIT 24 +#define PRASTEL_25_COUNT_BIT 25 +#define PRASTEL_42_COUNT_BIT 42 +#define PRASTEL_NAME "Prastel" +#define AIRFORCE_COUNT_BIT 18 +#define AIRFORCE_NAME "Airforce" static const SubGhzBlockConst subghz_protocol_came_const = { .te_short = 320, @@ -123,6 +124,7 @@ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* i switch(instance->generic.data_count_bit) { case CAME_24_COUNT_BIT: + case PRASTEL_42_COUNT_BIT: // CAME 24 Bit = 24320 us header_te = 76; break; @@ -131,7 +133,7 @@ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* i // CAME 12 Bit Original only! and Airforce protocol = 15040 us header_te = 47; break; - case PRASTEL_COUNT_BIT: + case PRASTEL_25_COUNT_BIT: // PRASTEL = 11520 us header_te = 36; break; @@ -174,7 +176,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - if(instance->generic.data_count_bit > PRASTEL_COUNT_BIT) { + if(instance->generic.data_count_bit > PRASTEL_42_COUNT_BIT) { FURI_LOG_E(TAG, "Wrong number of bits in key"); ret = SubGhzProtocolStatusErrorValueBitCount; break; @@ -268,7 +270,8 @@ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t durat if((instance->decoder.decode_count_bit == subghz_protocol_came_const.min_count_bit_for_found) || (instance->decoder.decode_count_bit == AIRFORCE_COUNT_BIT) || - (instance->decoder.decode_count_bit == PRASTEL_COUNT_BIT) || + (instance->decoder.decode_count_bit == PRASTEL_25_COUNT_BIT) || + (instance->decoder.decode_count_bit == PRASTEL_42_COUNT_BIT) || (instance->decoder.decode_count_bit == CAME_24_COUNT_BIT)) { instance->generic.serial = 0x0; instance->generic.btn = 0x0; @@ -337,7 +340,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - if(instance->generic.data_count_bit > PRASTEL_COUNT_BIT) { + if(instance->generic.data_count_bit > PRASTEL_42_COUNT_BIT) { FURI_LOG_E(TAG, "Wrong number of bits in key"); ret = SubGhzProtocolStatusErrorValueBitCount; break; @@ -350,23 +353,30 @@ void subghz_protocol_decoder_came_get_string(void* context, FuriString* output) furi_assert(context); SubGhzProtocolDecoderCame* instance = context; - uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + uint32_t code_found_lo = instance->generic.data & 0x000003ffffffffff; uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( instance->generic.data, instance->generic.data_count_bit); - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + uint32_t code_found_reverse_lo = code_found_reverse & 0x000003ffffffffff; + + const char* name = instance->generic.protocol_name; + switch(instance->generic.data_count_bit) { + case PRASTEL_25_COUNT_BIT: + case PRASTEL_42_COUNT_BIT: + name = PRASTEL_NAME; + break; + case AIRFORCE_COUNT_BIT: + name = AIRFORCE_NAME; + break; + } furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%08lX\r\n" "Yek:0x%08lX\r\n", - (instance->generic.data_count_bit == PRASTEL_COUNT_BIT ? - PRASTEL_NAME : - (instance->generic.data_count_bit == AIRFORCE_COUNT_BIT ? - AIRFORCE_NAME : - instance->generic.protocol_name)), + name, instance->generic.data_count_bit, code_found_lo, code_found_reverse_lo); diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index d21440490..5b9e6defd 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -191,7 +191,7 @@ static void subghz_protocol_encoder_came_atomo_get_upload( } else { instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if(instance->generic.cnt >= 0xFFFF) { + } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; } @@ -218,8 +218,10 @@ static void subghz_protocol_encoder_came_atomo_get_upload( instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_came_atomo_const.te_long * 60); + // Btn counter 0x0 - 0x7F + pack[0] = 0; for(uint8_t i = 0; i < 8; i++) { - pack[0] = (instance->generic.data_2 >> 56); + //pack[0] = (instance->generic.data_2 >> 56); pack[1] = (instance->generic.cnt >> 8); pack[2] = (instance->generic.cnt & 0xFF); pack[3] = ((instance->generic.data_2 >> 32) & 0xFF); @@ -228,11 +230,42 @@ static void subghz_protocol_encoder_came_atomo_get_upload( pack[6] = ((instance->generic.data_2 >> 8) & 0xFF); pack[7] = (btn << 4); - if(pack[0] == 0x7F) { + /* if(pack[0] == 0x7F) { pack[0] = 0; } else { pack[0] += (i + 1); } + */ + switch(i) { + case 0: + pack[0] = 10; // 0A + break; + case 1: + pack[0] = 30; + break; + case 2: + pack[0] = 125; // 7D + break; + case 3: + pack[0] = 126; // 7E + break; + case 4: + pack[0] = 127; // 7F + break; + case 5: + pack[0] = 0; // 00 + break; + case 6: + pack[0] = 1; // 01 + break; + case 7: + pack[0] = 3; + break; + + default: + break; + } + // 10 50 125 126 127 0 1 2 atomo_encrypt(pack); uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3]; @@ -521,7 +554,8 @@ static void subghz_protocol_came_atomo_remote_controller(SubGhzBlockGeneric* ins * 0x931dfb16c0b1 ^ 0xXXXXXXXXXXXXXXXX = 0xEF3ED0F7D9EF * 0xEF3 ED0F7D9E F => 0xEF3 - CNT, 0xED0F7D9E - SN, 0xF - key * - * ***Eng1n33r ver. (actual)*** + * ***Actual*** + * Button hold-cycle counter (8-bit, from 0 to 0x7F) should DO full cycle or half cycle keeping values like zero * 0x1FF08D9924984115 - received data * 0x00F7266DB67BEEA0 - inverted data * 0x0501FD0000A08300 - decrypted data, @@ -720,7 +754,7 @@ void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* ou "%s %db\r\n" "Key:%08lX%08lX\r\n" "Sn:0x%08lX Btn:%01X\r\n" - "Pcl_Cnt:0x%04lX\r\n" + "Cnt:0x%04lX\r\n" "Btn_Cnt:0x%02X", instance->generic.protocol_name, diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index b095977e1..489fbdbc2 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -140,9 +140,40 @@ static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* inst data_prg[0] = 0x00; if(allow_zero_seed || (instance->generic.seed != 0x0)) { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + if(!(furi_hal_subghz_get_rolling_counter_mult() >= 0xFFFF)) { + if(instance->generic.cnt < 0xFFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > + 0xFFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { + instance->generic.cnt = 0; + } + } else { + instance->generic.cnt += 1; + } + if(temp_counter_backup != 0x0) { - temp_counter_backup += furi_hal_subghz_get_rolling_counter_mult(); + if(!(furi_hal_subghz_get_rolling_counter_mult() >= 0xFFFF)) { + if(temp_counter_backup < 0xFFFFF) { + if((temp_counter_backup + furi_hal_subghz_get_rolling_counter_mult()) > + 0xFFFFF) { + temp_counter_backup = 0; + } else { + temp_counter_backup += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (temp_counter_backup >= 0xFFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { + temp_counter_backup = 0; + } + } else { + temp_counter_backup += 1; + } } } @@ -193,7 +224,9 @@ static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* inst (temp_fix_backup != 0x0) && !faac_prog_mode) { instance->generic.serial = temp_fix_backup >> 4; instance->generic.btn = temp_fix_backup & 0xF; - instance->generic.cnt = temp_counter_backup; + if(temp_counter_backup != 0x0) { + instance->generic.cnt = temp_counter_backup; + } } uint32_t fix = instance->generic.serial << 4 | instance->generic.btn; uint32_t hop = 0; @@ -207,7 +240,32 @@ static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* inst } if(allow_zero_seed || (instance->generic.seed != 0x0)) { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + if(!(furi_hal_subghz_get_rolling_counter_mult() >= 0xFFFF)) { + if(instance->generic.cnt < 0xFFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > + 0xFFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { + instance->generic.cnt = 0; + } + } else { + if(instance->generic.cnt < 0xFFFFF) { + if((instance->generic.cnt + 0xFFFFF) > 0xFFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += 0xFFFFF; + } + } else if( + (instance->generic.cnt >= 0xFFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { + instance->generic.cnt = 0; + } + } } if((instance->generic.cnt % 2) == 0) { @@ -248,7 +306,7 @@ bool subghz_protocol_faac_slh_create_data( const char* manufacture_name, SubGhzRadioPreset* preset) { furi_assert(context); - // roguemaster don't steal!!! + // OwO SubGhzProtocolEncoderFaacSLH* instance = context; instance->generic.serial = serial; instance->generic.btn = btn; diff --git a/lib/subghz/protocols/feron.c b/lib/subghz/protocols/feron.c new file mode 100644 index 000000000..1096f07a7 --- /dev/null +++ b/lib/subghz/protocols/feron.c @@ -0,0 +1,350 @@ +#include "feron.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolFeron" + +static const SubGhzBlockConst subghz_protocol_feron_const = { + .te_short = 350, + .te_long = 750, + .te_delta = 150, + .min_count_bit_for_found = 32, +}; + +struct SubGhzProtocolDecoderFeron { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderFeron { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + FeronDecoderStepReset = 0, + FeronDecoderStepSaveDuration, + FeronDecoderStepCheckDuration, +} FeronDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_feron_decoder = { + .alloc = subghz_protocol_decoder_feron_alloc, + .free = subghz_protocol_decoder_feron_free, + + .feed = subghz_protocol_decoder_feron_feed, + .reset = subghz_protocol_decoder_feron_reset, + + .get_hash_data = subghz_protocol_decoder_feron_get_hash_data, + .serialize = subghz_protocol_decoder_feron_serialize, + .deserialize = subghz_protocol_decoder_feron_deserialize, + .get_string = subghz_protocol_decoder_feron_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_feron_encoder = { + .alloc = subghz_protocol_encoder_feron_alloc, + .free = subghz_protocol_encoder_feron_free, + + .deserialize = subghz_protocol_encoder_feron_deserialize, + .stop = subghz_protocol_encoder_feron_stop, + .yield = subghz_protocol_encoder_feron_yield, +}; + +const SubGhzProtocol subghz_protocol_feron = { + .name = SUBGHZ_PROTOCOL_FERON_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_feron_decoder, + .encoder = &subghz_protocol_feron_encoder, +}; + +void* subghz_protocol_encoder_feron_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderFeron* instance = malloc(sizeof(SubGhzProtocolEncoderFeron)); + + instance->base.protocol = &subghz_protocol_feron; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_feron_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderFeron* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderFeron instance + */ +static void subghz_protocol_encoder_feron_get_upload(SubGhzProtocolEncoderFeron* instance) { + furi_assert(instance); + size_t index = 0; + + // Send key and GAP + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_feron_const.te_long); + if(i == 1) { + //Send 500/500 and gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_feron_const.te_short + 150); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_feron_const.te_short + 150); + // Gap + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long * 6); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_feron_const.te_short); + if(i == 1) { + //Send 500/500 and gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_feron_const.te_short + 150); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_feron_const.te_short + 150); + // Gap + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long * 6); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long); + } + } + } + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_feron_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->serial = instance->data >> 16; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_feron_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderFeron* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_feron_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_feron_check_remote_controller(&instance->generic); + subghz_protocol_encoder_feron_get_upload(instance); + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_feron_stop(void* context) { + SubGhzProtocolEncoderFeron* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_feron_yield(void* context) { + SubGhzProtocolEncoderFeron* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_feron_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderFeron* instance = malloc(sizeof(SubGhzProtocolDecoderFeron)); + instance->base.protocol = &subghz_protocol_feron; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_feron_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + free(instance); +} + +void subghz_protocol_decoder_feron_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + instance->decoder.parser_step = FeronDecoderStepReset; +} + +void subghz_protocol_decoder_feron_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + + // Feron Decoder + // 2025.04 - @xMasterX (MMX) + + // Key samples + /* + 0110001100111000 1000010101111010 - ON + 0110001100111000 1000010001111011 - OFF + + 0110001100111000 1000011001111001 - brightness up + 0110001100111000 1000011101111000 - brightness down + + 0110001100111000 1000001001111101 - scroll mode command + + ------------------------------------------ + 0110001100111000 0111000010001111 - R + 0110001100111000 0001101011100101 - B + 0110001100111000 0100000010111111 - G + */ + + switch(instance->decoder.parser_step) { + case FeronDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_feron_const.te_long * 6) < + subghz_protocol_feron_const.te_delta * 4)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = FeronDecoderStepSaveDuration; + } + break; + case FeronDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = FeronDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = FeronDecoderStepReset; + } + break; + case FeronDecoderStepCheckDuration: + if(!level) { + // Bit 0 is short and long timing = 350us HIGH (te_last) and 750us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_short) < + subghz_protocol_feron_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_feron_const.te_long) < + subghz_protocol_feron_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = FeronDecoderStepSaveDuration; + // Bit 1 is long and short timing = 750us HIGH (te_last) and 350us LOW + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_long) < + subghz_protocol_feron_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_feron_const.te_short) < + subghz_protocol_feron_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = FeronDecoderStepSaveDuration; + } else if( + // End of the key 500Low(we are here)/500High us + DURATION_DIFF( + duration, (uint16_t)(subghz_protocol_feron_const.te_short + (uint16_t)150)) < + subghz_protocol_feron_const.te_delta) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_short) < + subghz_protocol_feron_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_long) < + subghz_protocol_feron_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + // If got 32 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_feron_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = FeronDecoderStepReset; + } else { + instance->decoder.parser_step = FeronDecoderStepReset; + } + } else { + instance->decoder.parser_step = FeronDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_feron_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_feron_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_feron_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_feron_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_feron_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + + subghz_protocol_feron_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%08lX\r\n" + "Serial: 0x%04lX\r\n" + "Command: 0x%04lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.serial, + (uint32_t)(instance->generic.data & 0xFFFF)); +} diff --git a/lib/subghz/protocols/feron.h b/lib/subghz/protocols/feron.h new file mode 100644 index 000000000..97f0eb6fe --- /dev/null +++ b/lib/subghz/protocols/feron.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_FERON_NAME "Feron" + +typedef struct SubGhzProtocolDecoderFeron SubGhzProtocolDecoderFeron; +typedef struct SubGhzProtocolEncoderFeron SubGhzProtocolEncoderFeron; + +extern const SubGhzProtocolDecoder subghz_protocol_feron_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_feron_encoder; +extern const SubGhzProtocol subghz_protocol_feron; + +/** + * Allocate SubGhzProtocolEncoderFeron. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderFeron* pointer to a SubGhzProtocolEncoderFeron instance + */ +void* subghz_protocol_encoder_feron_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderFeron. + * @param context Pointer to a SubGhzProtocolEncoderFeron instance + */ +void subghz_protocol_encoder_feron_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderFeron instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_feron_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderFeron instance + */ +void subghz_protocol_encoder_feron_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderFeron instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_feron_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderFeron. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderFeron* pointer to a SubGhzProtocolDecoderFeron instance + */ +void* subghz_protocol_decoder_feron_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderFeron. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + */ +void subghz_protocol_decoder_feron_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderFeron. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + */ +void subghz_protocol_decoder_feron_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_feron_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_feron_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderFeron. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_feron_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderFeron. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_feron_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + * @param output Resulting text + */ +void subghz_protocol_decoder_feron_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index 0cb76393d..720a4d54a 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -168,13 +168,14 @@ static void subghz_protocol_encoder_gangqi_get_upload(SubGhzProtocolEncoderGangQ // Generate new key using custom or default button instance->generic.btn = subghz_protocol_gangqi_get_btn_code(); - uint64_t new_key = (instance->generic.data >> 14) << 14 | (instance->generic.btn << 10) | - (0b01 << 8); + uint16_t serial = (uint16_t)((instance->generic.data >> 18) & 0xFFFF); + uint8_t const_and_button = (uint8_t)(0xD0 | instance->generic.btn); + uint8_t serial_high = (uint8_t)(serial >> 8); + uint8_t serial_low = (uint8_t)(serial & 0xFF); + uint8_t bytesum = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button); - uint8_t crc = -0xD7 - ((new_key >> 32) & 0xFF) - ((new_key >> 24) & 0xFF) - - ((new_key >> 16) & 0xFF) - ((new_key >> 8) & 0xFF); - - instance->generic.data = (new_key | crc); + instance->generic.data = (instance->generic.data >> 14) << 14 | (instance->generic.btn << 10) | + (bytesum << 2); size_t index = 0; @@ -230,41 +231,12 @@ static void subghz_protocol_gangqi_remote_controller(SubGhzBlockGeneric* instanc subghz_custom_btn_set_max(3); // GangQi Decoder - // 09.2024 - @xMasterX (MMX) + // 09.2024 - @xMasterX (MMX) (last update - bytesum calculation at 02.2025) // Thanks @Skorpionm for support! + // Thanks @Drone1950 and @mishamyte (who spent 2 weeks on this) for making this work properly - //// 4D=F8=171=229 byte sum should be always the same - // Button - // Serial || BBBB || CRC (byte sum) with overflow and starting point 0xD7 - //034AAB75BC = 00110100101010101011 01 1101 01 101111 00 // A (0xD) - //034AAB79B8 = 00110100101010101011 01 1110 01 101110 00 // B (0xE) - //034AAB6DC4 = 00110100101010101011 01 1011 01 110001 00 // C (0xB) - //034AAB5DD4 = 00110100101010101011 01 0111 01 110101 00 // D (0x7) - //034AAB55DC = 00110100101010101011 01 0101 01 110111 00 // Settings (0x5) - //034AAB51E0 = 00110100101010101011 01 0100 01 111000 00 // A (0x4) - //034AAB49E8 = 00110100101010101011 01 0010 01 111010 00 // C (0x2) - //034AAB59D8 = 00110100101010101011 01 0110 01 110110 00 // D (0x6) - //034AAB45EC = 00110100101010101011 01 0001 01 111011 00 // Settings exit (0x1) - // - // Serial 3 bytes should meet requirements see validation example at subghz_protocol_decoder_gangqi_get_string - // - // Code for finding start byte for crc sum - // - //uint64_t test = 0x034AAB79B8; //B8 - //for(size_t byte = 0; byte < 0xFF; ++byte) { - // uint8_t crc_res = -byte - ((test >> 32) & 0xFF) - ((test >> 24) & 0xFF) - - // ((test >> 16) & 0xFF) - ((test >> 8) & 0xFF); - // if(crc_res == 0xB8) { - // uint64_t test2 = 0x034AAB6DC4; //C4 - // uint8_t crc_res2 = -byte - ((test2 >> 32) & 0xFF) - ((test2 >> 24) & 0xFF) - - // ((test2 >> 16) & 0xFF) - ((test2 >> 8) & 0xFF); - // if(crc_res2 == 0xC4) { - // printf("Start byte for CRC = %02lX / CRC = %02X \n", byte, crc_res); - // - // printf("Testing second parcel CRC = %02X", crc_res2); - // } - // } - // } + // Example of correct bytesum calculation + // 0xC8 - serial_high - serial_low - constant_and_button } SubGhzProtocolStatus @@ -483,23 +455,29 @@ void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output // Parse serial subghz_protocol_gangqi_remote_controller(&instance->generic); - // Get CRC - uint8_t crc = -0xD7 - ((instance->generic.data >> 32) & 0xFF) - - ((instance->generic.data >> 24) & 0xFF) - - ((instance->generic.data >> 16) & 0xFF) - ((instance->generic.data >> 8) & 0xFF); + // Get byte sum + uint16_t serial = (uint16_t)((instance->generic.data >> 18) & 0xFFFF); + uint8_t const_and_button = (uint8_t)(0xD0 | instance->generic.btn); + uint8_t serial_high = (uint8_t)(serial >> 8); + uint8_t serial_low = (uint8_t)(serial & 0xFF); + // Type 1 is what original remotes use, type 2 is "backdoor" sum that receiver accepts too + uint8_t sum_type1 = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button); + uint8_t sum_type2 = (uint8_t)(0x02 + serial_high + serial_low + const_and_button); furi_string_cat_printf( output, "%s %db\r\n" "Key: 0x%X%08lX\r\n" - "Serial: 0x%05lX CRC: 0x%02X\r\n" + "Serial: 0x%05lX\r\n" + "Sum: 0x%02X Sum2: 0x%02X\r\n" "Btn: 0x%01X - %s\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint8_t)(instance->generic.data >> 32), (uint32_t)(instance->generic.data & 0xFFFFFFFF), instance->generic.serial, - crc, + sum_type1, + sum_type2, instance->generic.btn, subghz_protocol_gangqi_get_button_name(instance->generic.btn)); } diff --git a/lib/subghz/protocols/hay21.c b/lib/subghz/protocols/hay21.c index 21d186df8..1e3576459 100644 --- a/lib/subghz/protocols/hay21.c +++ b/lib/subghz/protocols/hay21.c @@ -151,6 +151,9 @@ static void subghz_protocol_encoder_hay21_get_upload(SubGhzProtocolEncoderHay21* } else { instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } + if(furi_hal_subghz_get_rolling_counter_mult() >= 0xF) { + instance->generic.cnt = 0xF; + } } else if(instance->generic.cnt >= 0xF) { instance->generic.cnt = 0; } diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c index ed94cb7a9..fc76affa0 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -169,10 +169,10 @@ static void subghz_protocol_encoder_hollarm_get_upload(SubGhzProtocolEncoderHoll uint64_t new_key = (instance->generic.data >> 12) << 12 | (instance->generic.btn << 8); - uint8_t crc = ((new_key >> 32) & 0xFF) + ((new_key >> 24) & 0xFF) + ((new_key >> 16) & 0xFF) + - ((new_key >> 8) & 0xFF); + uint8_t bytesum = ((new_key >> 32) & 0xFF) + ((new_key >> 24) & 0xFF) + + ((new_key >> 16) & 0xFF) + ((new_key >> 8) & 0xFF); - instance->generic.data = (new_key | crc); + instance->generic.data = (new_key | bytesum); size_t index = 0; @@ -233,7 +233,7 @@ static void subghz_protocol_hollarm_remote_controller(SubGhzBlockGeneric* instan // F0B9342401 = 01 8bit Sum // F0B9342805 = 05 8bit Sum - // Serial (moved 2bit to right) | Btn | 8b CRC (previous 4 bytes sum) + // Serial (moved 2bit to right) | Btn | 8b previous 4 bytes sum // 00001111000010111001001101000010 0010 11111111 btn = (0x2) // 00001111000010111001001101000010 0001 11111110 btn = (0x1) // 00001111000010111001001101000010 0100 00000001 btn = (0x4) @@ -376,6 +376,21 @@ void subghz_protocol_decoder_hollarm_feed(void* context, bool level, volatile ui // Saving with 2bit to the right offset for proper parsing instance->generic.data = (instance->decoder.decode_data >> 2); instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + uint8_t bytesum = ((instance->generic.data >> 32) & 0xFF) + + ((instance->generic.data >> 24) & 0xFF) + + ((instance->generic.data >> 16) & 0xFF) + + ((instance->generic.data >> 8) & 0xFF); + + if(bytesum != (instance->generic.data & 0xFF)) { + // Check if the key is valid by verifying the sum + instance->generic.data = 0; + instance->generic.data_count_bit = 0; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = HollarmDecoderStepReset; + break; + } if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context); } @@ -447,23 +462,23 @@ void subghz_protocol_decoder_hollarm_get_string(void* context, FuriString* outpu // Parse serial subghz_protocol_hollarm_remote_controller(&instance->generic); - // Get CRC - uint8_t crc = ((instance->generic.data >> 32) & 0xFF) + - ((instance->generic.data >> 24) & 0xFF) + - ((instance->generic.data >> 16) & 0xFF) + ((instance->generic.data >> 8) & 0xFF); + // Get byte sum + uint8_t bytesum = + ((instance->generic.data >> 32) & 0xFF) + ((instance->generic.data >> 24) & 0xFF) + + ((instance->generic.data >> 16) & 0xFF) + ((instance->generic.data >> 8) & 0xFF); furi_string_cat_printf( output, "%s %db\r\n" "Key: 0x%02lX%08lX\r\n" - "Serial: 0x%06lX CRC: %02X\r\n" + "Serial: 0x%06lX Sum: %02X\r\n" "Btn: 0x%01X - %s\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), (uint32_t)instance->generic.data, instance->generic.serial, - crc, + bytesum, instance->generic.btn, subghz_protocol_hollarm_get_button_name(instance->generic.btn)); } diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 12d739dec..a774e5825 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -190,7 +190,9 @@ static bool subghz_protocol_keeloq_gen_data( } else { instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if(instance->generic.cnt >= 0xFFFF) { + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; } } diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index 56795d2dd..119a198bc 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -161,7 +161,7 @@ static bool subghz_protocol_kinggates_stylo_4k_gen_data( } else { instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if(instance->generic.cnt >= 0xFFFF) { + } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; } diff --git a/lib/subghz/protocols/marantec24.c b/lib/subghz/protocols/marantec24.c index eee009f2d..588aa1e5a 100644 --- a/lib/subghz/protocols/marantec24.c +++ b/lib/subghz/protocols/marantec24.c @@ -214,6 +214,9 @@ void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile furi_assert(context); SubGhzProtocolDecoderMarantec24* instance = context; + // Marantec24 Decoder + // 2024 - @xMasterX (MMX) + // Key samples // 101011000000010111001000 = AC05C8 // 101011000000010111000100 = AC05C4 diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 8e007582b..50adb43dc 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -157,7 +157,7 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload( } else { instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if(instance->generic.cnt >= 0xFFFF) { + } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; } uint64_t decrypt = ((uint64_t)instance->generic.serial << 16) | instance->generic.cnt; diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index ff7d29650..c73923c7a 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -1,6 +1,6 @@ #include "protocol_items.h" // IWYU pragma: keep -const SubGhzProtocol* subghz_protocol_registry_items[] = { +const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_gate_tx, &subghz_protocol_keeloq, &subghz_protocol_star_line, @@ -51,6 +51,8 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_marantec24, &subghz_protocol_hollarm, &subghz_protocol_hay21, + &subghz_protocol_revers_rb2, + &subghz_protocol_feron, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 71265e88c..6165d748a 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -52,3 +52,5 @@ #include "marantec24.h" #include "hollarm.h" #include "hay21.h" +#include "revers_rb2.h" +#include "feron.h" diff --git a/lib/subghz/protocols/revers_rb2.c b/lib/subghz/protocols/revers_rb2.c new file mode 100644 index 000000000..510e2698a --- /dev/null +++ b/lib/subghz/protocols/revers_rb2.c @@ -0,0 +1,408 @@ +#include "revers_rb2.h" +#include +#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolRevers_RB2" + +static const SubGhzBlockConst subghz_protocol_revers_rb2_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 160, + .min_count_bit_for_found = 64, +}; + +struct SubGhzProtocolDecoderRevers_RB2 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + ManchesterState manchester_saved_state; + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderRevers_RB2 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Revers_RB2DecoderStepReset = 0, + Revers_RB2DecoderStepHeader, + Revers_RB2DecoderStepDecoderData, +} Revers_RB2DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_revers_rb2_decoder = { + .alloc = subghz_protocol_decoder_revers_rb2_alloc, + .free = subghz_protocol_decoder_revers_rb2_free, + + .feed = subghz_protocol_decoder_revers_rb2_feed, + .reset = subghz_protocol_decoder_revers_rb2_reset, + + .get_hash_data = subghz_protocol_decoder_revers_rb2_get_hash_data, + .serialize = subghz_protocol_decoder_revers_rb2_serialize, + .deserialize = subghz_protocol_decoder_revers_rb2_deserialize, + .get_string = subghz_protocol_decoder_revers_rb2_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_revers_rb2_encoder = { + .alloc = subghz_protocol_encoder_revers_rb2_alloc, + .free = subghz_protocol_encoder_revers_rb2_free, + + .deserialize = subghz_protocol_encoder_revers_rb2_deserialize, + .stop = subghz_protocol_encoder_revers_rb2_stop, + .yield = subghz_protocol_encoder_revers_rb2_yield, +}; + +const SubGhzProtocol subghz_protocol_revers_rb2 = { + .name = SUBGHZ_PROTOCOL_REVERSRB2_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_revers_rb2_decoder, + .encoder = &subghz_protocol_revers_rb2_encoder, +}; + +void* subghz_protocol_encoder_revers_rb2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderRevers_RB2* instance = malloc(sizeof(SubGhzProtocolEncoderRevers_RB2)); + + instance->base.protocol = &subghz_protocol_revers_rb2; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_revers_rb2_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderRevers_RB2* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static LevelDuration + subghz_protocol_encoder_revers_rb2_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_revers_rb2_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_revers_rb2_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_revers_rb2_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_revers_rb2_const.te_short; + data.level = true; + break; + + default: + furi_crash("SubGhz: ManchesterEncoderResult is incorrect."); + break; + } + return level_duration_make(data.level, data.duration); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderRevers_RB2 instance + */ +static void + subghz_protocol_encoder_revers_rb2_get_upload(SubGhzProtocolEncoderRevers_RB2* instance) { + furi_assert(instance); + size_t index = 0; + + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result); + manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result); + } + instance->encoder.upload[index++] = + subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_revers_rb2_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)320); + instance->encoder.size_upload = index; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_revers_rb2_remote_controller(SubGhzBlockGeneric* instance) { + // Revers RB2 / RB2M Decoder + // 02.2025 - @xMasterX (MMX) + instance->serial = (((instance->data << 16) >> 16) >> 10); +} + +SubGhzProtocolStatus + subghz_protocol_encoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderRevers_RB2* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_revers_rb2_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_revers_rb2_remote_controller(&instance->generic); + subghz_protocol_encoder_revers_rb2_get_upload(instance); + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_revers_rb2_stop(void* context) { + SubGhzProtocolEncoderRevers_RB2* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_revers_rb2_yield(void* context) { + SubGhzProtocolEncoderRevers_RB2* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_revers_rb2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderRevers_RB2* instance = malloc(sizeof(SubGhzProtocolDecoderRevers_RB2)); + instance->base.protocol = &subghz_protocol_revers_rb2; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_revers_rb2_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + free(instance); +} + +void subghz_protocol_decoder_revers_rb2_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +void subghz_protocol_decoder_revers_rb2_addbit(void* context, bool data) { + SubGhzProtocolDecoderRevers_RB2* instance = context; + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; + instance->decoder.decode_count_bit++; + + if(instance->decoder.decode_count_bit >= 65) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + return; + } + + if(instance->decoder.decode_count_bit < + subghz_protocol_revers_rb2_const.min_count_bit_for_found) { + return; + } + + // Revers RB2 / RB2M Decoder + // 02.2025 - @xMasterX (MMX) + + uint16_t preamble = (instance->decoder.decode_data >> 48) & 0xFF; + uint16_t stop_code = (instance->decoder.decode_data & 0x3FF); + + if(preamble == 0xFF && stop_code == 0x200) { + //Found header and stop code + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + } +} + +void subghz_protocol_decoder_revers_rb2_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + ManchesterEvent event = ManchesterEventReset; + + switch(instance->decoder.parser_step) { + case Revers_RB2DecoderStepReset: + if((!level) && + (DURATION_DIFF(duration, 600) < subghz_protocol_revers_rb2_const.te_delta)) { + instance->decoder.parser_step = Revers_RB2DecoderStepHeader; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + } + break; + case Revers_RB2DecoderStepHeader: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) < + subghz_protocol_revers_rb2_const.te_delta) { + if(instance->decoder.te_last == 1) { + instance->header_count++; + } + instance->decoder.te_last = level; + } else { + instance->header_count = 0; + instance->decoder.te_last = 0; + instance->decoder.parser_step = Revers_RB2DecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) < + subghz_protocol_revers_rb2_const.te_delta) { + if(instance->decoder.te_last == 0) { + instance->header_count++; + } + instance->decoder.te_last = level; + } else { + instance->header_count = 0; + instance->decoder.te_last = 0; + instance->decoder.parser_step = Revers_RB2DecoderStepReset; + } + } + + if(instance->header_count == 4) { + instance->header_count = 0; + instance->decoder.decode_data = 0xF; + instance->decoder.decode_count_bit = 4; + instance->decoder.parser_step = Revers_RB2DecoderStepDecoderData; + } + break; + case Revers_RB2DecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) < + subghz_protocol_revers_rb2_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_long) < + subghz_protocol_revers_rb2_const.te_delta) { + event = ManchesterEventLongLow; + } else { + instance->decoder.parser_step = Revers_RB2DecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) < + subghz_protocol_revers_rb2_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_long) < + subghz_protocol_revers_rb2_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = Revers_RB2DecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + subghz_protocol_decoder_revers_rb2_addbit(instance, data); + } + } + break; + } +} + +uint8_t subghz_protocol_decoder_revers_rb2_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_revers_rb2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_revers_rb2_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_revers_rb2_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + subghz_protocol_revers_rb2_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:%lX%08lX\r\n" + "Sn:0x%08lX \r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.serial); +} diff --git a/lib/subghz/protocols/revers_rb2.h b/lib/subghz/protocols/revers_rb2.h new file mode 100644 index 000000000..7ccd2a9a2 --- /dev/null +++ b/lib/subghz/protocols/revers_rb2.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_REVERSRB2_NAME "Revers_RB2" + +typedef struct SubGhzProtocolDecoderRevers_RB2 SubGhzProtocolDecoderRevers_RB2; +typedef struct SubGhzProtocolEncoderRevers_RB2 SubGhzProtocolEncoderRevers_RB2; + +extern const SubGhzProtocolDecoder subghz_protocol_revers_rb2_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_revers_rb2_encoder; +extern const SubGhzProtocol subghz_protocol_revers_rb2; + +/** + * Allocate SubGhzProtocolEncoderRevers_RB2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderRevers_RB2* pointer to a SubGhzProtocolEncoderRevers_RB2 instance + */ +void* subghz_protocol_encoder_revers_rb2_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderRevers_RB2. + * @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance + */ +void subghz_protocol_encoder_revers_rb2_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance + */ +void subghz_protocol_encoder_revers_rb2_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_revers_rb2_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderRevers_RB2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderRevers_RB2* pointer to a SubGhzProtocolDecoderRevers_RB2 instance + */ +void* subghz_protocol_decoder_revers_rb2_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderRevers_RB2. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + */ +void subghz_protocol_decoder_revers_rb2_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderRevers_RB2. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + */ +void subghz_protocol_decoder_revers_rb2_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_revers_rb2_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_revers_rb2_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderRevers_RB2. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_revers_rb2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderRevers_RB2. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_revers_rb2_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index 0ed2af073..e1b21d4fd 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -228,7 +228,7 @@ static bool subghz_protocol_secplus_v1_encode(SubGhzProtocolEncoderSecPlus_v1* i rolling = 0xE6000000; } if(fixed > 0xCFD41B90) { - FURI_LOG_E("TAG", "Encode wrong fixed data"); + FURI_LOG_E(TAG, "Encode wrong fixed data"); return false; } diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index 22d2b5e9f..0606b9bf5 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -136,7 +136,7 @@ static bool } else { instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if(instance->generic.cnt >= 0xFFFF) { + } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; } diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index a1308dd6d..fd41180d5 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -130,7 +130,7 @@ static bool subghz_protocol_somfy_telis_gen_data( } else { instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if(instance->generic.cnt >= 0xFFFF) { + } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; } diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c index 75a7fd471..0005ad5fc 100644 --- a/lib/subghz/protocols/star_line.c +++ b/lib/subghz/protocols/star_line.c @@ -135,7 +135,7 @@ static bool } else { instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if(instance->generic.cnt >= 0xFFFF) { + } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; } uint32_t fix = btn << 24 | instance->generic.serial; diff --git a/lib/subghz/registry.h b/lib/subghz/registry.h index 8529c1097..8de376b16 100644 --- a/lib/subghz/registry.h +++ b/lib/subghz/registry.h @@ -12,7 +12,7 @@ typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry; typedef struct SubGhzProtocol SubGhzProtocol; struct SubGhzProtocolRegistry { - const SubGhzProtocol** items; + const SubGhzProtocol* const* items; const size_t size; }; diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 2ee02c7f9..ab8c77910 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -296,6 +296,7 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { FURI_LOG_E(TAG, "Rewind error"); break; } + furi_string_reset(temp_str); while(flipper_format_read_string(fff_data_file, "Custom_preset_name", temp_str)) { FURI_LOG_I(TAG, "Custom preset loaded %s", furi_string_get_cstr(temp_str)); subghz_setting_load_custom_preset( diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 0381ce5b9..dceb2fc70 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -13,6 +13,10 @@ env.Append( SDK_HEADERS=[ File("api_lock.h"), File("value_index.h"), + File("cli/cli_ansi.h"), + File("cli/cli_command.h"), + File("cli/cli_registry.h"), + File("cli/shell/cli_shell.h"), File("compress.h"), File("manchester_decoder.h"), File("manchester_encoder.h"), diff --git a/lib/toolbox/cli/cli_ansi.c b/lib/toolbox/cli/cli_ansi.c new file mode 100644 index 000000000..81167643b --- /dev/null +++ b/lib/toolbox/cli/cli_ansi.c @@ -0,0 +1,123 @@ +#include "cli_ansi.h" + +typedef enum { + CliAnsiParserStateInitial, + CliAnsiParserStateEscape, + CliAnsiParserStateEscapeBrace, + CliAnsiParserStateEscapeBraceOne, + CliAnsiParserStateEscapeBraceOneSemicolon, + CliAnsiParserStateEscapeBraceOneSemicolonModifiers, +} CliAnsiParserState; + +struct CliAnsiParser { + CliAnsiParserState state; + CliModKey modifiers; +}; + +CliAnsiParser* cli_ansi_parser_alloc(void) { + CliAnsiParser* parser = malloc(sizeof(CliAnsiParser)); + return parser; +} + +void cli_ansi_parser_free(CliAnsiParser* parser) { + free(parser); +} + +/** + * @brief Converts a single character representing a special key into the enum + * representation + */ +static CliKey cli_ansi_key_from_mnemonic(char c) { + switch(c) { + case 'A': + return CliKeyUp; + case 'B': + return CliKeyDown; + case 'C': + return CliKeyRight; + case 'D': + return CliKeyLeft; + case 'F': + return CliKeyEnd; + case 'H': + return CliKeyHome; + default: + return CliKeyUnrecognized; + } +} + +#define PARSER_RESET_AND_RETURN(parser, modifiers_val, key_val) \ + do { \ + parser->state = CliAnsiParserStateInitial; \ + return (CliAnsiParserResult){ \ + .is_done = true, \ + .result = (CliKeyCombo){ \ + .modifiers = modifiers_val, \ + .key = key_val, \ + }}; \ + } while(0); + +CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c) { + switch(parser->state) { + case CliAnsiParserStateInitial: + // -> + if(c != CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c); // -V1048 + + // ... + parser->state = CliAnsiParserStateEscape; + break; + + case CliAnsiParserStateEscape: + // -> + if(c == CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c); + + // -> Alt + + if(c != '[') PARSER_RESET_AND_RETURN(parser, CliModKeyAlt, c); + + // [ ... + parser->state = CliAnsiParserStateEscapeBrace; + break; + + case CliAnsiParserStateEscapeBrace: + // [ -> + if(c != '1') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, cli_ansi_key_from_mnemonic(c)); + + // [ 1 ... + parser->state = CliAnsiParserStateEscapeBraceOne; + break; + + case CliAnsiParserStateEscapeBraceOne: + // [ 1 -> error + if(c != ';') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, CliKeyUnrecognized); + + // [ 1 ; ... + parser->state = CliAnsiParserStateEscapeBraceOneSemicolon; + break; + + case CliAnsiParserStateEscapeBraceOneSemicolon: + // [ 1 ; ... + parser->modifiers = (c - '0'); + parser->modifiers &= ~1; + parser->state = CliAnsiParserStateEscapeBraceOneSemicolonModifiers; + break; + + case CliAnsiParserStateEscapeBraceOneSemicolonModifiers: + // [ 1 ; -> + + PARSER_RESET_AND_RETURN(parser, parser->modifiers, cli_ansi_key_from_mnemonic(c)); + } + + return (CliAnsiParserResult){.is_done = false}; +} + +CliAnsiParserResult cli_ansi_parser_feed_timeout(CliAnsiParser* parser) { + CliAnsiParserResult result = {.is_done = false}; + + if(parser->state == CliAnsiParserStateEscape) { + result.is_done = true; + result.result.key = CliKeyEsc; + result.result.modifiers = CliModKeyNo; + } + + parser->state = CliAnsiParserStateInitial; + return result; +} diff --git a/lib/toolbox/cli/cli_ansi.h b/lib/toolbox/cli/cli_ansi.h new file mode 100644 index 000000000..04b6d7759 --- /dev/null +++ b/lib/toolbox/cli/cli_ansi.h @@ -0,0 +1,153 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// text styling + +#define ANSI_RESET "\e[0m" +#define ANSI_BOLD "\e[1m" +#define ANSI_FAINT "\e[2m" +#define ANSI_INVERT "\e[7m" + +#define ANSI_FG_BLACK "\e[30m" +#define ANSI_FG_RED "\e[31m" +#define ANSI_FG_GREEN "\e[32m" +#define ANSI_FG_YELLOW "\e[33m" +#define ANSI_FG_BLUE "\e[34m" +#define ANSI_FG_MAGENTA "\e[35m" +#define ANSI_FG_CYAN "\e[36m" +#define ANSI_FG_WHITE "\e[37m" +#define ANSI_FG_BR_BLACK "\e[90m" +#define ANSI_FG_BR_RED "\e[91m" +#define ANSI_FG_BR_GREEN "\e[92m" +#define ANSI_FG_BR_YELLOW "\e[93m" +#define ANSI_FG_BR_BLUE "\e[94m" +#define ANSI_FG_BR_MAGENTA "\e[95m" +#define ANSI_FG_BR_CYAN "\e[96m" +#define ANSI_FG_BR_WHITE "\e[97m" + +#define ANSI_BG_BLACK "\e[40m" +#define ANSI_BG_RED "\e[41m" +#define ANSI_BG_GREEN "\e[42m" +#define ANSI_BG_YELLOW "\e[43m" +#define ANSI_BG_BLUE "\e[44m" +#define ANSI_BG_MAGENTA "\e[45m" +#define ANSI_BG_CYAN "\e[46m" +#define ANSI_BG_WHITE "\e[47m" +#define ANSI_BG_BR_BLACK "\e[100m" +#define ANSI_BG_BR_RED "\e[101m" +#define ANSI_BG_BR_GREEN "\e[102m" +#define ANSI_BG_BR_YELLOW "\e[103m" +#define ANSI_BG_BR_BLUE "\e[104m" +#define ANSI_BG_BR_MAGENTA "\e[105m" +#define ANSI_BG_BR_CYAN "\e[106m" +#define ANSI_BG_BR_WHITE "\e[107m" + +#define ANSI_FLIPPER_BRAND_ORANGE "\e[38;2;255;130;0m" + +// cursor positioning + +#define ANSI_CURSOR_UP_BY(rows) "\e[" rows "A" +#define ANSI_CURSOR_DOWN_BY(rows) "\e[" rows "B" +#define ANSI_CURSOR_RIGHT_BY(cols) "\e[" cols "C" +#define ANSI_CURSOR_LEFT_BY(cols) "\e[" cols "D" +#define ANSI_CURSOR_DOWN_BY_AND_FIRST_COLUMN(rows) "\e[" rows "E" +#define ANSI_CURSOR_UP_BY_AND_FIRST_COLUMN(rows) "\e[" rows "F" +#define ANSI_CURSOR_HOR_POS(pos) "\e[" pos "G" +#define ANSI_CURSOR_POS(row, col) "\e[" row ";" col "H" + +// erasing + +#define ANSI_ERASE_FROM_CURSOR_TO_END "0" +#define ANSI_ERASE_FROM_START_TO_CURSOR "1" +#define ANSI_ERASE_ENTIRE "2" + +#define ANSI_ERASE_DISPLAY(portion) "\e[" portion "J" +#define ANSI_ERASE_LINE(portion) "\e[" portion "K" +#define ANSI_ERASE_SCROLLBACK_BUFFER ANSI_ERASE_DISPLAY("3") + +// misc + +#define ANSI_INSERT_MODE_ENABLE "\e[4h" +#define ANSI_INSERT_MODE_DISABLE "\e[4l" + +typedef enum { + CliKeyUnrecognized = 0, + + CliKeySOH = 0x01, + CliKeyETX = 0x03, + CliKeyEOT = 0x04, + CliKeyBell = 0x07, + CliKeyBackspace = 0x08, + CliKeyTab = 0x09, + CliKeyLF = 0x0A, + CliKeyFF = 0x0C, + CliKeyCR = 0x0D, + CliKeyETB = 0x17, + CliKeyEsc = 0x1B, + CliKeyUS = 0x1F, + CliKeySpace = 0x20, + CliKeyDEL = 0x7F, + + CliKeySpecial = 0x80, + CliKeyLeft, + CliKeyRight, + CliKeyUp, + CliKeyDown, + CliKeyHome, + CliKeyEnd, +} CliKey; + +typedef enum { + CliModKeyNo = 0, + CliModKeyAlt = 2, + CliModKeyCtrl = 4, + CliModKeyMeta = 8, +} CliModKey; + +typedef struct { + CliModKey modifiers; + CliKey key; +} CliKeyCombo; + +typedef struct CliAnsiParser CliAnsiParser; + +typedef struct { + bool is_done; + CliKeyCombo result; +} CliAnsiParserResult; + +/** + * @brief Allocates an ANSI parser + */ +CliAnsiParser* cli_ansi_parser_alloc(void); + +/** + * @brief Frees an ANSI parser + */ +void cli_ansi_parser_free(CliAnsiParser* parser); + +/** + * @brief Feeds an ANSI parser a character + */ +CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c); + +/** + * @brief Feeds an ANSI parser a timeout event + * + * As a user of the ANSI parser API, you are responsible for calling this + * function some time after the last character was fed into the parser. The + * recommended timeout is about 10 ms. The exact value does not matter as long + * as it is small enough for the user not notice a delay, but big enough that + * when a terminal is sending an escape sequence, this function does not get + * called in between the characters of the sequence. + */ +CliAnsiParserResult cli_ansi_parser_feed_timeout(CliAnsiParser* parser); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/cli/cli_command.c b/lib/toolbox/cli/cli_command.c new file mode 100644 index 000000000..a3c9ff292 --- /dev/null +++ b/lib/toolbox/cli/cli_command.c @@ -0,0 +1,17 @@ +#include "cli_command.h" +#include "cli_ansi.h" + +bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side) { + if(pipe_state(side) == PipeStateBroken) return true; + if(!pipe_bytes_available(side)) return false; + char c = getchar(); + return c == CliKeyETX; +} + +void cli_print_usage(const char* cmd, const char* usage, const char* arg) { + furi_check(cmd); + furi_check(arg); + furi_check(usage); + + printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage); +} diff --git a/lib/toolbox/cli/cli_command.h b/lib/toolbox/cli/cli_command.h new file mode 100644 index 000000000..2d1d851d6 --- /dev/null +++ b/lib/toolbox/cli/cli_command.h @@ -0,0 +1,103 @@ +/** + * @file cli_command.h + * Command metadata and helpers + */ + +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CLI_PLUGIN_API_VERSION 1 + +typedef enum { + CliCommandFlagDefault = 0, /**< Default */ + CliCommandFlagParallelSafe = (1 << 0), /**< Safe to run in parallel with other apps */ + CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */ + CliCommandFlagDontAttachStdio = (1 << 2), /**< Do no attach I/O pipe to thread stdio */ + CliCommandFlagUseShellThread = + (1 + << 3), /**< Don't start a separate thread to run the command in. Incompatible with DontAttachStdio */ + + // internal flags (do not set them yourselves!) + + CliCommandFlagExternal = (1 << 4), /**< The command comes from a .fal file */ +} CliCommandFlag; + +/** + * @brief CLI command execution callback pointer + * + * This callback will be called from a separate thread spawned just for your + * command. The pipe will be installed as the thread's stdio, so you can use + * `printf`, `getchar` and other standard functions to communicate with the + * user. + * + * @param [in] pipe Pipe that can be used to send and receive data. If + * `CliCommandFlagDontAttachStdio` was not set, you can + * also use standard C functions (printf, getc, etc.) to + * access this pipe. + * @param [in] args String with what was passed after the command + * @param [in] context Whatever you provided to `cli_add_command` + */ +typedef void (*CliCommandExecuteCallback)(PipeSide* pipe, FuriString* args, void* context); + +typedef struct { + char* name; + CliCommandExecuteCallback execute_callback; + CliCommandFlag flags; + size_t stack_depth; +} CliCommandDescriptor; + +/** + * @brief Configuration for locating external commands + */ +typedef struct { + const char* search_directory; // +#include + +#define TAG "CliRegistry" + +struct CliRegistry { + CliCommandDict_t commands; + FuriMutex* mutex; +}; + +CliRegistry* cli_registry_alloc(void) { + CliRegistry* registry = malloc(sizeof(CliRegistry)); + CliCommandDict_init(registry->commands); + registry->mutex = furi_mutex_alloc(FuriMutexTypeRecursive); + return registry; +} + +void cli_registry_free(CliRegistry* registry) { + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + furi_mutex_free(registry->mutex); + CliCommandDict_clear(registry->commands); + free(registry); +} + +void cli_registry_add_command( + CliRegistry* registry, + const char* name, + CliCommandFlag flags, + CliCommandExecuteCallback callback, + void* context) { + cli_registry_add_command_ex( + registry, name, flags, callback, context, CLI_BUILTIN_COMMAND_STACK_SIZE); +} + +void cli_registry_add_command_ex( + CliRegistry* registry, + const char* name, + CliCommandFlag flags, + CliCommandExecuteCallback callback, + void* context, + size_t stack_size) { + furi_check(registry); + furi_check(name); + furi_check(callback); + + // the shell always attaches the pipe to the stdio, thus both flags can't be used at once + if(flags & CliCommandFlagUseShellThread) furi_check(!(flags & CliCommandFlagDontAttachStdio)); + + FuriString* name_str; + name_str = furi_string_alloc_set(name); + // command cannot contain spaces + furi_check(furi_string_search_char(name_str, ' ') == FURI_STRING_FAILURE); + + CliRegistryCommand command = { + .context = context, + .execute_callback = callback, + .flags = flags, + .stack_depth = stack_size, + }; + + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + CliCommandDict_set_at(registry->commands, name_str, command); + furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); + + furi_string_free(name_str); +} + +void cli_registry_delete_command(CliRegistry* registry, const char* name) { + furi_check(registry); + FuriString* name_str; + name_str = furi_string_alloc_set(name); + furi_string_trim(name_str); + + size_t name_replace; + do { + name_replace = furi_string_replace(name_str, " ", "_"); + } while(name_replace != FURI_STRING_FAILURE); + + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + CliCommandDict_erase(registry->commands, name_str); + furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); + + furi_string_free(name_str); +} + +bool cli_registry_get_command( + CliRegistry* registry, + FuriString* command, + CliRegistryCommand* result) { + furi_assert(registry); + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + CliRegistryCommand* data = CliCommandDict_get(registry->commands, command); + if(data) *result = *data; + + furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); + + return !!data; +} + +void cli_registry_remove_external_commands(CliRegistry* registry) { + furi_check(registry); + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + + CliCommandDict_t internal_cmds; + CliCommandDict_init(internal_cmds); + for + M_EACH(item, registry->commands, CliCommandDict_t) { + if(!(item->value.flags & CliCommandFlagExternal)) + CliCommandDict_set_at(internal_cmds, item->key, item->value); + } + CliCommandDict_move(registry->commands, internal_cmds); + + furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); +} + +void cli_registry_reload_external_commands( + CliRegistry* registry, + const CliCommandExternalConfig* config) { + furi_check(registry); + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + FURI_LOG_D(TAG, "Reloading ext commands"); + + cli_registry_remove_external_commands(registry); + + // iterate over files in plugin directory + Storage* storage = furi_record_open(RECORD_STORAGE); + File* plugin_dir = storage_file_alloc(storage); + + if(storage_dir_open(plugin_dir, config->search_directory)) { + char plugin_filename[64]; + FuriString* plugin_name = furi_string_alloc(); + + while(storage_dir_read(plugin_dir, NULL, plugin_filename, sizeof(plugin_filename))) { + FURI_LOG_T(TAG, "Plugin: %s", plugin_filename); + furi_string_set_str(plugin_name, plugin_filename); + + furi_check(furi_string_end_with_str(plugin_name, ".fal")); + furi_string_replace_all_str(plugin_name, ".fal", ""); + furi_check(furi_string_start_with_str(plugin_name, config->fal_prefix)); + furi_string_replace_at(plugin_name, 0, strlen(config->fal_prefix), ""); + + CliRegistryCommand command = { + .context = NULL, + .execute_callback = NULL, + .flags = CliCommandFlagExternal, + }; + CliCommandDict_set_at(registry->commands, plugin_name, command); + } + + furi_string_free(plugin_name); + } + + storage_dir_close(plugin_dir); + storage_file_free(plugin_dir); + furi_record_close(RECORD_STORAGE); + + FURI_LOG_D(TAG, "Done reloading ext commands"); + furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); +} + +void cli_registry_lock(CliRegistry* registry) { + furi_assert(registry); + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); +} + +void cli_registry_unlock(CliRegistry* registry) { + furi_assert(registry); + furi_mutex_release(registry->mutex); +} + +CliCommandDict_t* cli_registry_get_commands(CliRegistry* registry) { + furi_assert(registry); + return ®istry->commands; +} diff --git a/lib/toolbox/cli/cli_registry.h b/lib/toolbox/cli/cli_registry.h new file mode 100644 index 000000000..44650e79b --- /dev/null +++ b/lib/toolbox/cli/cli_registry.h @@ -0,0 +1,92 @@ +/** + * @file cli_registry.h + * API for registering commands with a CLI shell + */ + +#pragma once + +#include +#include +#include +#include "cli_command.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct CliRegistry CliRegistry; + +/** + * @brief Allocates a `CliRegistry`. + */ +CliRegistry* cli_registry_alloc(void); + +/** + * @brief Frees a `CliRegistry`. + */ +void cli_registry_free(CliRegistry* registry); + +/** + * @brief Registers a command with the registry. Provides less options than the + * `_ex` counterpart. + * + * @param [in] registry Pointer to registry instance + * @param [in] name Command name + * @param [in] flags see CliCommandFlag + * @param [in] callback Callback function + * @param [in] context Custom context + */ +void cli_registry_add_command( + CliRegistry* registry, + const char* name, + CliCommandFlag flags, + CliCommandExecuteCallback callback, + void* context); + +/** + * @brief Registers a command with the registry. Provides more options than the + * non-`_ex` counterpart. + * + * @param [in] registry Pointer to registry instance + * @param [in] name Command name + * @param [in] flags see CliCommandFlag + * @param [in] callback Callback function + * @param [in] context Custom context + * @param [in] stack_size Thread stack size + */ +void cli_registry_add_command_ex( + CliRegistry* registry, + const char* name, + CliCommandFlag flags, + CliCommandExecuteCallback callback, + void* context, + size_t stack_size); + +/** + * @brief Deletes a cli command + * + * @param [in] registry Pointer to registry instance + * @param [in] name Command name + */ +void cli_registry_delete_command(CliRegistry* registry, const char* name); + +/** + * @brief Unregisters all external commands + * + * @param [in] registry Pointer to registry instance + */ +void cli_registry_remove_external_commands(CliRegistry* registry); + +/** + * @brief Reloads the list of externally available commands + * + * @param [in] registry Pointer to registry instance + * @param [in] config See `CliCommandExternalConfig` + */ +void cli_registry_reload_external_commands( + CliRegistry* registry, + const CliCommandExternalConfig* config); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/cli/cli_registry_i.h b/lib/toolbox/cli/cli_registry_i.h new file mode 100644 index 000000000..31995832f --- /dev/null +++ b/lib/toolbox/cli/cli_registry_i.h @@ -0,0 +1,45 @@ +/** + * @file cli_registry_i.h + * Internal API for getting commands registered with the CLI + */ + +#pragma once + +#include +#include +#include "cli_registry.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CLI_BUILTIN_COMMAND_STACK_SIZE (4 * 1024U) + +typedef struct { + void* context; // +#include +#include +#include +#include +#include +#include +#include + +#define TAG "CliShell" + +#define ANSI_TIMEOUT_MS 10 + +typedef enum { + CliShellComponentCompletions, + CliShellComponentLine, + CliShellComponentMAX, //pipe); + pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop); + pipe_set_callback_context(cli_shell->pipe, cli_shell); + pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0); + pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, 0); +} + +static void cli_shell_detach_pipe(CliShell* cli_shell) { + pipe_detach_from_event_loop(cli_shell->pipe); + furi_thread_set_stdin_callback(NULL, NULL); + furi_thread_set_stdout_callback(NULL, NULL); +} + +// ================= +// Built-in commands +// ================= + +void cli_command_reload_external(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + CliShell* shell = context; + furi_check(shell->ext_config); + cli_registry_reload_external_commands(shell->registry, shell->ext_config); + printf("OK!"); +} + +void cli_command_help(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + CliShell* shell = context; + CliRegistry* registry = shell->registry; + + const size_t columns = 3; + + printf("Available commands:\r\n" ANSI_FG_GREEN); + cli_registry_lock(registry); + CliCommandDict_t* commands = cli_registry_get_commands(registry); + size_t commands_count = CliCommandDict_size(*commands); + + CliCommandDict_it_t iterator; + CliCommandDict_it(iterator, *commands); + for(size_t i = 0; i < commands_count; i++) { + const CliCommandDict_itref_t* item = CliCommandDict_cref(iterator); + printf("%-30s", furi_string_get_cstr(item->key)); + CliCommandDict_next(iterator); + + if(i % columns == columns - 1) printf("\r\n"); + } + + if(shell->ext_config) + printf( + ANSI_RESET + "\r\nIf you added a new external command and can't see it above, run `reload_ext_cmds`"); + printf(ANSI_RESET "\r\nFind out more: https://docs.flipper.net/development/cli"); + + cli_registry_unlock(registry); +} + +void cli_command_exit(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + CliShell* shell = context; + cli_shell_line_set_about_to_exit(shell->components[CliShellComponentLine]); + furi_event_loop_stop(shell->event_loop); +} + +// ================== +// Internal functions +// ================== + +static int32_t cli_command_thread(void* context) { + CliCommandThreadData* thread_data = context; + if(!(thread_data->command->flags & CliCommandFlagDontAttachStdio)) + pipe_install_as_stdio(thread_data->pipe); + + thread_data->command->execute_callback( + thread_data->pipe, thread_data->args, thread_data->command->context); + + fflush(stdout); + return 0; +} + +void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) { + // split command into command and args + size_t space = furi_string_search_char(command, ' '); + if(space == FURI_STRING_FAILURE) space = furi_string_size(command); + FuriString* command_name = furi_string_alloc_set(command); + furi_string_left(command_name, space); + FuriString* args = furi_string_alloc_set(command); + furi_string_right(args, space + 1); + + PluginManager* plugin_manager = NULL; + Loader* loader = furi_record_open(RECORD_LOADER); + bool loader_locked = false; + CliRegistryCommand command_data; + + do { + // find handler + if(!cli_registry_get_command(cli_shell->registry, command_name, &command_data)) { + printf( + ANSI_FG_RED "could not find command `%s`, try `help`" ANSI_RESET, + furi_string_get_cstr(command_name)); + break; + } + + // load external command + if(command_data.flags & CliCommandFlagExternal) { + const CliCommandExternalConfig* ext_config = cli_shell->ext_config; + plugin_manager = plugin_manager_alloc( + ext_config->appid, CLI_PLUGIN_API_VERSION, firmware_api_interface); + FuriString* path = furi_string_alloc_printf( + "%s/%s%s.fal", + ext_config->search_directory, + ext_config->fal_prefix, + furi_string_get_cstr(command_name)); + uint32_t plugin_cnt_last = plugin_manager_get_count(plugin_manager); + PluginManagerError error = + plugin_manager_load_single(plugin_manager, furi_string_get_cstr(path)); + furi_string_free(path); + + if(error != PluginManagerErrorNone) { + printf(ANSI_FG_RED "failed to load external command" ANSI_RESET); + break; + } + + const CliCommandDescriptor* plugin = + plugin_manager_get_ep(plugin_manager, plugin_cnt_last); + furi_assert(plugin); + furi_check(furi_string_cmp_str(command_name, plugin->name) == 0); + command_data.execute_callback = plugin->execute_callback; + command_data.flags = plugin->flags | CliCommandFlagExternal; + command_data.stack_depth = plugin->stack_depth; + + // external commands have to run in an external thread + furi_check(!(command_data.flags & CliCommandFlagUseShellThread)); + } + + // lock loader + if(!(command_data.flags & CliCommandFlagParallelSafe)) { + loader_locked = loader_lock(loader); + if(!loader_locked) { + printf(ANSI_FG_RED + "this command cannot be run while an application is open" ANSI_RESET); + break; + } + } + + if(command_data.flags & CliCommandFlagUseShellThread) { + // run command in this thread + command_data.execute_callback(cli_shell->pipe, args, command_data.context); + } else { + // run command in separate thread + cli_shell_detach_pipe(cli_shell); + CliCommandThreadData thread_data = { + .command = &command_data, + .pipe = cli_shell->pipe, + .args = args, + }; + FuriThread* thread = furi_thread_alloc_ex( + furi_string_get_cstr(command_name), + command_data.stack_depth, + cli_command_thread, + &thread_data); + furi_thread_start(thread); + furi_thread_join(thread); + furi_thread_free(thread); + cli_shell_install_pipe(cli_shell); + } + } while(0); + + furi_string_free(command_name); + furi_string_free(args); + + // unlock loader + if(loader_locked) loader_unlock(loader); + furi_record_close(RECORD_LOADER); + + // unload external command + if(plugin_manager) plugin_manager_free(plugin_manager); +} + +const char* cli_shell_get_prompt(CliShell* cli_shell) { + return cli_shell->prompt; +} + +// ============== +// Event handlers +// ============== + +static void cli_shell_signal_storage_event(CliShell* cli_shell, CliShellStorageEvent event) { + furi_check(!(furi_event_flag_set(cli_shell->storage.event_flag, event) & FuriFlagError)); +} + +static void cli_shell_storage_event(const void* message, void* context) { + CliShell* cli_shell = context; + const StorageEvent* event = message; + + if(event->type == StorageEventTypeCardMount) { + cli_shell_signal_storage_event(cli_shell, CliShellStorageEventMount); + } else if(event->type == StorageEventTypeCardUnmount) { + cli_shell_signal_storage_event(cli_shell, CliShellStorageEventUnmount); + } +} + +static void cli_shell_storage_internal_event(FuriEventLoopObject* object, void* context) { + CliShell* cli_shell = context; + FuriEventFlag* event_flag = object; + CliShellStorageEvent event = + furi_event_flag_wait(event_flag, FuriFlagWaitAll, FuriFlagWaitAny, 0); + furi_check(!(event & FuriFlagError)); + + if(event & CliShellStorageEventUnmount) { + cli_registry_remove_external_commands(cli_shell->registry); + } else if(event & CliShellStorageEventMount) { + cli_registry_reload_external_commands(cli_shell->registry, cli_shell->ext_config); + } else { + furi_crash(); + } +} + +static void + cli_shell_process_parser_result(CliShell* cli_shell, CliAnsiParserResult parse_result) { + if(!parse_result.is_done) return; + CliKeyCombo key_combo = parse_result.result; + if(key_combo.key == CliKeyUnrecognized) return; + + for(size_t i = 0; i < CliShellComponentMAX; i++) { // -V1008 + CliShellKeyComboSet* set = component_key_combo_sets[i]; + void* component_context = cli_shell->components[i]; + + for(size_t j = 0; j < set->count; j++) { + if(set->records[j].combo.modifiers == key_combo.modifiers && + set->records[j].combo.key == key_combo.key) + if(set->records[j].action(key_combo, component_context)) return; + } + + if(set->fallback) + if(set->fallback(key_combo, component_context)) return; + } +} + +static void cli_shell_pipe_broken(PipeSide* pipe, void* context) { + // allow commands to be processed before we stop the shell + if(pipe_bytes_available(pipe)) return; + + CliShell* cli_shell = context; + furi_event_loop_stop(cli_shell->event_loop); +} + +static void cli_shell_data_available(PipeSide* pipe, void* context) { + UNUSED(pipe); + CliShell* cli_shell = context; + + furi_event_loop_timer_start(cli_shell->ansi_parsing_timer, furi_ms_to_ticks(ANSI_TIMEOUT_MS)); + + // process ANSI escape sequences + int c = getchar(); + furi_assert(c >= 0); + cli_shell_process_parser_result(cli_shell, cli_ansi_parser_feed(cli_shell->ansi_parser, c)); +} + +static void cli_shell_timer_expired(void* context) { + CliShell* cli_shell = context; + cli_shell_process_parser_result( + cli_shell, cli_ansi_parser_feed_timeout(cli_shell->ansi_parser)); +} + +// =========== +// Thread code +// =========== + +static void cli_shell_init(CliShell* shell) { + cli_registry_add_command( + shell->registry, + "help", + CliCommandFlagUseShellThread | CliCommandFlagParallelSafe, + cli_command_help, + shell); + cli_registry_add_command( + shell->registry, + "?", + CliCommandFlagUseShellThread | CliCommandFlagParallelSafe, + cli_command_help, + shell); + cli_registry_add_command( + shell->registry, + "exit", + CliCommandFlagUseShellThread | CliCommandFlagParallelSafe, + cli_command_exit, + shell); + + if(shell->ext_config) { + cli_registry_add_command( + shell->registry, + "reload_ext_cmds", + CliCommandFlagUseShellThread, + cli_command_reload_external, + shell); + cli_registry_reload_external_commands(shell->registry, shell->ext_config); + } + + shell->components[CliShellComponentLine] = cli_shell_line_alloc(shell); + shell->components[CliShellComponentCompletions] = cli_shell_completions_alloc( + shell->registry, shell, shell->components[CliShellComponentLine]); + + shell->ansi_parser = cli_ansi_parser_alloc(); + + shell->event_loop = furi_event_loop_alloc(); + shell->ansi_parsing_timer = furi_event_loop_timer_alloc( + shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, shell); + + shell->storage.event_flag = furi_event_flag_alloc(); + furi_event_loop_subscribe_event_flag( + shell->event_loop, + shell->storage.event_flag, + FuriEventLoopEventIn, + cli_shell_storage_internal_event, + shell); + shell->storage.storage = furi_record_open(RECORD_STORAGE); + shell->storage.subscription = furi_pubsub_subscribe( + storage_get_pubsub(shell->storage.storage), cli_shell_storage_event, shell); + + cli_shell_install_pipe(shell); +} + +static void cli_shell_deinit(CliShell* shell) { + furi_pubsub_unsubscribe( + storage_get_pubsub(shell->storage.storage), shell->storage.subscription); + furi_record_close(RECORD_STORAGE); + furi_event_loop_unsubscribe(shell->event_loop, shell->storage.event_flag); + furi_event_flag_free(shell->storage.event_flag); + + cli_shell_completions_free(shell->components[CliShellComponentCompletions]); + cli_shell_line_free(shell->components[CliShellComponentLine]); + + cli_shell_detach_pipe(shell); + furi_event_loop_timer_free(shell->ansi_parsing_timer); + furi_event_loop_free(shell->event_loop); + cli_ansi_parser_free(shell->ansi_parser); +} + +static int32_t cli_shell_thread(void* context) { + CliShell* shell = context; + + // Sometimes, the other side closes the pipe even before our thread is started. Although the + // rest of the code will eventually find this out if this check is removed, there's no point in + // wasting time. + if(pipe_state(shell->pipe) == PipeStateBroken) return 0; + + cli_shell_init(shell); + FURI_LOG_D(TAG, "Started"); + + shell->motd(shell->callback_context); + cli_shell_line_prompt(shell->components[CliShellComponentLine]); + + furi_event_loop_run(shell->event_loop); + + FURI_LOG_D(TAG, "Stopped"); + cli_shell_deinit(shell); + return 0; +} + +// ========== +// Public API +// ========== + +CliShell* cli_shell_alloc( + CliShellMotd motd, + void* context, + PipeSide* pipe, + CliRegistry* registry, + const CliCommandExternalConfig* ext_config) { + furi_check(motd); + furi_check(pipe); + furi_check(registry); + + CliShell* shell = malloc(sizeof(CliShell)); + *shell = (CliShell){ + .motd = motd, + .callback_context = context, + .pipe = pipe, + .registry = registry, + .ext_config = ext_config, + }; + + shell->thread = + furi_thread_alloc_ex("CliShell", CLI_SHELL_STACK_SIZE, cli_shell_thread, shell); + + return shell; +} + +void cli_shell_free(CliShell* shell) { + furi_check(shell); + furi_thread_free(shell->thread); + free(shell); +} + +void cli_shell_start(CliShell* shell) { + furi_check(shell); + furi_thread_start(shell->thread); +} + +void cli_shell_join(CliShell* shell) { + furi_check(shell); + furi_thread_join(shell->thread); +} + +void cli_shell_set_prompt(CliShell* shell, const char* prompt) { + furi_check(shell); + furi_check(furi_thread_get_state(shell->thread) == FuriThreadStateStopped); + shell->prompt = prompt; +} diff --git a/lib/toolbox/cli/shell/cli_shell.h b/lib/toolbox/cli/shell/cli_shell.h new file mode 100644 index 000000000..74f71273e --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include "../cli_registry.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CLI_SHELL_STACK_SIZE (4 * 1024U) + +typedef struct CliShell CliShell; + +/** + * Called from the shell thread to print the Message of the Day when the shell + * is started. + */ +typedef void (*CliShellMotd)(void* context); + +/** + * @brief Allocates a shell + * + * @param [in] motd Message of the Day callback + * @param [in] context Callback context + * @param [in] pipe Pipe side to be used by the shell + * @param [in] registry Command registry + * @param [in] ext_config External command configuration. See + * `CliCommandExternalConfig`. May be NULL if support for + * external commands is not required. + * + * @return Shell instance + */ +CliShell* cli_shell_alloc( + CliShellMotd motd, + void* context, + PipeSide* pipe, + CliRegistry* registry, + const CliCommandExternalConfig* ext_config); + +/** + * @brief Frees a shell + * + * @param [in] shell Shell instance + */ +void cli_shell_free(CliShell* shell); + +/** + * @brief Starts a shell + * + * The shell runs in a separate thread. This call is non-blocking. + * + * @param [in] shell Shell instance + */ +void cli_shell_start(CliShell* shell); + +/** + * @brief Joins the shell thread + * + * @warning This call is blocking. + * + * @param [in] shell Shell instance + */ +void cli_shell_join(CliShell* shell); + +/** + * @brief Sets optional text before prompt (`>:`) + * + * @param [in] shell Shell instance + */ +void cli_shell_set_prompt(CliShell* shell, const char* prompt); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/cli/shell/cli_shell_completions.c b/lib/toolbox/cli/shell/cli_shell_completions.c new file mode 100644 index 000000000..6b6634dbb --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell_completions.c @@ -0,0 +1,367 @@ +#include "cli_shell_completions.h" + +ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524 +#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions) + +struct CliShellCompletions { + CliRegistry* registry; + CliShell* shell; + CliShellLine* line; + CommandCompletions_t variants; + size_t selected; + bool is_displaying; +}; + +#define COMPLETION_COLUMNS 3 +#define COMPLETION_COLUMN_WIDTH "30" +#define COMPLETION_COLUMN_WIDTH_I 30 + +/** + * @brief Update for the completions menu + */ +typedef enum { + CliShellCompletionsActionOpen, + CliShellCompletionsActionClose, + CliShellCompletionsActionUp, + CliShellCompletionsActionDown, + CliShellCompletionsActionLeft, + CliShellCompletionsActionRight, + CliShellCompletionsActionSelect, + CliShellCompletionsActionSelectNoClose, +} CliShellCompletionsAction; + +typedef enum { + CliShellCompletionSegmentTypeCommand, + CliShellCompletionSegmentTypeArguments, +} CliShellCompletionSegmentType; + +typedef struct { + CliShellCompletionSegmentType type; + size_t start; + size_t length; +} CliShellCompletionSegment; + +// ========== +// Public API +// ========== + +CliShellCompletions* + cli_shell_completions_alloc(CliRegistry* registry, CliShell* shell, CliShellLine* line) { + CliShellCompletions* completions = malloc(sizeof(CliShellCompletions)); + + completions->registry = registry; + completions->shell = shell; + completions->line = line; + CommandCompletions_init(completions->variants); + + return completions; +} + +void cli_shell_completions_free(CliShellCompletions* completions) { + CommandCompletions_clear(completions->variants); + free(completions); +} + +// ======= +// Helpers +// ======= + +CliShellCompletionSegment cli_shell_completions_segment(CliShellCompletions* completions) { + furi_assert(completions); + CliShellCompletionSegment segment; + + FuriString* input = furi_string_alloc_set(cli_shell_line_get_editing(completions->line)); + furi_string_left(input, cli_shell_line_get_line_position(completions->line)); + + // find index of first non-space character + size_t first_non_space = 0; + while(1) { + size_t ret = furi_string_search_char(input, ' ', first_non_space); + if(ret == FURI_STRING_FAILURE) break; + if(ret - first_non_space > 1) break; + first_non_space++; + } + + size_t first_space_in_command = furi_string_search_char(input, ' ', first_non_space); + + if(first_space_in_command == FURI_STRING_FAILURE) { + segment.type = CliShellCompletionSegmentTypeCommand; + segment.start = first_non_space; + segment.length = furi_string_size(input) - first_non_space; + } else { + segment.type = CliShellCompletionSegmentTypeArguments; + segment.start = 0; + segment.length = 0; + // support removed, might reimplement in the future + } + + furi_string_free(input); + return segment; +} + +void cli_shell_completions_fill_variants(CliShellCompletions* completions) { + furi_assert(completions); + CommandCompletions_reset(completions->variants); + + CliShellCompletionSegment segment = cli_shell_completions_segment(completions); + FuriString* input = furi_string_alloc_set(cli_shell_line_get_editing(completions->line)); + furi_string_right(input, segment.start); + furi_string_left(input, segment.length); + + if(segment.type == CliShellCompletionSegmentTypeCommand) { + CliRegistry* registry = completions->registry; + cli_registry_lock(registry); + CliCommandDict_t* commands = cli_registry_get_commands(registry); + for + M_EACH(registered_command, *commands, CliCommandDict_t) { + FuriString* command_name = registered_command->key; + if(furi_string_start_with(command_name, input)) { + CommandCompletions_push_back(completions->variants, command_name); + } + } + cli_registry_unlock(registry); + + } else { + // support removed, might reimplement in the future + } + + furi_string_free(input); +} + +static size_t cli_shell_completions_rows_at_column(CliShellCompletions* completions, size_t x) { + size_t completions_size = CommandCompletions_size(completions->variants); + size_t n_full_rows = completions_size / COMPLETION_COLUMNS; + size_t n_cols_in_last_row = completions_size % COMPLETION_COLUMNS; + size_t n_rows_at_x = n_full_rows + ((x >= n_cols_in_last_row) ? 0 : 1); + return n_rows_at_x; +} + +void cli_shell_completions_render( + CliShellCompletions* completions, + CliShellCompletionsAction action) { + furi_assert(completions); + if(action == CliShellCompletionsActionOpen) furi_check(!completions->is_displaying); + if(action == CliShellCompletionsActionClose) furi_check(completions->is_displaying); + + char prompt[64]; + cli_shell_line_format_prompt(completions->line, prompt, sizeof(prompt)); + + if(action == CliShellCompletionsActionOpen) { + cli_shell_completions_fill_variants(completions); + completions->selected = 0; + + if(CommandCompletions_size(completions->variants) == 1) { + cli_shell_completions_render(completions, CliShellCompletionsActionSelectNoClose); + return; + } + + // show completions menu (full re-render) + printf("\n\r"); + size_t position = 0; + for + M_EACH(completion, completions->variants, CommandCompletions_t) { + if(position == completions->selected) printf(ANSI_INVERT); + printf("%-" COMPLETION_COLUMN_WIDTH "s", furi_string_get_cstr(*completion)); + if(position == completions->selected) printf(ANSI_RESET); + if((position % COMPLETION_COLUMNS == COMPLETION_COLUMNS - 1) && + position != CommandCompletions_size(completions->variants)) { + printf("\r\n"); + } + position++; + } + + if(!position) { + printf(ANSI_FG_RED "no completions" ANSI_RESET); + } + + size_t total_rows = (position / COMPLETION_COLUMNS) + 1; + printf( + ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) ANSI_CURSOR_UP_BY("%zu") + ANSI_CURSOR_HOR_POS("%zu"), + total_rows, + strlen(prompt) + cli_shell_line_get_line_position(completions->line) + 1); + + completions->is_displaying = true; + + } else if(action == CliShellCompletionsActionClose) { + // clear completions menu + printf( + ANSI_CURSOR_HOR_POS("%zu") ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) + ANSI_CURSOR_HOR_POS("%zu"), + strlen(prompt) + furi_string_size(cli_shell_line_get_selected(completions->line)) + 1, + strlen(prompt) + cli_shell_line_get_line_position(completions->line) + 1); + completions->is_displaying = false; + + } else if( + action == CliShellCompletionsActionUp || action == CliShellCompletionsActionDown || + action == CliShellCompletionsActionLeft || action == CliShellCompletionsActionRight) { + if(CommandCompletions_empty_p(completions->variants)) return; + + // move selection + size_t completions_size = CommandCompletions_size(completions->variants); + size_t old_selection = completions->selected; + int n_columns = (completions_size >= COMPLETION_COLUMNS) ? COMPLETION_COLUMNS : + completions_size; + int selection_unclamped = old_selection; + if(action == CliShellCompletionsActionLeft) { + selection_unclamped--; + } else if(action == CliShellCompletionsActionRight) { + selection_unclamped++; + } else { + int selection_x = old_selection % COMPLETION_COLUMNS; + int selection_y_unclamped = old_selection / COMPLETION_COLUMNS; + if(action == CliShellCompletionsActionUp) selection_y_unclamped--; + if(action == CliShellCompletionsActionDown) selection_y_unclamped++; + size_t selection_y = 0; + if(selection_y_unclamped < 0) { + selection_x = CLAMP_WRAPAROUND(selection_x - 1, n_columns - 1, 0); + selection_y = + cli_shell_completions_rows_at_column(completions, selection_x) - 1; // -V537 + } else if( + (size_t)selection_y_unclamped > + cli_shell_completions_rows_at_column(completions, selection_x) - 1) { + selection_x = CLAMP_WRAPAROUND(selection_x + 1, n_columns - 1, 0); + selection_y = 0; + } else { + selection_y = selection_y_unclamped; + } + selection_unclamped = (selection_y * COMPLETION_COLUMNS) + selection_x; + } + size_t new_selection = CLAMP_WRAPAROUND(selection_unclamped, (int)completions_size - 1, 0); + completions->selected = new_selection; + + if(new_selection != old_selection) { + // determine selection coordinates relative to top-left of suggestion menu + size_t old_x = (old_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I; + size_t old_y = old_selection / COMPLETION_COLUMNS; + size_t new_x = (new_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I; + size_t new_y = new_selection / COMPLETION_COLUMNS; + printf("\n\r"); + + // print old selection in normal colors + if(old_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), old_y); + printf(ANSI_CURSOR_HOR_POS("%zu"), old_x + 1); + printf( + "%-" COMPLETION_COLUMN_WIDTH "s", + furi_string_get_cstr( + *CommandCompletions_cget(completions->variants, old_selection))); + if(old_y) printf(ANSI_CURSOR_UP_BY("%zu"), old_y); + printf(ANSI_CURSOR_HOR_POS("1")); + + // print new selection in inverted colors + if(new_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), new_y); + printf(ANSI_CURSOR_HOR_POS("%zu"), new_x + 1); + printf( + ANSI_INVERT "%-" COMPLETION_COLUMN_WIDTH "s" ANSI_RESET, + furi_string_get_cstr( + *CommandCompletions_cget(completions->variants, new_selection))); + + // return cursor + printf(ANSI_CURSOR_UP_BY("%zu"), new_y + 1); + printf( + ANSI_CURSOR_HOR_POS("%zu"), + strlen(prompt) + furi_string_size(cli_shell_line_get_selected(completions->line)) + + 1); + } + + } else if(action == CliShellCompletionsActionSelectNoClose) { + if(!CommandCompletions_size(completions->variants)) return; + // insert selection into prompt + CliShellCompletionSegment segment = cli_shell_completions_segment(completions); + FuriString* input = cli_shell_line_get_selected(completions->line); + FuriString* completion = + *CommandCompletions_cget(completions->variants, completions->selected); + furi_string_replace_at( + input, segment.start, segment.length, furi_string_get_cstr(completion)); + printf( + ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), + strlen(prompt) + 1, + furi_string_get_cstr(input)); + + int position_change = (int)furi_string_size(completion) - (int)segment.length; + cli_shell_line_set_line_position( + completions->line, + MAX(0, (int)cli_shell_line_get_line_position(completions->line) + position_change)); + + } else if(action == CliShellCompletionsActionSelect) { + cli_shell_completions_render(completions, CliShellCompletionsActionSelectNoClose); + cli_shell_completions_render(completions, CliShellCompletionsActionClose); + + } else { + furi_crash(); + } + + fflush(stdout); +} + +// ============== +// Input handlers +// ============== + +static bool hide_if_open_and_continue_handling(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellCompletions* completions = context; + if(completions->is_displaying) + cli_shell_completions_render(completions, CliShellCompletionsActionClose); + return false; // process other home events +} + +static bool key_combo_cr(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellCompletions* completions = context; + if(!completions->is_displaying) return false; + cli_shell_completions_render(completions, CliShellCompletionsActionSelect); + return true; +} + +static bool key_combo_up_down(CliKeyCombo combo, void* context) { + CliShellCompletions* completions = context; + if(!completions->is_displaying) return false; + cli_shell_completions_render( + completions, + (combo.key == CliKeyUp) ? CliShellCompletionsActionUp : CliShellCompletionsActionDown); + return true; +} + +static bool key_combo_left_right(CliKeyCombo combo, void* context) { + CliShellCompletions* completions = context; + if(!completions->is_displaying) return false; + cli_shell_completions_render( + completions, + (combo.key == CliKeyLeft) ? CliShellCompletionsActionLeft : + CliShellCompletionsActionRight); + return true; +} + +static bool key_combo_tab(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellCompletions* completions = context; + cli_shell_completions_render( + completions, + completions->is_displaying ? CliShellCompletionsActionRight : + CliShellCompletionsActionOpen); + return true; +} + +static bool key_combo_esc(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellCompletions* completions = context; + if(!completions->is_displaying) return false; + cli_shell_completions_render(completions, CliShellCompletionsActionClose); + return true; +} + +CliShellKeyComboSet cli_shell_completions_key_combo_set = { + .fallback = hide_if_open_and_continue_handling, + .count = 7, + .records = + { + {{CliModKeyNo, CliKeyCR}, key_combo_cr}, + {{CliModKeyNo, CliKeyUp}, key_combo_up_down}, + {{CliModKeyNo, CliKeyDown}, key_combo_up_down}, + {{CliModKeyNo, CliKeyLeft}, key_combo_left_right}, + {{CliModKeyNo, CliKeyRight}, key_combo_left_right}, + {{CliModKeyNo, CliKeyTab}, key_combo_tab}, + {{CliModKeyNo, CliKeyEsc}, key_combo_esc}, + }, +}; diff --git a/lib/toolbox/cli/shell/cli_shell_completions.h b/lib/toolbox/cli/shell/cli_shell_completions.h new file mode 100644 index 000000000..d49a1982d --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell_completions.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include "cli_shell_i.h" +#include "cli_shell_line.h" +#include "../cli_registry.h" +#include "../cli_registry_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct CliShellCompletions CliShellCompletions; + +CliShellCompletions* + cli_shell_completions_alloc(CliRegistry* registry, CliShell* shell, CliShellLine* line); + +void cli_shell_completions_free(CliShellCompletions* completions); + +extern CliShellKeyComboSet cli_shell_completions_key_combo_set; + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/cli/shell/cli_shell_i.h b/lib/toolbox/cli/shell/cli_shell_i.h new file mode 100644 index 000000000..0b676b7de --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell_i.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../cli_ansi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct CliShell CliShell; + +/** + * @brief Key combo handler + * @return true if the event was handled, false otherwise + */ +typedef bool (*CliShellKeyComboAction)(CliKeyCombo combo, void* context); + +typedef struct { + CliKeyCombo combo; + CliShellKeyComboAction action; +} CliShellKeyComboRecord; + +typedef struct { + CliShellKeyComboAction fallback; + size_t count; + CliShellKeyComboRecord records[]; +} CliShellKeyComboSet; + +void cli_shell_execute_command(CliShell* cli_shell, FuriString* command); + +const char* cli_shell_get_prompt(CliShell* cli_shell); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/cli/shell/cli_shell_line.c b/lib/toolbox/cli/shell/cli_shell_line.c new file mode 100644 index 000000000..4826ba252 --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell_line.c @@ -0,0 +1,378 @@ +#include "cli_shell_line.h" + +#define HISTORY_DEPTH 10 + +struct CliShellLine { + size_t history_position; + size_t line_position; + FuriString* history[HISTORY_DEPTH]; + size_t history_entries; + CliShell* shell; + bool about_to_exit; +}; + +// ========== +// Public API +// ========== + +CliShellLine* cli_shell_line_alloc(CliShell* shell) { + CliShellLine* line = malloc(sizeof(CliShellLine)); + line->shell = shell; + + line->history[0] = furi_string_alloc(); + line->history_entries = 1; + + return line; +} + +void cli_shell_line_free(CliShellLine* line) { + for(size_t i = 0; i < line->history_entries; i++) + furi_string_free(line->history[i]); + + free(line); +} + +FuriString* cli_shell_line_get_selected(CliShellLine* line) { + return line->history[line->history_position]; +} + +FuriString* cli_shell_line_get_editing(CliShellLine* line) { + return line->history[0]; +} + +void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length) { + UNUSED(line); + const char* prompt = cli_shell_get_prompt(line->shell); + snprintf(buf, length - 1, "%s>: ", prompt ? prompt : ""); +} + +size_t cli_shell_line_prompt_length(CliShellLine* line) { + char buffer[128]; + cli_shell_line_format_prompt(line, buffer, sizeof(buffer)); + return strlen(buffer); +} + +void cli_shell_line_prompt(CliShellLine* line) { + char buffer[32]; + cli_shell_line_format_prompt(line, buffer, sizeof(buffer)); + printf("\r\n%s", buffer); + fflush(stdout); +} + +void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line) { + if(line->history_position > 0) { + FuriString* source = cli_shell_line_get_selected(line); + FuriString* destination = cli_shell_line_get_editing(line); + furi_string_set(destination, source); + line->history_position = 0; + } +} + +void cli_shell_line_set_about_to_exit(CliShellLine* line) { + line->about_to_exit = true; +} + +size_t cli_shell_line_get_line_position(CliShellLine* line) { + return line->line_position; +} + +void cli_shell_line_set_line_position(CliShellLine* line, size_t position) { + line->line_position = position; +} + +// ======= +// Helpers +// ======= + +typedef enum { + CliCharClassWord, + CliCharClassSpace, + CliCharClassOther, +} CliCharClass; + +typedef enum { + CliSkipDirectionLeft, + CliSkipDirectionRight, +} CliSkipDirection; + +CliCharClass cli_shell_line_char_class(char c) { + if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') { + return CliCharClassWord; + } else if(c == ' ') { + return CliCharClassSpace; + } else { + return CliCharClassOther; + } +} + +size_t + cli_shell_line_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) { + if(furi_string_size(string) == 0) return original_pos; + if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos; + if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string)) + return original_pos; + + int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0; + int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1; + int32_t position = original_pos; + CliCharClass start_class = + cli_shell_line_char_class(furi_string_get_char(string, position + look_offset)); + + while(true) { + position += increment; + if(position < 0) break; + if(position >= (int32_t)furi_string_size(string)) break; + if(cli_shell_line_char_class(furi_string_get_char(string, position + look_offset)) != + start_class) + break; + } + + return MAX(0, position); +} + +// ============== +// Input handlers +// ============== + +static bool cli_shell_line_input_ctrl_c(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // reset input + furi_string_reset(cli_shell_line_get_editing(line)); + line->line_position = 0; + line->history_position = 0; + printf("^C"); + cli_shell_line_prompt(line); + return true; +} + +static bool cli_shell_line_input_cr(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + + FuriString* command = cli_shell_line_get_selected(line); + furi_string_trim(command); + if(furi_string_empty(command)) { + cli_shell_line_prompt(line); + return true; + } + + FuriString* command_copy = furi_string_alloc_set(command); + + if(line->history_position == 0) { + for(size_t i = 1; i < line->history_entries; i++) { + if(furi_string_cmp(line->history[i], command) == 0) { + line->history_position = i; + command = cli_shell_line_get_selected(line); + furi_string_trim(command); + break; + } + } + } + + // move selected command to the front + if(line->history_position > 0) { + size_t pos = line->history_position; + size_t len = line->history_entries; + memmove( + &line->history[pos], &line->history[pos + 1], (len - pos - 1) * sizeof(FuriString*)); + furi_string_move(line->history[0], command); + line->history_entries--; + } + + // insert empty command + if(line->history_entries == HISTORY_DEPTH) { + furi_string_free(line->history[HISTORY_DEPTH - 1]); + line->history_entries--; + } + memmove(&line->history[1], &line->history[0], line->history_entries * sizeof(FuriString*)); + line->history[0] = furi_string_alloc(); + line->history_entries++; + line->line_position = 0; + line->history_position = 0; + + // execute command + printf("\r\n"); + cli_shell_execute_command(line->shell, command_copy); + furi_string_free(command_copy); + if(!line->about_to_exit) cli_shell_line_prompt(line); + return true; +} + +static bool cli_shell_line_input_up_down(CliKeyCombo combo, void* context) { + CliShellLine* line = context; + // go up and down in history + int increment = (combo.key == CliKeyUp) ? 1 : -1; + size_t new_pos = + CLAMP((int)line->history_position + increment, (int)line->history_entries - 1, 0); + + // print prompt with selected command + if(new_pos != line->history_position) { + char prompt[64]; + cli_shell_line_format_prompt(line, prompt, sizeof(prompt)); + line->history_position = new_pos; + FuriString* command = cli_shell_line_get_selected(line); + printf( + ANSI_CURSOR_HOR_POS("1") "%s%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), + prompt, + furi_string_get_cstr(command)); + fflush(stdout); + line->line_position = furi_string_size(command); + } + return true; +} + +static bool cli_shell_line_input_left_right(CliKeyCombo combo, void* context) { + CliShellLine* line = context; + // go left and right in the current line + FuriString* command = cli_shell_line_get_selected(line); + int increment = (combo.key == CliKeyRight) ? 1 : -1; + size_t new_pos = + CLAMP((int)line->line_position + increment, (int)furi_string_size(command), 0); + + // move cursor + if(new_pos != line->line_position) { + line->line_position = new_pos; + printf("%s", (increment == 1) ? ANSI_CURSOR_RIGHT_BY("1") : ANSI_CURSOR_LEFT_BY("1")); + fflush(stdout); + } + return true; +} + +static bool cli_shell_line_input_home(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // go to the start + line->line_position = 0; + printf(ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + 1); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_end(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // go to the end + line->line_position = furi_string_size(cli_shell_line_get_selected(line)); + printf( + ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + line->line_position + 1); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_bksp(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // erase one character + cli_shell_line_ensure_not_overwriting_history(line); + FuriString* editing_line = cli_shell_line_get_editing(line); + if(line->line_position == 0) { + putc(CliKeyBell, stdout); + fflush(stdout); + return true; + } + line->line_position--; + furi_string_replace_at(editing_line, line->line_position, 1, ""); + + // move cursor, print the rest of the line, restore cursor + printf( + ANSI_CURSOR_LEFT_BY("1") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), + furi_string_get_cstr(editing_line) + line->line_position); + size_t left_by = furi_string_size(editing_line) - line->line_position; + if(left_by) // apparently LEFT_BY("0") still shifts left by one ._ . + printf(ANSI_CURSOR_LEFT_BY("%zu"), left_by); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_ctrl_l(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // clear screen + FuriString* command = cli_shell_line_get_selected(line); + char prompt[64]; + cli_shell_line_format_prompt(line, prompt, sizeof(prompt)); + printf( + ANSI_ERASE_DISPLAY(ANSI_ERASE_ENTIRE) ANSI_ERASE_SCROLLBACK_BUFFER ANSI_CURSOR_POS( + "1", "1") "%s%s" ANSI_CURSOR_HOR_POS("%zu"), + prompt, + furi_string_get_cstr(command), + strlen(prompt) + line->line_position + 1 /* 1-based column indexing */); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_ctrl_left_right(CliKeyCombo combo, void* context) { + CliShellLine* line = context; + // skip run of similar chars to the left or right + FuriString* selected_line = cli_shell_line_get_selected(line); + CliSkipDirection direction = (combo.key == CliKeyLeft) ? CliSkipDirectionLeft : + CliSkipDirectionRight; + line->line_position = cli_shell_line_skip_run(selected_line, line->line_position, direction); + printf( + ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + line->line_position + 1); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_ctrl_bksp(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // delete run of similar chars to the left + cli_shell_line_ensure_not_overwriting_history(line); + FuriString* selected_line = cli_shell_line_get_selected(line); + size_t run_start = + cli_shell_line_skip_run(selected_line, line->line_position, CliSkipDirectionLeft); + furi_string_replace_at(selected_line, run_start, line->line_position - run_start, ""); + line->line_position = run_start; + printf( + ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END) + ANSI_CURSOR_HOR_POS("%zu"), + cli_shell_line_prompt_length(line) + line->line_position + 1, + furi_string_get_cstr(selected_line) + run_start, + cli_shell_line_prompt_length(line) + run_start + 1); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_normal(CliKeyCombo combo, void* context) { + CliShellLine* line = context; + if(combo.modifiers != CliModKeyNo) return false; + if(combo.key < CliKeySpace || combo.key >= CliKeyDEL) return false; + // insert character + cli_shell_line_ensure_not_overwriting_history(line); + FuriString* editing_line = cli_shell_line_get_editing(line); + if(line->line_position == furi_string_size(editing_line)) { + furi_string_push_back(editing_line, combo.key); + printf("%c", combo.key); + } else { + const char in_str[2] = {combo.key, 0}; + furi_string_replace_at(editing_line, line->line_position, 0, in_str); + printf(ANSI_INSERT_MODE_ENABLE "%c" ANSI_INSERT_MODE_DISABLE, combo.key); + } + fflush(stdout); + line->line_position++; + return true; +} + +CliShellKeyComboSet cli_shell_line_key_combo_set = { + .fallback = cli_shell_line_input_normal, + .count = 14, + .records = + { + {{CliModKeyNo, CliKeyETX}, cli_shell_line_input_ctrl_c}, + {{CliModKeyNo, CliKeyCR}, cli_shell_line_input_cr}, + {{CliModKeyNo, CliKeyUp}, cli_shell_line_input_up_down}, + {{CliModKeyNo, CliKeyDown}, cli_shell_line_input_up_down}, + {{CliModKeyNo, CliKeyLeft}, cli_shell_line_input_left_right}, + {{CliModKeyNo, CliKeyRight}, cli_shell_line_input_left_right}, + {{CliModKeyNo, CliKeyHome}, cli_shell_line_input_home}, + {{CliModKeyNo, CliKeyEnd}, cli_shell_line_input_end}, + {{CliModKeyNo, CliKeyBackspace}, cli_shell_line_input_bksp}, + {{CliModKeyNo, CliKeyDEL}, cli_shell_line_input_bksp}, + {{CliModKeyNo, CliKeyFF}, cli_shell_line_input_ctrl_l}, + {{CliModKeyCtrl, CliKeyLeft}, cli_shell_line_input_ctrl_left_right}, + {{CliModKeyCtrl, CliKeyRight}, cli_shell_line_input_ctrl_left_right}, + {{CliModKeyNo, CliKeyETB}, cli_shell_line_input_ctrl_bksp}, + }, +}; diff --git a/lib/toolbox/cli/shell/cli_shell_line.h b/lib/toolbox/cli/shell/cli_shell_line.h new file mode 100644 index 000000000..e40a12bd6 --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell_line.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "cli_shell_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct CliShellLine CliShellLine; + +CliShellLine* cli_shell_line_alloc(CliShell* shell); + +void cli_shell_line_free(CliShellLine* line); + +FuriString* cli_shell_line_get_selected(CliShellLine* line); + +FuriString* cli_shell_line_get_editing(CliShellLine* line); + +size_t cli_shell_line_prompt_length(CliShellLine* line); + +void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length); + +void cli_shell_line_prompt(CliShellLine* line); + +size_t cli_shell_line_get_line_position(CliShellLine* line); + +void cli_shell_line_set_line_position(CliShellLine* line, size_t position); + +/** + * @brief If a line from history has been selected, moves it into the active line + */ +void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line); + +void cli_shell_line_set_about_to_exit(CliShellLine* line); + +extern CliShellKeyComboSet cli_shell_line_key_combo_set; + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/pipe.c b/lib/toolbox/pipe.c index 9dc1d368e..59b2f63f4 100644 --- a/lib/toolbox/pipe.c +++ b/lib/toolbox/pipe.c @@ -1,6 +1,8 @@ #include "pipe.h" #include +#define PIPE_DEFAULT_STATE_CHECK_PERIOD furi_ms_to_ticks(100) + /** * Data shared between both sides. */ @@ -23,6 +25,7 @@ struct PipeSide { PipeSideDataArrivedCallback on_data_arrived; PipeSideSpaceFreedCallback on_space_freed; PipeSideBrokenCallback on_pipe_broken; + FuriWait state_check_period; }; PipeSideBundle pipe_alloc(size_t capacity, size_t trigger_level) { @@ -52,12 +55,14 @@ PipeSideBundle pipe_alloc_ex(PipeSideReceiveSettings alice, PipeSideReceiveSetti .shared = shared, .sending = alice_to_bob, .receiving = bob_to_alice, + .state_check_period = PIPE_DEFAULT_STATE_CHECK_PERIOD, }; *bobs_side = (PipeSide){ .role = PipeRoleBob, .shared = shared, .sending = bob_to_alice, .receiving = alice_to_bob, + .state_check_period = PIPE_DEFAULT_STATE_CHECK_PERIOD, }; return (PipeSideBundle){.alices_side = alices_side, .bobs_side = bobs_side}; @@ -96,36 +101,62 @@ void pipe_free(PipeSide* pipe) { } } -static void _pipe_stdout_cb(const char* data, size_t size, void* context) { +static void pipe_stdout_cb(const char* data, size_t size, void* context) { furi_assert(context); PipeSide* pipe = context; - while(size) { - size_t sent = pipe_send(pipe, data, size, FuriWaitForever); - data += sent; - size -= sent; - } + pipe_send(pipe, data, size); } -static size_t _pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { +static size_t pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { + UNUSED(timeout); furi_assert(context); PipeSide* pipe = context; - return pipe_receive(pipe, data, size, timeout); + return pipe_receive(pipe, data, size); } void pipe_install_as_stdio(PipeSide* pipe) { furi_check(pipe); - furi_thread_set_stdout_callback(_pipe_stdout_cb, pipe); - furi_thread_set_stdin_callback(_pipe_stdin_cb, pipe); + furi_thread_set_stdout_callback(pipe_stdout_cb, pipe); + furi_thread_set_stdin_callback(pipe_stdin_cb, pipe); } -size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout) { +void pipe_set_state_check_period(PipeSide* pipe, FuriWait check_period) { furi_check(pipe); - return furi_stream_buffer_receive(pipe->receiving, data, length, timeout); + pipe->state_check_period = check_period; } -size_t pipe_send(PipeSide* pipe, const void* data, size_t length, FuriWait timeout) { +size_t pipe_receive(PipeSide* pipe, void* data, size_t length) { furi_check(pipe); - return furi_stream_buffer_send(pipe->sending, data, length, timeout); + + size_t received = 0; + while(length) { + size_t received_this_time = + furi_stream_buffer_receive(pipe->receiving, data, length, pipe->state_check_period); + if(!received_this_time && pipe_state(pipe) == PipeStateBroken) break; + + received += received_this_time; + length -= received_this_time; + data += received_this_time; + } + + return received; +} + +size_t pipe_send(PipeSide* pipe, const void* data, size_t length) { + furi_check(pipe); + + size_t sent = 0; + while(length) { + size_t sent_this_time = + furi_stream_buffer_send(pipe->sending, data, length, pipe->state_check_period); + if(!sent_this_time && pipe_state(pipe) == PipeStateBroken) break; + + sent += sent_this_time; + length -= sent_this_time; + data += sent_this_time; + } + + return sent; } size_t pipe_bytes_available(PipeSide* pipe) { @@ -142,14 +173,14 @@ static void pipe_receiving_buffer_callback(FuriEventLoopObject* buffer, void* co UNUSED(buffer); PipeSide* pipe = context; furi_assert(pipe); - if(pipe->on_space_freed) pipe->on_data_arrived(pipe, pipe->callback_context); + if(pipe->on_data_arrived) pipe->on_data_arrived(pipe, pipe->callback_context); } static void pipe_sending_buffer_callback(FuriEventLoopObject* buffer, void* context) { UNUSED(buffer); PipeSide* pipe = context; furi_assert(pipe); - if(pipe->on_data_arrived) pipe->on_space_freed(pipe, pipe->callback_context); + if(pipe->on_space_freed) pipe->on_space_freed(pipe, pipe->callback_context); } static void pipe_semaphore_callback(FuriEventLoopObject* semaphore, void* context) { diff --git a/lib/toolbox/pipe.h b/lib/toolbox/pipe.h index df75f4c48..b071975f7 100644 --- a/lib/toolbox/pipe.h +++ b/lib/toolbox/pipe.h @@ -147,29 +147,49 @@ void pipe_free(PipeSide* pipe); */ void pipe_install_as_stdio(PipeSide* pipe); +/** + * @brief Sets the state check period for `send` and `receive` operations + * + * @note This value is set to 100 ms when the pipe is created + * + * `send` and `receive` will check the state of the pipe if exactly 0 bytes were + * sent or received during any given `check_period`. Read the documentation for + * `pipe_send` and `pipe_receive` for more info. + * + * @param [in] pipe Pipe side to set the check period of + * @param [in] check_period Period in ticks + */ +void pipe_set_state_check_period(PipeSide* pipe, FuriWait check_period); + /** * @brief Receives data from the pipe. * + * This function will try to receive all of the requested bytes from the pipe. + * If at some point during the operation the pipe becomes broken, this function + * will return prematurely, in which case the return value will be less than the + * requested `length`. + * * @param [in] pipe The pipe side to read data out of * @param [out] data The buffer to fill with data * @param length Maximum length of data to read - * @param timeout The timeout (in ticks) after which the read operation is - * interrupted * @returns The number of bytes actually written into the provided buffer */ -size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout); +size_t pipe_receive(PipeSide* pipe, void* data, size_t length); /** * @brief Sends data into the pipe. * + * This function will try to send all of the requested bytes to the pipe. + * If at some point during the operation the pipe becomes broken, this function + * will return prematurely, in which case the return value will be less than the + * requested `length`. + * * @param [in] pipe The pipe side to send data into * @param [out] data The buffer to get data from * @param length Maximum length of data to send - * @param timeout The timeout (in ticks) after which the write operation is - * interrupted * @returns The number of bytes actually read from the provided buffer */ -size_t pipe_send(PipeSide* pipe, const void* data, size_t length, FuriWait timeout); +size_t pipe_send(PipeSide* pipe, const void* data, size_t length); /** * @brief Determines how many bytes there are in the pipe available to be read. diff --git a/lib/toolbox/pretty_format.c b/lib/toolbox/pretty_format.c index f8319b69d..496738c4d 100644 --- a/lib/toolbox/pretty_format.c +++ b/lib/toolbox/pretty_format.c @@ -36,11 +36,17 @@ void pretty_format_bytes_hex_canonical( } const size_t begin_idx = i; - const size_t end_idx = MIN(i + num_places, data_size); + const size_t wrap_idx = i + num_places; + const size_t end_idx = MIN(wrap_idx, data_size); for(size_t j = begin_idx; j < end_idx; j++) { furi_string_cat_printf(result, "%02X ", data[j]); } + if(end_idx < wrap_idx) { + for(size_t j = end_idx; j < wrap_idx; j++) { + furi_string_cat_printf(result, " "); + } + } furi_string_push_back(result, '|'); diff --git a/lib/toolbox/protocols/protocol_dict.c b/lib/toolbox/protocols/protocol_dict.c index 5680be18d..5cc46d6eb 100644 --- a/lib/toolbox/protocols/protocol_dict.c +++ b/lib/toolbox/protocols/protocol_dict.c @@ -2,12 +2,12 @@ #include "protocol_dict.h" struct ProtocolDict { - const ProtocolBase** base; + const ProtocolBase* const* base; size_t count; void* data[]; }; -ProtocolDict* protocol_dict_alloc(const ProtocolBase** protocols, size_t count) { +ProtocolDict* protocol_dict_alloc(const ProtocolBase* const* protocols, size_t count) { furi_check(protocols); ProtocolDict* dict = malloc(sizeof(ProtocolDict) + (sizeof(void*) * count)); diff --git a/lib/toolbox/protocols/protocol_dict.h b/lib/toolbox/protocols/protocol_dict.h index 543b3ead2..a7e02a988 100644 --- a/lib/toolbox/protocols/protocol_dict.h +++ b/lib/toolbox/protocols/protocol_dict.h @@ -12,7 +12,7 @@ typedef int32_t ProtocolId; #define PROTOCOL_NO (-1) #define PROTOCOL_ALL_FEATURES (0xFFFFFFFF) -ProtocolDict* protocol_dict_alloc(const ProtocolBase** protocols, size_t protocol_count); +ProtocolDict* protocol_dict_alloc(const ProtocolBase* const* protocols, size_t protocol_count); void protocol_dict_free(ProtocolDict* dict); diff --git a/lib/toolbox/run_parallel.c b/lib/toolbox/run_parallel.c new file mode 100644 index 000000000..439949cf4 --- /dev/null +++ b/lib/toolbox/run_parallel.c @@ -0,0 +1,17 @@ +#include "run_parallel.h" + +#include + +static void run_parallel_thread_state(FuriThread* thread, FuriThreadState state, void* context) { + UNUSED(context); + + if(state == FuriThreadStateStopped) { + furi_thread_free(thread); + } +} + +void run_parallel(FuriThreadCallback callback, void* context, uint32_t stack_size) { + FuriThread* thread = furi_thread_alloc_ex(NULL, stack_size, callback, context); + furi_thread_set_state_callback(thread, run_parallel_thread_state); + furi_thread_start(thread); +} diff --git a/lib/toolbox/run_parallel.h b/lib/toolbox/run_parallel.h new file mode 100644 index 000000000..2722c54eb --- /dev/null +++ b/lib/toolbox/run_parallel.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +/** + * @brief Run function in thread, then automatically clean up thread. + * + * @param[in] callback pointer to a function to be executed in parallel + * @param[in] context pointer to a user-specified object (will be passed to the callback) + * @param[in] stack_size stack size in bytes + */ +void run_parallel(FuriThreadCallback callback, void* context, uint32_t stack_size); diff --git a/lib/toolbox/settings_helpers/submenu_based.c b/lib/toolbox/settings_helpers/submenu_based.c new file mode 100644 index 000000000..ce785dd73 --- /dev/null +++ b/lib/toolbox/settings_helpers/submenu_based.c @@ -0,0 +1,103 @@ +#include "submenu_based.h" +#include + +struct SubmenuSettingsHelper { + const SubmenuSettingsHelperDescriptor* descriptor; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + Submenu* submenu; + uint32_t submenu_view_id; + uint32_t main_scene_id; +}; + +SubmenuSettingsHelper* + submenu_settings_helpers_alloc(const SubmenuSettingsHelperDescriptor* descriptor) { + furi_check(descriptor); + SubmenuSettingsHelper* helper = malloc(sizeof(SubmenuSettingsHelper)); + helper->descriptor = descriptor; + return helper; +} + +void submenu_settings_helpers_assign_objects( + SubmenuSettingsHelper* helper, + ViewDispatcher* view_dispatcher, + SceneManager* scene_manager, + Submenu* submenu, + uint32_t submenu_view_id, + uint32_t main_scene_id) { + furi_check(helper); + furi_check(view_dispatcher); + furi_check(scene_manager); + furi_check(submenu); + helper->view_dispatcher = view_dispatcher; + helper->scene_manager = scene_manager; + helper->submenu = submenu; + helper->submenu_view_id = submenu_view_id; + helper->main_scene_id = main_scene_id; +} + +void submenu_settings_helpers_free(SubmenuSettingsHelper* helper) { + free(helper); +} + +bool submenu_settings_helpers_app_start(SubmenuSettingsHelper* helper, void* arg) { + furi_check(helper); + if(!arg) return false; + + const char* option = arg; + for(size_t i = 0; i < helper->descriptor->options_cnt; i++) { + if(strcmp(helper->descriptor->options[i].name, option) == 0) { + scene_manager_next_scene( + helper->scene_manager, helper->descriptor->options[i].scene_id); + return true; + } + } + + return false; +} + +static void + submenu_settings_helpers_callback(void* context, InputType input_type, uint32_t index) { + SubmenuSettingsHelper* helper = context; + if(input_type == InputTypeShort) { + view_dispatcher_send_custom_event(helper->view_dispatcher, index); + } else if(input_type == InputTypeLong) { + archive_favorites_handle_setting_pin_unpin( + helper->descriptor->app_name, helper->descriptor->options[index].name); + } +} + +void submenu_settings_helpers_scene_enter(SubmenuSettingsHelper* helper) { + furi_check(helper); + for(size_t i = 0; i < helper->descriptor->options_cnt; i++) { + submenu_add_item_ex( + helper->submenu, + helper->descriptor->options[i].name, + i, + submenu_settings_helpers_callback, + helper); + } + + submenu_set_selected_item( + helper->submenu, + scene_manager_get_scene_state(helper->scene_manager, helper->main_scene_id)); + view_dispatcher_switch_to_view(helper->view_dispatcher, helper->submenu_view_id); +} + +bool submenu_settings_helpers_scene_event(SubmenuSettingsHelper* helper, SceneManagerEvent event) { + furi_check(helper); + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_next_scene( + helper->scene_manager, helper->descriptor->options[event.event].scene_id); + scene_manager_set_scene_state(helper->scene_manager, helper->main_scene_id, event.event); + return true; + } + + return false; +} + +void submenu_settings_helpers_scene_exit(SubmenuSettingsHelper* helper) { + furi_check(helper); + submenu_reset(helper->submenu); +} diff --git a/lib/toolbox/settings_helpers/submenu_based.h b/lib/toolbox/settings_helpers/submenu_based.h new file mode 100644 index 000000000..6929cb04a --- /dev/null +++ b/lib/toolbox/settings_helpers/submenu_based.h @@ -0,0 +1,89 @@ +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*SubmenuSettingsHelpherCallback)(void* context, uint32_t index); + +typedef struct { + const char* name; + uint32_t scene_id; +} SubmenuSettingsHelperOption; + +typedef struct { + const char* app_name; + size_t options_cnt; + SubmenuSettingsHelperOption options[]; +} SubmenuSettingsHelperDescriptor; + +typedef struct SubmenuSettingsHelper SubmenuSettingsHelper; + +/** + * @brief Allocates a submenu-based settings helper + * @param descriptor settings descriptor + */ +SubmenuSettingsHelper* + submenu_settings_helpers_alloc(const SubmenuSettingsHelperDescriptor* descriptor); + +/** + * @brief Assigns dynamic objects to the submenu-based settings helper + * @param helper helper object + * @param view_dispatcher ViewDispatcher + * @param scene_manager SceneManager + * @param submenu Submenu + * @param submenu_view_id Submenu view id in the ViewDispatcher + * @param main_scene_id Main scene id in the SceneManager + */ +void submenu_settings_helpers_assign_objects( + SubmenuSettingsHelper* helper, + ViewDispatcher* view_dispatcher, + SceneManager* scene_manager, + Submenu* submenu, + uint32_t submenu_view_id, + uint32_t main_scene_id); + +/** + * @brief Frees a submenu-based settings helper + * @param helper helper object + */ +void submenu_settings_helpers_free(SubmenuSettingsHelper* helper); + +/** + * @brief App start callback for the submenu-based settings helper + * + * If an argument containing one of the options was provided, launches the + * corresponding scene. + * + * @param helper helper object + * @param arg app argument, may be NULL + * @returns true if a setting name was provided in the argument, false if normal + * app operation shall commence + */ +bool submenu_settings_helpers_app_start(SubmenuSettingsHelper* helper, void* arg); + +/** + * @brief Main scene enter callback for the submenu-based settings helper + * @param helper helper object + */ +void submenu_settings_helpers_scene_enter(SubmenuSettingsHelper* helper); + +/** + * @brief Main scene event callback for the submenu-based settings helper + * @param helper helper object + * @param event event data + * @returns true if the event was consumed, false otherwise + */ +bool submenu_settings_helpers_scene_event(SubmenuSettingsHelper* helper, SceneManagerEvent event); + +/** + * @brief Main scene exit callback for the submenu-based settings helper + * @param helper helper object + */ +void submenu_settings_helpers_scene_exit(SubmenuSettingsHelper* helper); + +#ifdef __cplusplus +} +#endif diff --git a/lib/u8g2/u8g2.h b/lib/u8g2/u8g2.h index c37f3b931..100b56d57 100644 --- a/lib/u8g2/u8g2.h +++ b/lib/u8g2/u8g2.h @@ -3548,6 +3548,7 @@ void u8g2_Setup_a2printer_384x240_f( void u8g2_SendBuffer(u8g2_t* u8g2); void u8g2_ClearBuffer(u8g2_t* u8g2); +void u8g2_FillBuffer(u8g2_t* u8g2); void u8g2_SetBufferCurrTileRow(u8g2_t* u8g2, uint8_t row) U8G2_NOINLINE; diff --git a/lib/u8g2/u8g2_buffer.c b/lib/u8g2/u8g2_buffer.c index 45855bd5d..cd4e67972 100644 --- a/lib/u8g2/u8g2_buffer.c +++ b/lib/u8g2/u8g2_buffer.c @@ -37,6 +37,7 @@ #include /*============================================*/ + void u8g2_ClearBuffer(u8g2_t* u8g2) { size_t cnt; cnt = u8g2_GetU8x8(u8g2)->display_info->tile_width; diff --git a/lib/u8g2/u8g2_glue.c b/lib/u8g2/u8g2_glue.c index b3e74c229..57d73b22e 100644 --- a/lib/u8g2/u8g2_glue.c +++ b/lib/u8g2/u8g2_glue.c @@ -280,3 +280,17 @@ void u8g2_Setup_st756x_flipper( buf = u8g2_m_16_8_f(&tile_buf_height); u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation); } + +void u8x8_d_st756x_set_inversion(u8x8_t* u8x8, bool arg) { + // if arg is true - set last bit to 1 (inversion ON) + if(arg) { + u8x8_cad_StartTransfer(u8x8); + u8x8_cad_SendCmd(u8x8, ST756X_CMD_INVERSE_DISPLAY | 0b01); + u8x8_cad_EndTransfer(u8x8); + // else use standart command with 0 in last bit + } else { + u8x8_cad_StartTransfer(u8x8); + u8x8_cad_SendCmd(u8x8, ST756X_CMD_INVERSE_DISPLAY); + u8x8_cad_EndTransfer(u8x8); + } +} diff --git a/lib/u8g2/u8g2_glue.h b/lib/u8g2/u8g2_glue.h index af236279e..858e8ec7f 100644 --- a/lib/u8g2/u8g2_glue.h +++ b/lib/u8g2/u8g2_glue.h @@ -16,3 +16,5 @@ void u8g2_Setup_st756x_flipper( void u8x8_d_st756x_init(u8x8_t* u8x8, uint8_t contrast, uint8_t regulation_ratio, bool bias); void u8x8_d_st756x_set_contrast(u8x8_t* u8x8, int8_t contrast_offset); + +void u8x8_d_st756x_set_inversion(u8x8_t* u8x8, bool arg); diff --git a/scripts/debug/flipperapps.py b/scripts/debug/flipperapps.py index 81aa43c34..6d2d4c0a9 100644 --- a/scripts/debug/flipperapps.py +++ b/scripts/debug/flipperapps.py @@ -7,6 +7,33 @@ import zlib import gdb +class bcolors: + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + + +LOG_PREFIX = "[FURI]" + + +def error(line): + print(f"{bcolors.FAIL}{LOG_PREFIX} {line}{bcolors.ENDC}") + + +def warning(line): + print(f"{bcolors.WARNING}{LOG_PREFIX} {line}{bcolors.ENDC}") + + +def info(line): + print(f"{bcolors.OKGREEN}{LOG_PREFIX} {line}{bcolors.ENDC}") + + def get_file_crc32(filename): with open(filename, "rb") as f: return zlib.crc32(f.read()) @@ -39,12 +66,12 @@ class AppState: def is_debug_available(self) -> bool: have_debug_info = bool(self.debug_link_elf and self.debug_link_crc) if not have_debug_info: - print("No debug info available for this app") + warning("No debug info available for this app") return False debug_elf_path = self.get_original_elf_path() debug_elf_crc32 = get_file_crc32(debug_elf_path) if self.debug_link_crc != debug_elf_crc32: - print( + warning( f"Debug info ({debug_elf_path}) CRC mismatch: {self.debug_link_crc:08x} != {debug_elf_crc32:08x}, rebuild app" ) return False @@ -52,7 +79,7 @@ class AppState: def get_gdb_load_command(self) -> str: load_path = self.get_original_elf_path() - print(f"Loading debug information from {load_path}") + info(f"Loading debug information from {load_path}") load_command = ( f"add-symbol-file -readnow {load_path} 0x{self.text_address:08x} " ) @@ -121,12 +148,12 @@ class SetFapDebugElfRoot(gdb.Command): AppState.DEBUG_ELF_ROOT = arg try: global helper - print(f"Set '{arg}' as debug info lookup path for Flipper external apps") + info(f"Set '{arg}' as debug info lookup path for Flipper external apps") helper.attach_to_fw() gdb.events.stop.connect(helper.handle_stop) gdb.events.gdb_exiting.connect(helper.handle_exit) except gdb.error as e: - print(f"Support for Flipper external apps debug is not available: {e}") + error(f"Support for Flipper external apps debug is not available: {e}") class FlipperAppStateHelper: @@ -148,13 +175,29 @@ class FlipperAppStateHelper: gdb.execute(command) return True except gdb.error as e: - print(f"Failed to execute GDB command '{command}': {e}") + error(f"Failed to execute GDB command '{command}': {e}") return False + def _get_crash_message(self): + message = self.app_check_message.value() + if message == 1: + return "furi_assert failed" + elif message == 2: + return "furi_check failed" + else: + return message + def _sync_apps(self) -> None: + crash_message = self._get_crash_message() + if crash_message: + crash_message = f"! System crashed: {crash_message} !" + error("!" * len(crash_message)) + error(crash_message) + error("!" * len(crash_message)) + self.set_debug_mode(True) if not (app_list := self.app_list_ptr.value()): - print("Reset app loader state") + info("Reset app loader state") for app in self._current_apps: self._exec_gdb_command(app.get_gdb_unload_command()) self._current_apps = [] @@ -167,22 +210,23 @@ class FlipperAppStateHelper: for app in self._current_apps.copy(): if app.entry_address not in loaded_apps: - print(f"Application {app.name} is no longer loaded") + warning(f"Application {app.name} is no longer loaded") if not self._exec_gdb_command(app.get_gdb_unload_command()): - print(f"Failed to unload debug info for {app.name}") + error(f"Failed to unload debug info for {app.name}") self._current_apps.remove(app) for entry_point, app in loaded_apps.items(): if entry_point not in set(app.entry_address for app in self._current_apps): new_app_state = AppState.from_gdb(app) - print(f"New application loaded. Adding debug info") + warning(f"New application loaded. Adding debug info") if self._exec_gdb_command(new_app_state.get_gdb_load_command()): self._current_apps.append(new_app_state) else: - print(f"Failed to load debug info for {new_app_state}") + error(f"Failed to load debug info for {new_app_state}") def attach_to_fw(self) -> None: - print("Attaching to Flipper firmware") + info("Attaching to Flipper firmware") + self.app_check_message = gdb.lookup_global_symbol("__furi_check_message") self.app_list_ptr = gdb.lookup_global_symbol( "flipper_application_loaded_app_list" ) @@ -200,10 +244,10 @@ class FlipperAppStateHelper: try: gdb.execute(f"set variable furi_hal_debug_gdb_session_active = {int(mode)}") except gdb.error as e: - print(f"Failed to set debug mode: {e}") + error(f"Failed to set debug mode: {e}") # Init additional 'fap-set-debug-elf-root' command and set up hooks SetFapDebugElfRoot() helper = FlipperAppStateHelper() -print("Support for Flipper external apps debug is loaded") +info("Support for Flipper external apps debug is loaded") diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 9b3b53b04..c46dffdbe 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -158,7 +158,7 @@ class AppManager: f"App {kw.get('appid')} cannot have fal_embedded set" ) - if apptype in AppBuildset.dist_app_types: + if apptype in AppBuildset.DIST_APP_TYPES: # For distributing .fap's resources, there's "fap_file_assets" for app_property in ("resources",): if kw.get(app_property): @@ -261,14 +261,12 @@ class AppBuildset: FlipperAppType.DEBUG: True, FlipperAppType.MENUEXTERNAL: False, } - - @classmethod - @property - def dist_app_types(cls): - """Applications that are installed on SD card""" - return list( - entry[0] for entry in cls.EXTERNAL_APP_TYPES_MAP.items() if entry[1] - ) + DIST_APP_TYPES = list( + # Applications that are installed on SD card + entry[0] + for entry in EXTERNAL_APP_TYPES_MAP.items() + if entry[1] + ) @staticmethod def print_writer(message): diff --git a/scripts/fbt/sdk/collector.py b/scripts/fbt/sdk/collector.py index 5615f105e..14653eb44 100644 --- a/scripts/fbt/sdk/collector.py +++ b/scripts/fbt/sdk/collector.py @@ -113,7 +113,7 @@ def stringify_descr(type_descr): # Hack if isinstance(type_descr.ptr_to, FunctionType): return stringify_descr(type_descr.ptr_to) - return f"{stringify_descr(type_descr.ptr_to)}*" + return f"{stringify_descr(type_descr.ptr_to)}*{' const' if type_descr.const else ''}" elif isinstance(type_descr, Type): return ( f"{'const ' if type_descr.const else ''}" diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index 3a17a79a3..c48e4ba80 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -150,11 +150,22 @@ def DumpApplicationConfig(target, source, env): print(fg.boldgreen("Firmware modules configuration:")) for apptype in FlipperAppType: app_sublist = env["APPBUILD"].get_apps_of_type(apptype) - if app_sublist: + # Print a warning if any apps in the list have same .order value + unique_order_values = set(app.order for app in app_sublist) + if len(app_sublist) != len(unique_order_values) and max(unique_order_values): + print( + fg.red(f"{apptype.value}: ") + + fg.yellow( + "Duplicate .order values in group:\n\t" + + ", ".join(f"{app.appid} ({app.order})" for app in app_sublist) + ) + ) + elif app_sublist: print( fg.green(f"{apptype.value}:\n\t"), ", ".join(app.appid for app in app_sublist), ) + if incompatible_ext_apps := env["APPBUILD"].get_incompatible_ext_apps(): print( fg.blue("Incompatible apps (skipped):\n\t"), diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 40af5cebc..0182cf45f 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -109,6 +109,8 @@ class FlipperStorage: def start(self): self.port.open() + time.sleep(0.5) + self.read.until(self.CLI_PROMPT) self.port.reset_input_buffer() # Send a command with a known syntax to make sure the buffer is flushed self.send("device_info\r") diff --git a/scripts/flipper/utils/cdc.py b/scripts/flipper/utils/cdc.py index ee1125f77..00b20d6fb 100644 --- a/scripts/flipper/utils/cdc.py +++ b/scripts/flipper/utils/cdc.py @@ -1,3 +1,4 @@ +import os import serial.tools.list_ports as list_ports @@ -15,3 +16,8 @@ def resolve_port(logger, portname: str = "auto"): logger.error("Failed to find connected Flipper") elif len(flippers) > 1: logger.error("More than one Flipper is attached") + env_path = os.environ.get("FLIPPER_PATH") + if env_path: + if os.path.exists(env_path): + logger.info(f"Using FLIPPER_PATH from environment: {env_path}") + return env_path diff --git a/scripts/power.py b/scripts/power.py index 50bb2d4f7..b13e63bd1 100755 --- a/scripts/power.py +++ b/scripts/power.py @@ -3,6 +3,8 @@ import time from typing import Optional +from serial.serialutil import SerialException + from flipper.app import App from flipper.storage import FlipperStorage from flipper.utils.cdc import resolve_port @@ -32,11 +34,11 @@ class Main(App): def _get_flipper(self, retry_count: Optional[int] = 1): port = None - self.logger.info(f"Attempting to find flipper with {retry_count} attempts.") - for i in range(retry_count): time.sleep(1) - self.logger.info(f"Attempting to find flipper #{i}.") + self.logger.info( + f"Attempting to find flipper (Attempt {i + 1}/{retry_count})." + ) if port := resolve_port(self.logger, self.args.port): self.logger.info(f"Found flipper at {port}") @@ -47,8 +49,16 @@ class Main(App): return None flipper = FlipperStorage(port) - flipper.start() - return flipper + for i in range(retry_count): + try: + flipper.start() + self.logger.info("Flipper successfully started.") + return flipper + except IOError as e: + self.logger.info( + f"Failed to start flipper (Attempt {i + 1}/{retry_count}): {e}" + ) + time.sleep(1) def power_off(self): if not (flipper := self._get_flipper(retry_count=10)): diff --git a/scripts/serial_cli_perf.py b/scripts/serial_cli_perf.py new file mode 100644 index 000000000..3d612e6de --- /dev/null +++ b/scripts/serial_cli_perf.py @@ -0,0 +1,71 @@ +import argparse +import logging +from serial import Serial +from random import randint +from time import time + +from flipper.utils.cdc import resolve_port + + +def main(): + logger = logging.getLogger() + parser = argparse.ArgumentParser() + parser.add_argument("-p", "--port", help="CDC Port", default="auto") + parser.add_argument( + "-l", "--length", type=int, help="Number of bytes to send", default=1024**2 + ) + args = parser.parse_args() + + if not (port := resolve_port(logger, args.port)): + logger.error("Is Flipper connected via USB and not in DFU mode?") + return 1 + port = Serial(port, 230400) + port.timeout = 2 + + port.read_until(b">: ") + port.write(b"echo\r") + port.read_until(b">: ") + + print(f"Transferring {args.length} bytes. Hang tight...") + + start_time = time() + + bytes_to_send = args.length + block_size = 1024 + success = True + while bytes_to_send: + actual_size = min(block_size, bytes_to_send) + # can't use 0x03 because that's ASCII ETX, or Ctrl+C + # block = bytes([randint(4, 255) for _ in range(actual_size)]) + block = bytes([4 + (i // 64) for i in range(actual_size)]) + + port.write(block) + return_block = port.read(actual_size) + + if return_block != block: + with open("block.bin", "wb") as f: + f.write(block) + with open("return_block.bin", "wb") as f: + f.write(return_block) + + logger.error( + "Incorrect block received. Saved to `block.bin' and `return_block.bin'." + ) + logger.error(f"{bytes_to_send} bytes left. Aborting.") + success = False + break + + bytes_to_send -= actual_size + + end_time = time() + delta = end_time - start_time + speed = args.length / delta + if success: + print(f"Speed: {speed/1024:.2f} KiB/s") + + port.write(b"\x03") # Ctrl+C + port.close() + + +if __name__ == "__main__": + main() diff --git a/scripts/testops.py b/scripts/testops.py index 3100a9b7f..119453448 100644 --- a/scripts/testops.py +++ b/scripts/testops.py @@ -4,6 +4,8 @@ import time from datetime import datetime from typing import Optional +from serial.serialutil import SerialException + from flipper.app import App from flipper.storage import FlipperStorage from flipper.utils.cdc import resolve_port @@ -34,25 +36,34 @@ class Main(App): def _get_flipper(self, retry_count: Optional[int] = 1): port = None - self.logger.info(f"Attempting to find flipper with {retry_count} attempts.") - for i in range(retry_count): - self.logger.info(f"Attempt to find flipper #{i}.") + time.sleep(1) + self.logger.info( + f"Attempting to find flipper (Attempt {i + 1}/{retry_count})." + ) if port := resolve_port(self.logger, self.args.port): self.logger.info(f"Found flipper at {port}") - time.sleep(1) break - time.sleep(1) - if not port: - self.logger.info(f"Failed to find flipper {port}") + self.logger.info(f"Failed to find flipper") return None flipper = FlipperStorage(port) - flipper.start() - return flipper + for i in range(retry_count): + try: + flipper.start() + self.logger.info("Flipper successfully started.") + return flipper + except IOError as e: + self.logger.info( + f"Failed to start flipper (Attempt {i + 1}/{retry_count}): {e}" + ) + time.sleep(1) + + self.logger.error("Flipper failed to start after all retries.") + return None def await_flipper(self): if not (flipper := self._get_flipper(retry_count=self.args.timeout)): diff --git a/scripts/ufbt/project_template/.vscode/settings.json b/scripts/ufbt/project_template/.vscode/settings.json index d304752a9..b2ee0ca3d 100644 --- a/scripts/ufbt/project_template/.vscode/settings.json +++ b/scripts/ufbt/project_template/.vscode/settings.json @@ -19,6 +19,7 @@ "[python]": { "editor.defaultFormatter": "ms-python.black-formatter" }, + "clangd.checkUpdates": false, "clangd.path": "@UFBT_TOOLCHAIN_CLANGD@", "clangd.arguments": [ "--query-driver=**/arm-none-eabi-*", diff --git a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml index 143847c4a..613590d47 100644 --- a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml +++ b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: with: sdk-channel: ${{ matrix.sdk-channel }} - name: Upload app artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: # See ufbt action docs for other output variables name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }} diff --git a/scripts/update.py b/scripts/update.py index 2b6620744..1c877c23e 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -34,7 +34,7 @@ class Main(App): FLASH_BASE = 0x8000000 FLASH_PAGE_SIZE = 4 * 1024 - MIN_GAP_PAGES = 2 + MIN_GAP_PAGES = 0 # Update stage file larger than that is not loadable without fix # https://github.com/flipperdevices/flipperzero-firmware/pull/3676 diff --git a/site_scons/cc.scons b/site_scons/cc.scons index c5d99b896..9960d350c 100644 --- a/site_scons/cc.scons +++ b/site_scons/cc.scons @@ -31,6 +31,7 @@ ENV.AppendUnique( "-Wundef", "-fdata-sections", "-ffunction-sections", + "-Wa,-gdwarf-sections", "-fsingle-precision-constant", "-fno-math-errno", # Generates .su files with stack usage information diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 6ce1736d0..3128fd412 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,79.2,, +Version,+,86.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -57,6 +57,7 @@ Header,+,lib/flipper_application/plugins/plugin_manager.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, Header,+,lib/flipper_format/flipper_format_stream.h,, +Header,+,lib/ieee754_parse_wrap/wrappers.h,, Header,+,lib/libusb_stm32/inc/hid_usage_button.h,, Header,+,lib/libusb_stm32/inc/hid_usage_consumer.h,, Header,+,lib/libusb_stm32/inc/hid_usage_desktop.h,, @@ -84,6 +85,7 @@ Header,+,lib/libusb_stm32/inc/usb_hid.h,, Header,+,lib/libusb_stm32/inc/usb_std.h,, Header,+,lib/libusb_stm32/inc/usb_tmc.h,, Header,+,lib/libusb_stm32/inc/usbd_core.h,, +Header,+,lib/mbedtls/include/mbedtls/aes.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, Header,+,lib/mbedtls/include/mbedtls/ecdh.h,, Header,+,lib/mbedtls/include/mbedtls/ecdsa.h,, @@ -150,6 +152,10 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/bit_buffer.h,, +Header,+,lib/toolbox/cli/cli_ansi.h,, +Header,+,lib/toolbox/cli/cli_command.h,, +Header,+,lib/toolbox/cli/cli_registry.h,, +Header,+,lib/toolbox/cli/shell/cli_shell.h,, Header,+,lib/toolbox/compress.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, @@ -381,6 +387,8 @@ Function,+,__wrap_putc,int,"int, FILE*" Function,+,__wrap_putchar,int,int Function,+,__wrap_puts,int,const char* Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_strtod,double,"const char*, char**" +Function,+,__wrap_strtof,float,"const char*, char**" Function,+,__wrap_ungetc,int,"int, FILE*" Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." @@ -774,18 +782,26 @@ Function,-,ceill,long double,long double Function,-,cfree,void,void* Function,-,clearerr,void,FILE* Function,-,clearerr_unlocked,void,FILE* -Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*" -Function,+,cli_cmd_interrupt_received,_Bool,Cli* -Function,+,cli_delete_command,void,"Cli*, const char*" -Function,+,cli_getc,char,Cli* -Function,+,cli_is_connected,_Bool,Cli* -Function,+,cli_nl,void,Cli* +Function,+,cli_ansi_parser_alloc,CliAnsiParser*, +Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char" +Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser* +Function,+,cli_ansi_parser_free,void,CliAnsiParser* +Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide* Function,+,cli_print_usage,void,"const char*, const char*, const char*" -Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t" -Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" -Function,+,cli_session_close,void,Cli* -Function,+,cli_session_open,void,"Cli*, void*" -Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" +Function,+,cli_registry_add_command,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*" +Function,+,cli_registry_add_command_ex,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*, size_t" +Function,+,cli_registry_alloc,CliRegistry*, +Function,+,cli_registry_delete_command,void,"CliRegistry*, const char*" +Function,+,cli_registry_free,void,CliRegistry* +Function,+,cli_registry_reload_external_commands,void,"CliRegistry*, const CliCommandExternalConfig*" +Function,+,cli_registry_remove_external_commands,void,CliRegistry* +Function,+,cli_shell_alloc,CliShell*,"CliShellMotd, void*, PipeSide*, CliRegistry*, const CliCommandExternalConfig*" +Function,+,cli_shell_free,void,CliShell* +Function,+,cli_shell_join,void,CliShell* +Function,+,cli_shell_set_prompt,void,"CliShell*, const char*" +Function,+,cli_shell_start,void,CliShell* +Function,+,cli_vcp_disable,void,CliVcp* +Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* @@ -1037,6 +1053,7 @@ Function,+,flipper_format_read_int32,_Bool,"FlipperFormat*, const char*, int32_t Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, FuriString*" Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32_t*, const uint16_t" Function,+,flipper_format_rewind,_Bool,FlipperFormat* +Function,+,flipper_format_seek,_Bool,"FlipperFormat*, int32_t, FlipperFormatOffset" Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat* Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool" Function,+,flipper_format_stream_delete_key_and_write,_Bool,"Stream*, FlipperStreamWriteData*, _Bool" @@ -1045,6 +1062,7 @@ Function,+,flipper_format_stream_read_value_line,_Bool,"Stream*, const char*, Fl Function,+,flipper_format_stream_write_comment_cstr,_Bool,"Stream*, const char*" Function,+,flipper_format_stream_write_value_line,_Bool,"Stream*, FlipperStreamWriteData*" Function,+,flipper_format_string_alloc,FlipperFormat*, +Function,+,flipper_format_tell,size_t,FlipperFormat* Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" @@ -1132,6 +1150,7 @@ Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMes Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_stream_buffer,void,"FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" +Function,+,furi_event_loop_subscribe_thread_flags,void,"FuriEventLoop*, FuriEventLoopThreadFlagsCallback, void*" Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*" Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer* @@ -1142,6 +1161,7 @@ Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer* Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t" Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer* Function,+,furi_event_loop_unsubscribe,void,"FuriEventLoop*, FuriEventLoopObject*" +Function,+,furi_event_loop_unsubscribe_thread_flags,void,FuriEventLoop* Function,+,furi_get_tick,uint32_t, Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* @@ -1284,23 +1304,23 @@ Function,+,furi_hal_hid_u2f_get_request,uint32_t,uint8_t* Function,+,furi_hal_hid_u2f_is_connected,_Bool, Function,+,furi_hal_hid_u2f_send_response,void,"uint8_t*, uint8_t" Function,+,furi_hal_hid_u2f_set_callback,void,"HidU2fCallback, void*" -Function,+,furi_hal_i2c_acquire,void,FuriHalI2cBusHandle* +Function,+,furi_hal_i2c_acquire,void,const FuriHalI2cBusHandle* Function,-,furi_hal_i2c_deinit_early,void, Function,-,furi_hal_i2c_init,void, Function,-,furi_hal_i2c_init_early,void, -Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" -Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" -Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" -Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* -Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_rx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" -Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_tx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" -Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" -Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" +Function,+,furi_hal_i2c_is_device_ready,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_read_reg_16,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" +Function,+,furi_hal_i2c_read_reg_8,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" +Function,+,furi_hal_i2c_release,void,const FuriHalI2cBusHandle* +Function,+,furi_hal_i2c_rx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_rx_ext,_Bool,"const FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx_ext,_Bool,"const FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_16,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_8,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" Function,+,furi_hal_info_get,void,"PropertyValueCallback, char, void*" Function,+,furi_hal_info_get_api_version,void,"uint16_t*, uint16_t*" Function,-,furi_hal_init,void, @@ -1434,6 +1454,7 @@ Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_configure_framing,void,"FuriHalSerialHandle*, FuriHalSerialDataBits, FuriHalSerialParity, FuriHalSerialStopBits" Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId Function,+,furi_hal_serial_control_deinit,void, Function,+,furi_hal_serial_control_init,void, @@ -1453,7 +1474,6 @@ Function,+,furi_hal_serial_get_gpio_pin,const GpioPin*,"FuriHalSerialHandle*, Fu Function,+,furi_hal_serial_init,void,"FuriHalSerialHandle*, uint32_t" Function,+,furi_hal_serial_is_baud_rate_supported,_Bool,"FuriHalSerialHandle*, uint32_t" Function,+,furi_hal_serial_resume,void,FuriHalSerialHandle* -Function,+,furi_hal_serial_send_break,void,FuriHalSerialHandle* Function,+,furi_hal_serial_set_br,void,"FuriHalSerialHandle*, uint32_t" Function,+,furi_hal_serial_suspend,void,FuriHalSerialHandle* Function,+,furi_hal_serial_tx,void,"FuriHalSerialHandle*, const uint8_t*, size_t" @@ -1466,20 +1486,20 @@ Function,+,furi_hal_speaker_release,void, Function,+,furi_hal_speaker_set_volume,void,float Function,+,furi_hal_speaker_start,void,"float, float" Function,+,furi_hal_speaker_stop,void, -Function,+,furi_hal_spi_acquire,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_acquire,void,const FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_deinit,void,FuriHalSpiBus* -Function,+,furi_hal_spi_bus_handle_deinit,void,FuriHalSpiBusHandle* -Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_handle_deinit,void,const FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_handle_init,void,const FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* -Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_trx_dma,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_rx,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx,_Bool,"const FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx_dma,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_tx,_Bool,"const FuriHalSpiBusHandle*, const uint8_t*, size_t, uint32_t" Function,-,furi_hal_spi_config_deinit_early,void, Function,-,furi_hal_spi_config_init,void, Function,-,furi_hal_spi_config_init_early,void, Function,-,furi_hal_spi_dma_init,void, -Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_release,void,const FuriHalSpiBusHandle* Function,+,furi_hal_switch,void,void* Function,+,furi_hal_usb_ccid_insert_smartcard,void, Function,+,furi_hal_usb_ccid_remove_smartcard,void, @@ -1492,7 +1512,7 @@ Function,+,furi_hal_usb_is_locked,_Bool, Function,+,furi_hal_usb_lock,void, Function,+,furi_hal_usb_reinit,void, Function,+,furi_hal_usb_set_config,_Bool,"FuriHalUsbInterface*, void*" -Function,-,furi_hal_usb_set_state_callback,void,"FuriHalUsbStateCallback, void*" +Function,+,furi_hal_usb_set_state_callback,void,"FuriHalUsbStateCallback, void*" Function,+,furi_hal_usb_unlock,void, Function,+,furi_hal_version_do_i_belong_here,_Bool, Function,+,furi_hal_version_get_ble_local_device_name_ptr,const char*, @@ -1663,8 +1683,8 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* -Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, -Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, +Function,+,furi_thread_get_stdin_callback,void,"FuriThreadStdinReadCallback*, void**" +Function,+,furi_thread_get_stdout_callback,void,"FuriThreadStdoutWriteCallback*, void**" Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_list_alloc,FuriThreadList*, @@ -1730,16 +1750,18 @@ Function,-,getchar,int, Function,-,getchar_unlocked,int, Function,-,getenv,char*,const char* Function,-,gets,char*,char* -Function,-,getsubopt,int,"char**, char**, char**" +Function,-,getsubopt,int,"char**, char* const*, char**" Function,-,getw,int,FILE* Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* Function,+,gui_direct_draw_release,void,Gui* Function,+,gui_get_framebuffer_size,size_t,const Gui* +Function,+,gui_is_lockdown,_Bool,const Gui* Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_lockdown,void,"Gui*, _Bool" +Function,+,gui_set_lockdown_inhibit,void,"Gui*, _Bool" Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,-,hci_send_req,int,"hci_request*, uint8_t" @@ -1841,6 +1863,9 @@ Function,-,llrintl,long long int,long double Function,-,llround,long long int,double Function,-,llroundf,long long int,float Function,-,llroundl,long long int,long double +Function,+,loader_clear_launch_queue,void,Loader* +Function,+,loader_enqueue_launch,void,"Loader*, const char*, const char*, LoaderDeferredLaunchFlag" +Function,+,loader_get_application_launch_path,_Bool,"Loader*, FuriString*" Function,+,loader_get_application_name,_Bool,"Loader*, FuriString*" Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_is_locked,_Bool,Loader* @@ -1892,6 +1917,21 @@ Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Boo Function,+,manchester_encoder_finish,ManchesterEncoderResult,ManchesterEncoderState* Function,+,manchester_encoder_reset,void,ManchesterEncoderState* Function,+,maxim_crc8,uint8_t,"const uint8_t*, const uint8_t, const uint8_t" +Function,-,mbedtls_aes_crypt_cbc,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb128,int,"mbedtls_aes_context*, int, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb8,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ctr,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ecb,int,"mbedtls_aes_context*, int, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_aes_crypt_ofb,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_xts,int,"mbedtls_aes_xts_context*, int, size_t, const unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_free,void,mbedtls_aes_context* +Function,-,mbedtls_aes_init,void,mbedtls_aes_context* +Function,-,mbedtls_aes_setkey_dec,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_setkey_enc,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_free,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_init,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_setkey_dec,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_setkey_enc,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" Function,-,mbedtls_des3_crypt_cbc,int,"mbedtls_des3_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" Function,-,mbedtls_des3_crypt_ecb,int,"mbedtls_des3_context*, const unsigned char[8], unsigned char[8]" Function,-,mbedtls_des3_free,void,mbedtls_des3_context* @@ -1915,6 +1955,7 @@ Function,-,mbedtls_ecdh_can_do,int,mbedtls_ecp_group_id Function,-,mbedtls_ecdh_compute_shared,int,"mbedtls_ecp_group*, mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecdh_free,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_gen_public,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_get_grp_id,mbedtls_ecp_group_id,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_get_params,int,"mbedtls_ecdh_context*, const mbedtls_ecp_keypair*, mbedtls_ecdh_side" Function,-,mbedtls_ecdh_init,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_make_params,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" @@ -1955,7 +1996,9 @@ Function,-,mbedtls_ecp_group_init,void,mbedtls_ecp_group* Function,-,mbedtls_ecp_group_load,int,"mbedtls_ecp_group*, mbedtls_ecp_group_id" Function,-,mbedtls_ecp_grp_id_list,const mbedtls_ecp_group_id*, Function,-,mbedtls_ecp_is_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_keypair_calc_public,int,"mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_keypair_free,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_keypair_get_group_id,mbedtls_ecp_group_id,const mbedtls_ecp_keypair* Function,-,mbedtls_ecp_keypair_init,void,mbedtls_ecp_keypair* Function,-,mbedtls_ecp_mul,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_mul_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecp_restart_ctx*" @@ -1968,6 +2011,7 @@ Function,-,mbedtls_ecp_point_read_binary,int,"const mbedtls_ecp_group*, mbedtls_ Function,-,mbedtls_ecp_point_read_string,int,"mbedtls_ecp_point*, int, const char*, const char*" Function,-,mbedtls_ecp_point_write_binary,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_read_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_set_public_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const mbedtls_ecp_point*" Function,-,mbedtls_ecp_set_zero,int,mbedtls_ecp_point* Function,-,mbedtls_ecp_tls_read_group,int,"mbedtls_ecp_group*, const unsigned char**, size_t" Function,-,mbedtls_ecp_tls_read_group_id,int,"mbedtls_ecp_group_id*, const unsigned char**, size_t" @@ -1975,6 +2019,10 @@ Function,-,mbedtls_ecp_tls_read_point,int,"const mbedtls_ecp_group*, mbedtls_ecp Function,-,mbedtls_ecp_tls_write_group,int,"const mbedtls_ecp_group*, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_tls_write_point,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_write_key,int,"mbedtls_ecp_keypair*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_key_ext,int,"const mbedtls_ecp_keypair*, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_public_key,int,"const mbedtls_ecp_keypair*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_internal_aes_decrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_internal_aes_encrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" Function,-,mbedtls_internal_md5_process,int,"mbedtls_md5_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha256_process,int,"mbedtls_sha256_context*, const unsigned char[64]" @@ -2300,13 +2348,14 @@ Function,+,pipe_bytes_available,size_t,PipeSide* Function,+,pipe_detach_from_event_loop,void,PipeSide* Function,+,pipe_free,void,PipeSide* Function,+,pipe_install_as_stdio,void,PipeSide* -Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t, FuriWait" +Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t" Function,+,pipe_role,PipeRole,PipeSide* -Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t, FuriWait" +Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t" +Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent" Function,+,pipe_set_callback_context,void,"PipeSide*, void*" Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent" -Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent" Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent" +Function,+,pipe_set_state_check_period,void,"PipeSide*, FuriWait" Function,+,pipe_spaces_available,size_t,PipeSide* Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" @@ -2334,9 +2383,11 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* +Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" Function,+,powf,float,"float, float" @@ -2344,7 +2395,7 @@ Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." -Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" +Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase* const*, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" Function,+,protocol_dict_decoders_feed_by_feature,ProtocolId,"ProtocolDict*, uint32_t, _Bool, uint32_t" Function,+,protocol_dict_decoders_feed_by_id,ProtocolId,"ProtocolDict*, size_t, _Bool, uint32_t" @@ -2445,6 +2496,7 @@ Function,-,scalbnl,long double,"long double, int" Function,-,scanf,int,"const char*, ..." Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" Function,+,scene_manager_free,void,SceneManager* +Function,+,scene_manager_get_current_scene,uint32_t,SceneManager* Function,+,scene_manager_get_scene_state,uint32_t,"const SceneManager*, uint32_t" Function,+,scene_manager_handle_back_event,_Bool,SceneManager* Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t" @@ -2506,29 +2558,29 @@ Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." -Function,+,st25r3916_change_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_change_test_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_check_reg,_Bool,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_clear_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_direct_cmd,void,"FuriHalSpiBusHandle*, uint8_t" -Function,+,st25r3916_get_irq,uint32_t,FuriHalSpiBusHandle* -Function,+,st25r3916_mask_irq,void,"FuriHalSpiBusHandle*, uint32_t" -Function,+,st25r3916_modify_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_read_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" -Function,+,st25r3916_read_fifo,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" -Function,+,st25r3916_read_pta_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_read_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" -Function,+,st25r3916_read_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" -Function,+,st25r3916_reg_read_fifo,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_reg_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_set_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_write_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" -Function,+,st25r3916_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_pta_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_ptf_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_change_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_change_test_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_check_reg,_Bool,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_clear_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_direct_cmd,void,"const FuriHalSpiBusHandle*, uint8_t" +Function,+,st25r3916_get_irq,uint32_t,const FuriHalSpiBusHandle* +Function,+,st25r3916_mask_irq,void,"const FuriHalSpiBusHandle*, uint32_t" +Function,+,st25r3916_modify_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_read_burst_regs,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" +Function,+,st25r3916_read_fifo,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" +Function,+,st25r3916_read_pta_mem,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_read_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_read_test_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_reg_read_fifo,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_reg_write_fifo,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_set_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_burst_regs,void,"const FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" +Function,+,st25r3916_write_fifo,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pta_mem,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_ptf_mem,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pttsn_mem,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_write_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_test_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*" Function,+,storage_common_exists,_Bool,"Storage*, const char*" @@ -2677,6 +2729,7 @@ Function,-,strverscmp,int,"const char*, const char*" Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" +Function,+,submenu_add_item_ex,void,"Submenu*, const char*, uint32_t, SubmenuItemCallbackEx, void*" Function,+,submenu_alloc,Submenu*, Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" Function,+,submenu_free,void,Submenu* @@ -2870,8 +2923,10 @@ Function,-,vsscanf,int,"const char*, const char*, __gnuc_va_list" Function,-,wcstombs,size_t,"char*, const wchar_t*, size_t" Function,-,wctomb,int,"char*, wchar_t" Function,+,widget_add_button_element,void,"Widget*, GuiButtonType, const char*, ButtonCallback, void*" -Function,+,widget_add_frame_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_circle_element,void,"Widget*, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_icon_element,void,"Widget*, uint8_t, uint8_t, const Icon*" +Function,+,widget_add_line_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_rect_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_string_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_string_multiline_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_text_box_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" @@ -2900,22 +2955,21 @@ Variable,-,__stdio_exit_handler,void (*)(), Variable,+,_ctype_,const char[], Variable,+,_impure_data,_reent, Variable,+,_impure_ptr,_reent*, -Variable,-,_sys_errlist,const char*[], +Variable,-,_sys_errlist,const char* const[], Variable,-,_sys_nerr,int, Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, -Variable,-,ble_profile_serial,const FuriHalBleProfileTemplate*, -Variable,+,cli_vcp,CliSession, +Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const, Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink, -Variable,+,firmware_api_interface,const ElfApiInterface*, +Variable,+,firmware_api_interface,const ElfApiInterface* const, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, -Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, -Variable,+,furi_hal_i2c_handle_power,FuriHalI2cBusHandle, +Variable,+,furi_hal_i2c_handle_external,const FuriHalI2cBusHandle, +Variable,+,furi_hal_i2c_handle_power,const FuriHalI2cBusHandle, Variable,+,furi_hal_spi_bus_d,FuriHalSpiBus, -Variable,+,furi_hal_spi_bus_handle_display,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_external,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_sd_fast,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_sd_slow,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_display,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_external,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_fast,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_slow,const FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_r,FuriHalSpiBus, Variable,+,furi_hal_spi_preset_1edge_low_16m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_1edge_low_2m,const LL_SPI_InitTypeDef, diff --git a/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h index 9a0d04cb6..23a88c215 100644 --- a/targets/f18/furi_hal/furi_hal_resources.h +++ b/targets/f18/furi_hal/furi_hal_resources.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -40,6 +41,7 @@ typedef struct { const GpioPin* pin; const char* name; const FuriHalAdcChannel channel; + const FuriHalPwmOutputId pwm_output; const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/targets/f18/furi_hal/furi_hal_spi_config.c b/targets/f18/furi_hal/furi_hal_spi_config.c index 8957bfe3a..a7393d3f0 100644 --- a/targets/f18/furi_hal/furi_hal_spi_config.c +++ b/targets/f18/furi_hal/furi_hal_spi_config.c @@ -143,7 +143,7 @@ FuriHalSpiBus furi_hal_spi_bus_d = { /* SPI Bus Handles */ inline static void furi_hal_spi_bus_r_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -189,7 +189,7 @@ inline static void furi_hal_spi_bus_r_handle_event_callback( } inline static void furi_hal_spi_bus_nfc_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -255,12 +255,12 @@ inline static void furi_hal_spi_bus_nfc_handle_event_callback( } static void furi_hal_spi_bus_handle_external_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { .bus = &furi_hal_spi_bus_r, .callback = furi_hal_spi_bus_handle_external_event_callback, .miso = &gpio_ext_pa6, @@ -270,7 +270,7 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { }; inline static void furi_hal_spi_bus_d_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -311,12 +311,12 @@ inline static void furi_hal_spi_bus_d_handle_event_callback( } static void furi_hal_spi_bus_handle_display_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_4m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_display_event_callback, .miso = &gpio_spi_d_miso, @@ -326,12 +326,12 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { }; static void furi_hal_spi_bus_handle_sd_fast_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_16m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_sd_fast_event_callback, .miso = &gpio_spi_d_miso, @@ -341,12 +341,12 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { }; static void furi_hal_spi_bus_handle_sd_slow_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_sd_slow_event_callback, .miso = &gpio_spi_d_miso, diff --git a/targets/f18/furi_hal/furi_hal_spi_config.h b/targets/f18/furi_hal/furi_hal_spi_config.h index da39fbfa6..2ff8b41b1 100644 --- a/targets/f18/furi_hal/furi_hal_spi_config.h +++ b/targets/f18/furi_hal/furi_hal_spi_config.h @@ -39,16 +39,16 @@ extern FuriHalSpiBus furi_hal_spi_bus_d; * Bus pins are floating on inactive state, CS high after initialization * */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_external; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_external; /** ST7567(Display) on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_display; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_display; /** SdCard in fast mode on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast; /** SdCard in slow mode on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow; #ifdef __cplusplus } diff --git a/targets/f18/target.json b/targets/f18/target.json index 3452c6707..1a8306596 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -36,7 +36,8 @@ "flipper18", "bit_lib", "toolbox", - "datetime" + "datetime", + "ieee754_parse_wrap" ], "excluded_sources": [ "furi_hal_infrared.c", diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv old mode 100644 new mode 100755 index b649f1f99..7c4dca564 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,79.3,, +Version,+,86.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -61,6 +61,7 @@ Header,+,lib/flipper_format/flipper_format_stream.h,, Header,+,lib/ibutton/ibutton_key.h,, Header,+,lib/ibutton/ibutton_protocols.h,, Header,+,lib/ibutton/ibutton_worker.h,, +Header,+,lib/ieee754_parse_wrap/wrappers.h,, Header,+,lib/infrared/encoder_decoder/infrared.h,, Header,+,lib/infrared/worker/infrared_transmit.h,, Header,+,lib/infrared/worker/infrared_worker.h,, @@ -96,6 +97,7 @@ Header,+,lib/libusb_stm32/inc/usb_hid.h,, Header,+,lib/libusb_stm32/inc/usb_std.h,, Header,+,lib/libusb_stm32/inc/usb_tmc.h,, Header,+,lib/libusb_stm32/inc/usbd_core.h,, +Header,+,lib/mbedtls/include/mbedtls/aes.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, Header,+,lib/mbedtls/include/mbedtls/ecdh.h,, Header,+,lib/mbedtls/include/mbedtls/ecdsa.h,, @@ -225,6 +227,10 @@ Header,+,lib/subghz/transmitter.h,, Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/bit_buffer.h,, +Header,+,lib/toolbox/cli/cli_ansi.h,, +Header,+,lib/toolbox/cli/cli_command.h,, +Header,+,lib/toolbox/cli/cli_registry.h,, +Header,+,lib/toolbox/cli/shell/cli_shell.h,, Header,+,lib/toolbox/compress.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, @@ -461,6 +467,8 @@ Function,+,__wrap_putc,int,"int, FILE*" Function,+,__wrap_putchar,int,int Function,+,__wrap_puts,int,const char* Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_strtod,double,"const char*, char**" +Function,+,__wrap_strtof,float,"const char*, char**" Function,+,__wrap_ungetc,int,"int, FILE*" Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." @@ -770,7 +778,6 @@ Function,+,ble_svc_serial_start,BleServiceSerial*, Function,+,ble_svc_serial_stop,void,BleServiceSerial* Function,+,ble_svc_serial_update_tx,_Bool,"BleServiceSerial*, uint8_t*, uint16_t" Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" -Function,-,bt_close_rpc_connection,void,Bt* Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char* @@ -783,10 +790,8 @@ Function,+,bt_keys_storage_set_file_path,void,"BtKeysStorage*, const char*" Function,+,bt_keys_storage_set_ram_params,void,"BtKeysStorage*, uint8_t*, uint16_t" Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" Function,+,bt_keys_storage_update,_Bool,"BtKeysStorage*, uint8_t*, uint32_t" -Function,-,bt_open_rpc_connection,void,Bt* Function,+,bt_profile_restore_default,_Bool,Bt* Function,+,bt_profile_start,FuriHalBleProfileBase*,"Bt*, const FuriHalBleProfileTemplate*, FuriHalBleProfileParams" -Function,+,bt_remote_rssi,_Bool,"Bt*, uint8_t*" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* @@ -860,18 +865,26 @@ Function,-,ceill,long double,long double Function,-,cfree,void,void* Function,-,clearerr,void,FILE* Function,-,clearerr_unlocked,void,FILE* -Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*" -Function,+,cli_cmd_interrupt_received,_Bool,Cli* -Function,+,cli_delete_command,void,"Cli*, const char*" -Function,+,cli_getc,char,Cli* -Function,+,cli_is_connected,_Bool,Cli* -Function,+,cli_nl,void,Cli* +Function,+,cli_ansi_parser_alloc,CliAnsiParser*, +Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char" +Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser* +Function,+,cli_ansi_parser_free,void,CliAnsiParser* +Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide* Function,+,cli_print_usage,void,"const char*, const char*, const char*" -Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t" -Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" -Function,+,cli_session_close,void,Cli* -Function,+,cli_session_open,void,"Cli*, void*" -Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" +Function,+,cli_registry_add_command,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*" +Function,+,cli_registry_add_command_ex,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*, size_t" +Function,+,cli_registry_alloc,CliRegistry*, +Function,+,cli_registry_delete_command,void,"CliRegistry*, const char*" +Function,+,cli_registry_free,void,CliRegistry* +Function,+,cli_registry_reload_external_commands,void,"CliRegistry*, const CliCommandExternalConfig*" +Function,+,cli_registry_remove_external_commands,void,CliRegistry* +Function,+,cli_shell_alloc,CliShell*,"CliShellMotd, void*, PipeSide*, CliRegistry*, const CliCommandExternalConfig*" +Function,+,cli_shell_free,void,CliShell* +Function,+,cli_shell_join,void,CliShell* +Function,+,cli_shell_set_prompt,void,"CliShell*, const char*" +Function,+,cli_shell_start,void,CliShell* +Function,+,cli_vcp_disable,void,CliVcp* +Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* @@ -1009,6 +1022,7 @@ Function,+,elements_string_fit_width,void,"Canvas*, FuriString*, size_t" Function,+,elements_text_box,void,"Canvas*, int32_t, int32_t, size_t, size_t, Align, Align, const char*, _Bool" Function,+,elf_resolve_from_hashtable,_Bool,"const ElfApiInterface*, uint32_t, Elf32_Addr*" Function,+,elf_symbolname_hash,uint32_t,const char* +Function,+,em4305_write,void,LFRFIDEM4305* Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* @@ -1077,6 +1091,7 @@ Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*" Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*" Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t" Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*" +Function,+,felica_poller_read_blocks,FelicaError,"FelicaPoller*, const uint8_t, const uint8_t* const, uint16_t, FelicaPollerReadCommandResponse** const" Function,+,felica_poller_sync_read,FelicaError,"Nfc*, FelicaData*, const FelicaCardKey*" Function,+,felica_reset,void,FelicaData* Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" @@ -1177,6 +1192,7 @@ Function,+,flipper_format_read_int32,_Bool,"FlipperFormat*, const char*, int32_t Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, FuriString*" Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32_t*, const uint16_t" Function,+,flipper_format_rewind,_Bool,FlipperFormat* +Function,+,flipper_format_seek,_Bool,"FlipperFormat*, int32_t, FlipperFormatOffset" Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat* Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool" Function,+,flipper_format_stream_delete_key_and_write,_Bool,"Stream*, FlipperStreamWriteData*, _Bool" @@ -1185,6 +1201,7 @@ Function,+,flipper_format_stream_read_value_line,_Bool,"Stream*, const char*, Fl Function,+,flipper_format_stream_write_comment_cstr,_Bool,"Stream*, const char*" Function,+,flipper_format_stream_write_value_line,_Bool,"Stream*, FlipperStreamWriteData*" Function,+,flipper_format_string_alloc,FlipperFormat*, +Function,+,flipper_format_tell,size_t,FlipperFormat* Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" @@ -1272,6 +1289,7 @@ Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMes Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_stream_buffer,void,"FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" +Function,+,furi_event_loop_subscribe_thread_flags,void,"FuriEventLoop*, FuriEventLoopThreadFlagsCallback, void*" Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*" Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer* @@ -1282,6 +1300,7 @@ Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer* Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t" Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer* Function,+,furi_event_loop_unsubscribe,void,"FuriEventLoop*, FuriEventLoopObject*" +Function,+,furi_event_loop_unsubscribe_thread_flags,void,FuriEventLoop* Function,+,furi_get_tick,uint32_t, Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* @@ -1305,7 +1324,6 @@ Function,+,furi_hal_bt_extra_beacon_set_config,_Bool,const GapExtraBeaconConfig* Function,+,furi_hal_bt_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" Function,+,furi_hal_bt_extra_beacon_start,_Bool, Function,+,furi_hal_bt_extra_beacon_stop,_Bool, -Function,-,furi_hal_bt_get_conn_rssi,uint32_t,uint8_t* Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, Function,+,furi_hal_bt_get_rssi,float, @@ -1313,14 +1331,12 @@ Function,+,furi_hal_bt_get_transmitted_packets,uint32_t, Function,-,furi_hal_bt_init,void, Function,+,furi_hal_bt_is_active,_Bool, Function,+,furi_hal_bt_is_alive,_Bool, -Function,+,furi_hal_bt_is_connected,_Bool, Function,+,furi_hal_bt_is_gatt_gap_supported,_Bool, Function,+,furi_hal_bt_is_testing_supported,_Bool, Function,+,furi_hal_bt_lock_core2,void, Function,+,furi_hal_bt_nvm_sram_sem_acquire,void, Function,+,furi_hal_bt_nvm_sram_sem_release,void, Function,+,furi_hal_bt_reinit,void, -Function,+,furi_hal_bt_reverse_mac_addr,void,uint8_t[( 6 )] Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" Function,+,furi_hal_bt_start_advertising,void, Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" @@ -1427,23 +1443,23 @@ Function,+,furi_hal_hid_u2f_get_request,uint32_t,uint8_t* Function,+,furi_hal_hid_u2f_is_connected,_Bool, Function,+,furi_hal_hid_u2f_send_response,void,"uint8_t*, uint8_t" Function,+,furi_hal_hid_u2f_set_callback,void,"HidU2fCallback, void*" -Function,+,furi_hal_i2c_acquire,void,FuriHalI2cBusHandle* +Function,+,furi_hal_i2c_acquire,void,const FuriHalI2cBusHandle* Function,-,furi_hal_i2c_deinit_early,void, Function,-,furi_hal_i2c_init,void, Function,-,furi_hal_i2c_init_early,void, -Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" -Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" -Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" -Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* -Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_rx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" -Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_tx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" -Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" -Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" +Function,+,furi_hal_i2c_is_device_ready,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_read_reg_16,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" +Function,+,furi_hal_i2c_read_reg_8,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" +Function,+,furi_hal_i2c_release,void,const FuriHalI2cBusHandle* +Function,+,furi_hal_i2c_rx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_rx_ext,_Bool,"const FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx_ext,_Bool,"const FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_16,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_8,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" Function,+,furi_hal_ibutton_emulate_set_next,void,uint32_t Function,+,furi_hal_ibutton_emulate_start,void,"uint32_t, FuriHalIbuttonEmulateCallback, void*" Function,+,furi_hal_ibutton_emulate_stop,void, @@ -1658,6 +1674,7 @@ Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_configure_framing,void,"FuriHalSerialHandle*, FuriHalSerialDataBits, FuriHalSerialParity, FuriHalSerialStopBits" Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId Function,+,furi_hal_serial_control_deinit,void, Function,+,furi_hal_serial_control_init,void, @@ -1677,7 +1694,6 @@ Function,+,furi_hal_serial_get_gpio_pin,const GpioPin*,"FuriHalSerialHandle*, Fu Function,+,furi_hal_serial_init,void,"FuriHalSerialHandle*, uint32_t" Function,+,furi_hal_serial_is_baud_rate_supported,_Bool,"FuriHalSerialHandle*, uint32_t" Function,+,furi_hal_serial_resume,void,FuriHalSerialHandle* -Function,+,furi_hal_serial_send_break,void,FuriHalSerialHandle* Function,+,furi_hal_serial_set_br,void,"FuriHalSerialHandle*, uint32_t" Function,+,furi_hal_serial_suspend,void,FuriHalSerialHandle* Function,+,furi_hal_serial_tx,void,"FuriHalSerialHandle*, const uint8_t*, size_t" @@ -1690,27 +1706,27 @@ Function,+,furi_hal_speaker_release,void, Function,+,furi_hal_speaker_set_volume,void,float Function,+,furi_hal_speaker_start,void,"float, float" Function,+,furi_hal_speaker_stop,void, -Function,+,furi_hal_spi_acquire,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_acquire,void,const FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_deinit,void,FuriHalSpiBus* -Function,+,furi_hal_spi_bus_handle_deinit,void,FuriHalSpiBusHandle* -Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_handle_deinit,void,const FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_handle_init,void,const FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* -Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_trx_dma,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_rx,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx,_Bool,"const FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx_dma,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_tx,_Bool,"const FuriHalSpiBusHandle*, const uint8_t*, size_t, uint32_t" Function,-,furi_hal_spi_config_deinit_early,void, Function,-,furi_hal_spi_config_init,void, Function,-,furi_hal_spi_config_init_early,void, Function,-,furi_hal_spi_dma_init,void, -Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_release,void,const FuriHalSpiBusHandle* Function,-,furi_hal_subghz_dump_state,void, Function,+,furi_hal_subghz_flush_rx,void, Function,+,furi_hal_subghz_flush_tx,void, Function,+,furi_hal_subghz_get_data_gpio,const GpioPin*, Function,+,furi_hal_subghz_get_ext_leds_and_amp,_Bool, Function,+,furi_hal_subghz_get_lqi,uint8_t, -Function,+,furi_hal_subghz_get_rolling_counter_mult,int8_t, +Function,+,furi_hal_subghz_get_rolling_counter_mult,int32_t, Function,+,furi_hal_subghz_get_rssi,float, Function,+,furi_hal_subghz_idle,void, Function,-,furi_hal_subghz_init,void, @@ -1730,7 +1746,7 @@ Function,+,furi_hal_subghz_set_ext_leds_and_amp,void,_Bool Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath -Function,+,furi_hal_subghz_set_rolling_counter_mult,void,int8_t +Function,+,furi_hal_subghz_set_rolling_counter_mult,void,int32_t Function,+,furi_hal_subghz_shutdown,void, Function,+,furi_hal_subghz_sleep,void, Function,+,furi_hal_subghz_start_async_rx,void,"FuriHalSubGhzCaptureCallback, void*" @@ -1751,7 +1767,7 @@ Function,+,furi_hal_usb_is_locked,_Bool, Function,+,furi_hal_usb_lock,void, Function,+,furi_hal_usb_reinit,void, Function,+,furi_hal_usb_set_config,_Bool,"FuriHalUsbInterface*, void*" -Function,-,furi_hal_usb_set_state_callback,void,"FuriHalUsbStateCallback, void*" +Function,+,furi_hal_usb_set_state_callback,void,"FuriHalUsbStateCallback, void*" Function,+,furi_hal_usb_unlock,void, Function,+,furi_hal_version_do_i_belong_here,_Bool, Function,+,furi_hal_version_get_ble_local_device_name_ptr,const char*, @@ -1926,8 +1942,8 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* -Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, -Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, +Function,+,furi_thread_get_stdin_callback,void,"FuriThreadStdinReadCallback*, void**" +Function,+,furi_thread_get_stdout_callback,void,"FuriThreadStdoutWriteCallback*, void**" Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_list_alloc,FuriThreadList*, @@ -1982,7 +1998,6 @@ Function,-,gap_extra_beacon_set_config,_Bool,const GapExtraBeaconConfig* Function,-,gap_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" Function,-,gap_extra_beacon_start,_Bool, Function,-,gap_extra_beacon_stop,_Bool, -Function,-,gap_get_remote_conn_rssi,uint32_t,int8_t* Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" Function,-,gap_start_advertising,void, @@ -1994,16 +2009,18 @@ Function,-,getchar,int, Function,-,getchar_unlocked,int, Function,-,getenv,char*,const char* Function,-,gets,char*,char* -Function,-,getsubopt,int,"char**, char**, char**" +Function,-,getsubopt,int,"char**, char* const*, char**" Function,-,getw,int,FILE* Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* Function,+,gui_direct_draw_release,void,Gui* Function,+,gui_get_framebuffer_size,size_t,const Gui* +Function,+,gui_is_lockdown,_Bool,const Gui* Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_lockdown,void,"Gui*, _Bool" +Function,+,gui_set_lockdown_inhibit,void,"Gui*, _Bool" Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,-,hci_send_req,int,"hci_request*, uint8_t" @@ -2112,6 +2129,8 @@ Function,+,infrared_worker_tx_stop,void,InfraredWorker* Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey Function,+,input_get_type_name,const char*,InputType +Function,+,input_settings_load,void,InputSettings* +Function,+,input_settings_save,void,const InputSettings* Function,-,iprintf,int,"const char*, ..." Function,-,isalnum,int,int Function,-,isalnum_l,int,"int, locale_t" @@ -2314,6 +2333,9 @@ Function,-,llrintl,long long int,long double Function,-,llround,long long int,double Function,-,llroundf,long long int,float Function,-,llroundl,long long int,long double +Function,+,loader_clear_launch_queue,void,Loader* +Function,+,loader_enqueue_launch,void,"Loader*, const char*, const char*, LoaderDeferredLaunchFlag" +Function,+,loader_get_application_launch_path,_Bool,"Loader*, FuriString*" Function,+,loader_get_application_name,_Bool,"Loader*, FuriString*" Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_is_locked,_Bool,Loader* @@ -2365,6 +2387,21 @@ Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Boo Function,+,manchester_encoder_finish,ManchesterEncoderResult,ManchesterEncoderState* Function,+,manchester_encoder_reset,void,ManchesterEncoderState* Function,+,maxim_crc8,uint8_t,"const uint8_t*, const uint8_t, const uint8_t" +Function,-,mbedtls_aes_crypt_cbc,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb128,int,"mbedtls_aes_context*, int, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb8,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ctr,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ecb,int,"mbedtls_aes_context*, int, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_aes_crypt_ofb,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_xts,int,"mbedtls_aes_xts_context*, int, size_t, const unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_free,void,mbedtls_aes_context* +Function,-,mbedtls_aes_init,void,mbedtls_aes_context* +Function,-,mbedtls_aes_setkey_dec,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_setkey_enc,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_free,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_init,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_setkey_dec,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_setkey_enc,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" Function,-,mbedtls_des3_crypt_cbc,int,"mbedtls_des3_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" Function,-,mbedtls_des3_crypt_ecb,int,"mbedtls_des3_context*, const unsigned char[8], unsigned char[8]" Function,-,mbedtls_des3_free,void,mbedtls_des3_context* @@ -2388,6 +2425,7 @@ Function,-,mbedtls_ecdh_can_do,int,mbedtls_ecp_group_id Function,-,mbedtls_ecdh_compute_shared,int,"mbedtls_ecp_group*, mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecdh_free,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_gen_public,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_get_grp_id,mbedtls_ecp_group_id,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_get_params,int,"mbedtls_ecdh_context*, const mbedtls_ecp_keypair*, mbedtls_ecdh_side" Function,-,mbedtls_ecdh_init,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_make_params,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" @@ -2428,7 +2466,9 @@ Function,-,mbedtls_ecp_group_init,void,mbedtls_ecp_group* Function,-,mbedtls_ecp_group_load,int,"mbedtls_ecp_group*, mbedtls_ecp_group_id" Function,-,mbedtls_ecp_grp_id_list,const mbedtls_ecp_group_id*, Function,-,mbedtls_ecp_is_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_keypair_calc_public,int,"mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_keypair_free,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_keypair_get_group_id,mbedtls_ecp_group_id,const mbedtls_ecp_keypair* Function,-,mbedtls_ecp_keypair_init,void,mbedtls_ecp_keypair* Function,-,mbedtls_ecp_mul,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_mul_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecp_restart_ctx*" @@ -2441,6 +2481,7 @@ Function,-,mbedtls_ecp_point_read_binary,int,"const mbedtls_ecp_group*, mbedtls_ Function,-,mbedtls_ecp_point_read_string,int,"mbedtls_ecp_point*, int, const char*, const char*" Function,-,mbedtls_ecp_point_write_binary,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_read_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_set_public_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const mbedtls_ecp_point*" Function,-,mbedtls_ecp_set_zero,int,mbedtls_ecp_point* Function,-,mbedtls_ecp_tls_read_group,int,"mbedtls_ecp_group*, const unsigned char**, size_t" Function,-,mbedtls_ecp_tls_read_group_id,int,"mbedtls_ecp_group_id*, const unsigned char**, size_t" @@ -2448,6 +2489,10 @@ Function,-,mbedtls_ecp_tls_read_point,int,"const mbedtls_ecp_group*, mbedtls_ecp Function,-,mbedtls_ecp_tls_write_group,int,"const mbedtls_ecp_group*, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_tls_write_point,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_write_key,int,"mbedtls_ecp_keypair*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_key_ext,int,"const mbedtls_ecp_keypair*, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_public_key,int,"const mbedtls_ecp_keypair*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_internal_aes_decrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_internal_aes_encrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" Function,-,mbedtls_internal_md5_process,int,"mbedtls_md5_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha256_process,int,"mbedtls_sha256_context*, const unsigned char[64]" @@ -2675,7 +2720,7 @@ Function,+,mf_ultralight_3des_decrypt,void,"mbedtls_des3_context*, const uint8_t Function,+,mf_ultralight_3des_encrypt,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*" Function,+,mf_ultralight_3des_get_key,const uint8_t*,const MfUltralightData* Function,+,mf_ultralight_3des_key_valid,_Bool,const MfUltralightData* -Function,+,mf_ultralight_3des_shift_data,void,uint8_t* +Function,+,mf_ultralight_3des_shift_data,void,uint8_t* const Function,+,mf_ultralight_alloc,MfUltralightData*, Function,+,mf_ultralight_copy,void,"MfUltralightData*, const MfUltralightData*" Function,+,mf_ultralight_detect_protocol,_Bool,const Iso14443_3aData* @@ -2704,7 +2749,7 @@ Function,+,mf_ultralight_poller_read_page_from_sector,MfUltralightError,"MfUltra Function,+,mf_ultralight_poller_read_signature,MfUltralightError,"MfUltralightPoller*, MfUltralightSignature*" Function,+,mf_ultralight_poller_read_tearing_flag,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightTearingFlag*" Function,+,mf_ultralight_poller_read_version,MfUltralightError,"MfUltralightPoller*, MfUltralightVersion*" -Function,+,mf_ultralight_poller_sync_read_card,MfUltralightError,"Nfc*, MfUltralightData*" +Function,+,mf_ultralight_poller_sync_read_card,MfUltralightError,"Nfc*, MfUltralightData*, const MfUltralightPollerAuthContext*" Function,+,mf_ultralight_poller_sync_read_counter,MfUltralightError,"Nfc*, uint8_t, MfUltralightCounter*" Function,+,mf_ultralight_poller_sync_read_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" Function,+,mf_ultralight_poller_sync_read_signature,MfUltralightError,"Nfc*, MfUltralightSignature*" @@ -2990,13 +3035,14 @@ Function,+,pipe_bytes_available,size_t,PipeSide* Function,+,pipe_detach_from_event_loop,void,PipeSide* Function,+,pipe_free,void,PipeSide* Function,+,pipe_install_as_stdio,void,PipeSide* -Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t, FuriWait" +Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t" Function,+,pipe_role,PipeRole,PipeSide* -Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t, FuriWait" +Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t" Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent" Function,+,pipe_set_callback_context,void,"PipeSide*, void*" Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent" Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent" +Function,+,pipe_set_state_check_period,void,"PipeSide*, FuriWait" Function,+,pipe_spaces_available,size_t,PipeSide* Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" @@ -3026,9 +3072,11 @@ Function,-,pow10f,float,float Function,+,power_api_get_settings,void,"Power*, PowerSettings*" Function,+,power_api_set_settings,void,"Power*, const PowerSettings*" Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* +Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" Function,+,power_settings_load,void,PowerSettings* @@ -3039,7 +3087,7 @@ Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." -Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" +Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase* const*, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" Function,+,protocol_dict_decoders_feed_by_feature,ProtocolId,"ProtocolDict*, uint32_t, _Bool, uint32_t" Function,+,protocol_dict_decoders_feed_by_id,ProtocolId,"ProtocolDict*, size_t, _Bool, uint32_t" @@ -3140,6 +3188,7 @@ Function,-,scalbnl,long double,"long double, int" Function,-,scanf,int,"const char*, ..." Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" Function,+,scene_manager_free,void,SceneManager* +Function,+,scene_manager_get_current_scene,uint32_t,SceneManager* Function,+,scene_manager_get_scene_state,uint32_t,"const SceneManager*, uint32_t" Function,+,scene_manager_handle_back_event,_Bool,SceneManager* Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t" @@ -3226,29 +3275,29 @@ Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." -Function,+,st25r3916_change_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_change_test_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_check_reg,_Bool,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_clear_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_direct_cmd,void,"FuriHalSpiBusHandle*, uint8_t" -Function,+,st25r3916_get_irq,uint32_t,FuriHalSpiBusHandle* -Function,+,st25r3916_mask_irq,void,"FuriHalSpiBusHandle*, uint32_t" -Function,+,st25r3916_modify_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_read_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" -Function,+,st25r3916_read_fifo,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" -Function,+,st25r3916_read_pta_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_read_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" -Function,+,st25r3916_read_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" -Function,+,st25r3916_reg_read_fifo,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_reg_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_set_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_write_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" -Function,+,st25r3916_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_pta_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_ptf_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_change_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_change_test_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_check_reg,_Bool,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_clear_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_direct_cmd,void,"const FuriHalSpiBusHandle*, uint8_t" +Function,+,st25r3916_get_irq,uint32_t,const FuriHalSpiBusHandle* +Function,+,st25r3916_mask_irq,void,"const FuriHalSpiBusHandle*, uint32_t" +Function,+,st25r3916_modify_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_read_burst_regs,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" +Function,+,st25r3916_read_fifo,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" +Function,+,st25r3916_read_pta_mem,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_read_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_read_test_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_reg_read_fifo,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_reg_write_fifo,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_set_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_burst_regs,void,"const FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" +Function,+,st25r3916_write_fifo,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pta_mem,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_ptf_mem,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pttsn_mem,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_write_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_test_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,st25tb_alloc,St25tbData*, Function,+,st25tb_copy,void,"St25tbData*, const St25tbData*" Function,+,st25tb_free,void,St25tbData* @@ -3483,14 +3532,14 @@ Function,+,subghz_file_encoder_worker_get_text_progress,void,"SubGhzFileEncoderW Function,+,subghz_file_encoder_worker_is_running,_Bool,SubGhzFileEncoderWorker* Function,+,subghz_file_encoder_worker_start,_Bool,"SubGhzFileEncoderWorker*, const char*, const char*" Function,+,subghz_file_encoder_worker_stop,void,SubGhzFileEncoderWorker* -Function,-,subghz_keystore_alloc,SubGhzKeystore*, -Function,-,subghz_keystore_free,void,SubGhzKeystore* -Function,-,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* -Function,-,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*" -Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*" -Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" -Function,-,subghz_keystore_reset_kl,void,SubGhzKeystore* -Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" +Function,+,subghz_keystore_alloc,SubGhzKeystore*, +Function,+,subghz_keystore_free,void,SubGhzKeystore* +Function,+,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* +Function,+,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*" +Function,+,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*" +Function,+,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" +Function,+,subghz_keystore_reset_kl,void,SubGhzKeystore* +Function,+,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" Function,+,subghz_protocol_alutech_at_4n_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t" Function,+,subghz_protocol_blocks_add_bytes,uint8_t,"const uint8_t[], size_t" @@ -3601,6 +3650,7 @@ Function,+,subghz_worker_set_pair_callback,void,"SubGhzWorker*, SubGhzWorkerPair Function,+,subghz_worker_start,void,SubGhzWorker* Function,+,subghz_worker_stop,void,SubGhzWorker* Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" +Function,+,submenu_add_item_ex,void,"Submenu*, const char*, uint32_t, SubmenuItemCallbackEx, void*" Function,+,submenu_add_lockable_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*, _Bool, const char*" Function,+,submenu_alloc,Submenu*, Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" @@ -3804,8 +3854,10 @@ Function,-,vsscanf,int,"const char*, const char*, __gnuc_va_list" Function,-,wcstombs,size_t,"char*, const wchar_t*, size_t" Function,-,wctomb,int,"char*, wchar_t" Function,+,widget_add_button_element,void,"Widget*, GuiButtonType, const char*, ButtonCallback, void*" -Function,+,widget_add_frame_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_circle_element,void,"Widget*, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_icon_element,void,"Widget*, uint8_t, uint8_t, const Icon*" +Function,+,widget_add_line_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_rect_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_string_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_string_multiline_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_text_box_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" @@ -3834,24 +3886,23 @@ Variable,-,__stdio_exit_handler,void (*)(), Variable,+,_ctype_,const char[], Variable,+,_impure_data,_reent, Variable,+,_impure_ptr,_reent*, -Variable,-,_sys_errlist,const char*[], +Variable,-,_sys_errlist,const char* const[], Variable,-,_sys_nerr,int, Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, -Variable,-,ble_profile_serial,const FuriHalBleProfileTemplate*, -Variable,+,cli_vcp,CliSession, +Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const, Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink, -Variable,+,firmware_api_interface,const ElfApiInterface*, +Variable,+,firmware_api_interface,const ElfApiInterface* const, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, -Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, -Variable,+,furi_hal_i2c_handle_power,FuriHalI2cBusHandle, +Variable,+,furi_hal_i2c_handle_external,const FuriHalI2cBusHandle, +Variable,+,furi_hal_i2c_handle_power,const FuriHalI2cBusHandle, Variable,+,furi_hal_spi_bus_d,FuriHalSpiBus, -Variable,+,furi_hal_spi_bus_handle_display,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_external,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_nfc,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_sd_fast,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_sd_slow,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_subghz,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_display,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_external,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_nfc,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_fast,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_slow,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_subghz,const FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_r,FuriHalSpiBus, Variable,+,furi_hal_spi_preset_1edge_low_16m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_1edge_low_2m,const LL_SPI_InitTypeDef, @@ -3909,7 +3960,7 @@ Variable,+,gpio_usb_dp,const GpioPin, Variable,+,gpio_vibro,const GpioPin, Variable,+,input_pins,const InputPin[], Variable,+,input_pins_count,const size_t, -Variable,+,lfrfid_protocols,const ProtocolBase*[], +Variable,+,lfrfid_protocols,const ProtocolBase* const[], Variable,+,message_blink_set_color_blue,const NotificationMessage, Variable,+,message_blink_set_color_cyan,const NotificationMessage, Variable,+,message_blink_set_color_green,const NotificationMessage, diff --git a/targets/f7/application_ext.ld b/targets/f7/application_ext.ld index b6496290a..456947db1 100644 --- a/targets/f7/application_ext.ld +++ b/targets/f7/application_ext.ld @@ -33,10 +33,56 @@ SECTIONS { *(COMMON) } - .ARM.attributes : { - *(.ARM.attributes) - *(.ARM.attributes.*) - } + /* Default debug-related rules from ld */ + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1. */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions. */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2. */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2. */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions. */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3. */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF 5. */ + .debug_addr 0 : { *(.debug_addr) } + .debug_line_str 0 : { *(.debug_line_str) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_macro 0 : { *(.debug_macro) } + .debug_names 0 : { *(.debug_names) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } + .debug_sup 0 : { *(.debug_sup) } + .ARM.attributes 0 : { KEEP (*(.ARM.attributes)) KEEP (*(.gnu.attributes)) } + .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) } + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } /DISCARD/ : { *(.comment) diff --git a/targets/f7/ble_glue/ble_event_thread.c b/targets/f7/ble_glue/ble_event_thread.c index c6bc56846..3d1fdd196 100644 --- a/targets/f7/ble_glue/ble_event_thread.c +++ b/targets/f7/ble_glue/ble_event_thread.c @@ -90,7 +90,7 @@ void ble_event_thread_stop(void) { void ble_event_thread_start(void) { furi_check(event_thread == NULL); - event_thread = furi_thread_alloc_ex("BleEventWorker", 1024, ble_event_thread, NULL); + event_thread = furi_thread_alloc_ex("BleEventWorker", 1280, ble_event_thread, NULL); furi_thread_set_priority(event_thread, FuriThreadPriorityHigh); furi_thread_start(event_thread); } diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index e03935ce0..fd8d09f51 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -23,6 +23,8 @@ typedef struct { uint16_t connection_handle; uint8_t adv_svc_uuid_len; uint8_t adv_svc_uuid[20]; + uint8_t mfg_data_len; + uint8_t mfg_data[23]; char* adv_name; } GapSvc; @@ -31,8 +33,6 @@ typedef struct { GapConfig* config; GapConnectionParams connection_params; GapState state; - int8_t conn_rssi; - uint32_t time_rssi_sample; FuriMutex* state_mutex; GapEventCallback on_event_cb; void* context; @@ -63,19 +63,6 @@ static Gap* gap = NULL; static void gap_advertise_start(GapState new_state); static int32_t gap_app(void* context); -/** function for updating rssi informations in global Gap object - * -*/ -static inline void fetch_rssi(void) { - uint8_t ret_rssi = 127; - if(hci_read_rssi(gap->service.connection_handle, &ret_rssi) == BLE_STATUS_SUCCESS) { - gap->conn_rssi = (int8_t)ret_rssi; - gap->time_rssi_sample = furi_get_tick(); - return; - } - FURI_LOG_D(TAG, "Failed to read RSSI"); -} - static void gap_verify_connection_parameters(Gap* gap) { furi_check(gap); @@ -180,8 +167,6 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { FURI_LOG_I(TAG, "Connection parameters event complete"); gap_verify_connection_parameters(gap); - // Save rssi for current connection - fetch_rssi(); break; } @@ -217,10 +202,10 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { gap_verify_connection_parameters(gap); - // Save rssi for current connection - fetch_rssi(); - // Start pairing by sending security request - aci_gap_slave_security_req(event->Connection_Handle); + if(gap->config->pairing_method != GapPairingNone) { + // Start pairing by sending security request + aci_gap_slave_security_req(event->Connection_Handle); + } } break; default: @@ -300,9 +285,6 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { pairing_complete->Status); aci_gap_terminate(gap->service.connection_handle, 5); } else { - // Save RSSI - fetch_rssi(); - FURI_LOG_I(TAG, "Pairing complete"); GapEvent event = {.type = GapEventTypeConnected}; gap->on_event_cb(event, gap->context); //-V595 @@ -345,6 +327,14 @@ static void set_advertisment_service_uid(uint8_t* uid, uint8_t uid_len) { gap->service.adv_svc_uuid_len += uid_len; } +static void set_manufacturer_data(uint8_t* mfg_data, uint8_t mfg_data_len) { + furi_check(mfg_data_len <= sizeof(gap->service.mfg_data) - 2); + gap->service.mfg_data[0] = mfg_data_len + 1; + gap->service.mfg_data[1] = AD_TYPE_MANUFACTURER_SPECIFIC_DATA; + memcpy(&gap->service.mfg_data[gap->service.mfg_data_len], mfg_data, mfg_data_len); + gap->service.mfg_data_len += mfg_data_len; +} + static void gap_init_svc(Gap* gap) { tBleStatus status; uint32_t srd_bd_addr[2]; @@ -464,6 +454,11 @@ static void gap_advertise_start(GapState new_state) { FURI_LOG_D(TAG, "set_non_discoverable success"); } } + + if(gap->service.mfg_data_len > 0) { + hci_le_set_scan_response_data(gap->service.mfg_data_len, gap->service.mfg_data); + } + // Configure advertising status = aci_gap_set_discoverable( ADV_IND, @@ -563,9 +558,6 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->service.connection_handle = 0xFFFF; gap->enable_adv = true; - gap->conn_rssi = 127; - gap->time_rssi_sample = 0; - // Command queue allocation gap->command_queue = furi_message_queue_alloc(8, sizeof(GapCommand)); @@ -577,11 +569,26 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->is_secure = false; gap->negotiation_round = 0; - uint8_t adv_service_uid[2]; - gap->service.adv_svc_uuid_len = 1; - adv_service_uid[0] = gap->config->adv_service_uuid & 0xff; - adv_service_uid[1] = gap->config->adv_service_uuid >> 8; - set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); + if(gap->config->mfg_data_len > 0) { + // Offset by 2 for length + AD_TYPE_MANUFACTURER_SPECIFIC_DATA + gap->service.mfg_data_len = 2; + set_manufacturer_data(gap->config->mfg_data, gap->config->mfg_data_len); + } + + if(gap->config->adv_service.UUID_Type == UUID_TYPE_16) { + uint8_t adv_service_uid[2]; + gap->service.adv_svc_uuid_len = 1; + adv_service_uid[0] = gap->config->adv_service.Service_UUID_16 & 0xff; + adv_service_uid[1] = gap->config->adv_service.Service_UUID_16 >> 8; + set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); + } else if(gap->config->adv_service.UUID_Type == UUID_TYPE_128) { + gap->service.adv_svc_uuid_len = 1; + set_advertisment_service_uid( + gap->config->adv_service.Service_UUID_128, + sizeof(gap->config->adv_service.Service_UUID_128)); + } else { + furi_crash("Invalid UUID type"); + } // Set callback gap->on_event_cb = on_event_cb; @@ -590,17 +597,6 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { return true; } -// Get RSSI -uint32_t gap_get_remote_conn_rssi(int8_t* rssi) { - if(gap && gap->state == GapStateConnected) { - fetch_rssi(); - *rssi = gap->conn_rssi; - - if(gap->time_rssi_sample) return furi_get_tick() - gap->time_rssi_sample; - } - return 0; -} - GapState gap_get_state(void) { GapState state; if(gap) { diff --git a/targets/f7/ble_glue/gap.h b/targets/f7/ble_glue/gap.h index 4376570ad..0bc3397e3 100644 --- a/targets/f7/ble_glue/gap.h +++ b/targets/f7/ble_glue/gap.h @@ -69,7 +69,13 @@ typedef struct { } GapConnectionParamsRequest; typedef struct { - uint16_t adv_service_uuid; + struct { + uint8_t UUID_Type; + uint16_t Service_UUID_16; + uint8_t Service_UUID_128[16]; + } adv_service; + uint8_t mfg_data[23]; + uint8_t mfg_data_len; uint16_t appearance_char; bool bonding_mode; GapPairing pairing_method; @@ -90,8 +96,6 @@ void gap_thread_stop(void); void gap_emit_ble_beacon_status_event(bool active); -uint32_t gap_get_remote_conn_rssi(int8_t* rssi); - #ifdef __cplusplus } #endif diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c index 1d414889f..9bba0d897 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.c +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -6,6 +6,7 @@ #include #include #include +#include typedef struct { FuriHalBleProfileBase base; @@ -46,8 +47,12 @@ static void ble_profile_serial_stop(FuriHalBleProfileBase* profile) { // Up to 45 ms #define CONNECTION_INTERVAL_MAX (0x24) -static GapConfig serial_template_config = { - .adv_service_uuid = 0x3080, +static const GapConfig serial_template_config = { + .adv_service = + { + .UUID_Type = UUID_TYPE_16, + .Service_UUID_16 = 0x3080, + }, .appearance_char = 0x8600, .bonding_mode = true, .pairing_method = GapPairingPinCodeShow, @@ -71,7 +76,8 @@ static void config->adv_name, furi_hal_version_get_ble_local_device_name_ptr(), FURI_HAL_VERSION_DEVICE_NAME_LENGTH); - config->adv_service_uuid |= furi_hal_version_get_hw_color(); + config->adv_service.UUID_Type = UUID_TYPE_16; + config->adv_service.Service_UUID_16 |= furi_hal_version_get_hw_color(); } static const FuriHalBleProfileTemplate profile_callbacks = { @@ -80,7 +86,7 @@ static const FuriHalBleProfileTemplate profile_callbacks = { .get_gap_config = ble_profile_serial_get_config, }; -const FuriHalBleProfileTemplate* ble_profile_serial = &profile_callbacks; +const FuriHalBleProfileTemplate* const ble_profile_serial = &profile_callbacks; void ble_profile_serial_set_event_callback( FuriHalBleProfileBase* profile, diff --git a/targets/f7/ble_glue/profiles/serial_profile.h b/targets/f7/ble_glue/profiles/serial_profile.h index e07eaef03..7938bfc33 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.h +++ b/targets/f7/ble_glue/profiles/serial_profile.h @@ -19,7 +19,7 @@ typedef enum { typedef SerialServiceEventCallback FuriHalBtSerialCallback; /** Serial profile descriptor */ -extern const FuriHalBleProfileTemplate* ble_profile_serial; +extern const FuriHalBleProfileTemplate* const ble_profile_serial; /** Send data through BLE * diff --git a/targets/f7/fatfs/user_diskio.c b/targets/f7/fatfs/user_diskio.c index 85e5cad4f..963cb595f 100644 --- a/targets/f7/fatfs/user_diskio.c +++ b/targets/f7/fatfs/user_diskio.c @@ -9,7 +9,7 @@ static DRESULT driver_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count); static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff); -Diskio_drvTypeDef sd_fatfs_driver = { +const Diskio_drvTypeDef sd_fatfs_driver = { driver_initialize, driver_status, driver_read, diff --git a/targets/f7/fatfs/user_diskio.h b/targets/f7/fatfs/user_diskio.h index 009a17d4b..2505de704 100644 --- a/targets/f7/fatfs/user_diskio.h +++ b/targets/f7/fatfs/user_diskio.h @@ -6,7 +6,7 @@ extern "C" { #include "fatfs/ff_gen_drv.h" -extern Diskio_drvTypeDef sd_fatfs_driver; +extern const Diskio_drvTypeDef sd_fatfs_driver; #ifdef __cplusplus } diff --git a/targets/f7/furi_hal/furi_hal_bt.c b/targets/f7/furi_hal/furi_hal_bt.c index 05a066630..2c1a9367b 100644 --- a/targets/f7/furi_hal/furi_hal_bt.c +++ b/targets/f7/furi_hal/furi_hal_bt.c @@ -251,10 +251,6 @@ bool furi_hal_bt_is_active(void) { return gap_get_state() > GapStateIdle; } -bool furi_hal_bt_is_connected() { - return gap_get_state() == GapStateConnected; -} - void furi_hal_bt_start_advertising(void) { if(gap_get_state() == GapStateIdle) { gap_start_advertising(); @@ -391,30 +387,6 @@ float furi_hal_bt_get_rssi(void) { return val; } -/** fill the RSSI of the remote host of the bt connection and returns the last - * time the RSSI was updated - * -*/ -uint32_t furi_hal_bt_get_conn_rssi(uint8_t* rssi) { - int8_t ret_rssi = 0; - uint32_t since = gap_get_remote_conn_rssi(&ret_rssi); - - if(ret_rssi == 127 || since == 0) return 0; - - *rssi = (uint8_t)abs(ret_rssi); - - return since; -} - -void furi_hal_bt_reverse_mac_addr(uint8_t mac_addr[GAP_MAC_ADDR_SIZE]) { - uint8_t tmp; - for(size_t i = 0; i < GAP_MAC_ADDR_SIZE / 2; i++) { - tmp = mac_addr[i]; - mac_addr[i] = mac_addr[GAP_MAC_ADDR_SIZE - 1 - i]; - mac_addr[GAP_MAC_ADDR_SIZE - 1 - i] = tmp; - } -} - uint32_t furi_hal_bt_get_transmitted_packets(void) { uint32_t packets = 0; aci_hal_le_tx_test_packet_number(&packets); diff --git a/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c index 28aa45173..c28f6b520 100644 --- a/targets/f7/furi_hal/furi_hal_flash.c +++ b/targets/f7/furi_hal/furi_hal_flash.c @@ -51,7 +51,8 @@ // Changing furi_assert() to furi_check() brought timeout crashes // Internal storage is very slow, and "big" files will often cause a "timeout" with 3 seconds // 10 seconds seems fine, the file operations complete successfully, albeit slowly -#define FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS (10000U) /* 10 seconds */ +//#define FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS (10000U) /* 10 seconds */ +#define FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS (3000U) /* 3 seconds */ #define IS_ADDR_ALIGNED_64BITS(__VALUE__) (((__VALUE__) & 0x7U) == (0x00UL)) #define IS_FLASH_PROGRAM_ADDRESS(__VALUE__) \ diff --git a/targets/f7/furi_hal/furi_hal_gpio.c b/targets/f7/furi_hal/furi_hal_gpio.c index e504b969c..772363739 100644 --- a/targets/f7/furi_hal/furi_hal_gpio.c +++ b/targets/f7/furi_hal/furi_hal_gpio.c @@ -258,85 +258,85 @@ FURI_ALWAYS_INLINE static void furi_hal_gpio_int_call(uint16_t pin_num) { /* Interrupt handlers */ void EXTI0_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_0)) { - furi_hal_gpio_int_call(0); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_0); + furi_hal_gpio_int_call(0); } } void EXTI1_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_1)) { - furi_hal_gpio_int_call(1); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_1); + furi_hal_gpio_int_call(1); } } void EXTI2_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_2)) { - furi_hal_gpio_int_call(2); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_2); + furi_hal_gpio_int_call(2); } } void EXTI3_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_3)) { - furi_hal_gpio_int_call(3); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_3); + furi_hal_gpio_int_call(3); } } void EXTI4_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_4)) { - furi_hal_gpio_int_call(4); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_4); + furi_hal_gpio_int_call(4); } } void EXTI9_5_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_5)) { - furi_hal_gpio_int_call(5); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_5); + furi_hal_gpio_int_call(5); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_6)) { - furi_hal_gpio_int_call(6); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_6); + furi_hal_gpio_int_call(6); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_7)) { - furi_hal_gpio_int_call(7); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_7); + furi_hal_gpio_int_call(7); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_8)) { - furi_hal_gpio_int_call(8); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_8); + furi_hal_gpio_int_call(8); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_9)) { - furi_hal_gpio_int_call(9); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_9); + furi_hal_gpio_int_call(9); } } void EXTI15_10_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_10)) { - furi_hal_gpio_int_call(10); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_10); + furi_hal_gpio_int_call(10); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_11)) { - furi_hal_gpio_int_call(11); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_11); + furi_hal_gpio_int_call(11); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_12)) { - furi_hal_gpio_int_call(12); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_12); + furi_hal_gpio_int_call(12); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_13)) { - furi_hal_gpio_int_call(13); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_13); + furi_hal_gpio_int_call(13); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_14)) { - furi_hal_gpio_int_call(14); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_14); + furi_hal_gpio_int_call(14); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_15)) { - furi_hal_gpio_int_call(15); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_15); + furi_hal_gpio_int_call(15); } } diff --git a/targets/f7/furi_hal/furi_hal_i2c.c b/targets/f7/furi_hal/furi_hal_i2c.c index 71e1a5814..7eb9b4928 100644 --- a/targets/f7/furi_hal/furi_hal_i2c.c +++ b/targets/f7/furi_hal/furi_hal_i2c.c @@ -22,7 +22,7 @@ void furi_hal_i2c_init(void) { FURI_LOG_I(TAG, "Init OK"); } -void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle) { +void furi_hal_i2c_acquire(const FuriHalI2cBusHandle* handle) { furi_hal_power_insomnia_enter(); // Lock bus access handle->bus->callback(handle->bus, FuriHalI2cBusEventLock); @@ -36,7 +36,7 @@ void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle) { handle->callback(handle, FuriHalI2cBusHandleEventActivate); } -void furi_hal_i2c_release(FuriHalI2cBusHandle* handle) { +void furi_hal_i2c_release(const FuriHalI2cBusHandle* handle) { // Ensure that current handle is our handle furi_check(handle->bus->current_handle == handle); // Deactivate handle @@ -196,7 +196,7 @@ static bool furi_hal_i2c_transaction( } bool furi_hal_i2c_rx_ext( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint16_t address, bool ten_bit, uint8_t* data, @@ -213,7 +213,7 @@ bool furi_hal_i2c_rx_ext( } bool furi_hal_i2c_tx_ext( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint16_t address, bool ten_bit, const uint8_t* data, @@ -230,7 +230,7 @@ bool furi_hal_i2c_tx_ext( } bool furi_hal_i2c_tx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* data, size_t size, @@ -242,7 +242,7 @@ bool furi_hal_i2c_tx( } bool furi_hal_i2c_rx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, uint8_t* data, size_t size, @@ -254,7 +254,7 @@ bool furi_hal_i2c_rx( } bool furi_hal_i2c_trx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* tx_data, size_t tx_size, @@ -281,7 +281,10 @@ bool furi_hal_i2c_trx( timeout); } -bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint32_t timeout) { +bool furi_hal_i2c_is_device_ready( + const FuriHalI2cBusHandle* handle, + uint8_t i2c_addr, + uint32_t timeout) { furi_check(handle); furi_check(handle->bus->current_handle == handle); furi_check(timeout > 0); @@ -314,7 +317,7 @@ bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, } bool furi_hal_i2c_read_reg_8( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint8_t* data, @@ -325,7 +328,7 @@ bool furi_hal_i2c_read_reg_8( } bool furi_hal_i2c_read_reg_16( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint16_t* data, @@ -340,7 +343,7 @@ bool furi_hal_i2c_read_reg_16( } bool furi_hal_i2c_read_mem( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, uint8_t* data, @@ -352,7 +355,7 @@ bool furi_hal_i2c_read_mem( } bool furi_hal_i2c_write_reg_8( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint8_t data, @@ -368,7 +371,7 @@ bool furi_hal_i2c_write_reg_8( } bool furi_hal_i2c_write_reg_16( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint16_t data, @@ -385,7 +388,7 @@ bool furi_hal_i2c_write_reg_16( } bool furi_hal_i2c_write_mem( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, const uint8_t* data, diff --git a/targets/f7/furi_hal/furi_hal_i2c_config.c b/targets/f7/furi_hal/furi_hal_i2c_config.c index f9d88abb3..b10c53d32 100644 --- a/targets/f7/furi_hal/furi_hal_i2c_config.c +++ b/targets/f7/furi_hal/furi_hal_i2c_config.c @@ -74,7 +74,7 @@ FuriHalI2cBus furi_hal_i2c_bus_external = { }; void furi_hal_i2c_bus_handle_power_event( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, FuriHalI2cBusHandleEvent event) { if(event == FuriHalI2cBusHandleEventActivate) { furi_hal_gpio_init_ex( @@ -120,13 +120,13 @@ void furi_hal_i2c_bus_handle_power_event( } } -FuriHalI2cBusHandle furi_hal_i2c_handle_power = { +const FuriHalI2cBusHandle furi_hal_i2c_handle_power = { .bus = &furi_hal_i2c_bus_power, .callback = furi_hal_i2c_bus_handle_power_event, }; void furi_hal_i2c_bus_handle_external_event( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, FuriHalI2cBusHandleEvent event) { if(event == FuriHalI2cBusHandleEventActivate) { furi_hal_gpio_init_ex( @@ -160,7 +160,7 @@ void furi_hal_i2c_bus_handle_external_event( } } -FuriHalI2cBusHandle furi_hal_i2c_handle_external = { +const FuriHalI2cBusHandle furi_hal_i2c_handle_external = { .bus = &furi_hal_i2c_bus_external, .callback = furi_hal_i2c_bus_handle_external_event, }; diff --git a/targets/f7/furi_hal/furi_hal_i2c_config.h b/targets/f7/furi_hal/furi_hal_i2c_config.h index a8fb91835..28bd09a0a 100644 --- a/targets/f7/furi_hal/furi_hal_i2c_config.h +++ b/targets/f7/furi_hal/furi_hal_i2c_config.h @@ -17,14 +17,14 @@ extern FuriHalI2cBus furi_hal_i2c_bus_external; * Pins: PA9(SCL) / PA10(SDA), float on release * Params: 400khz */ -extern FuriHalI2cBusHandle furi_hal_i2c_handle_power; +extern const FuriHalI2cBusHandle furi_hal_i2c_handle_power; /** Handle for external i2c bus * Bus: furi_hal_i2c_bus_external * Pins: PC0(SCL) / PC1(SDA), float on release * Params: 100khz */ -extern FuriHalI2cBusHandle furi_hal_i2c_handle_external; +extern const FuriHalI2cBusHandle furi_hal_i2c_handle_external; #ifdef __cplusplus } diff --git a/targets/f7/furi_hal/furi_hal_i2c_types.h b/targets/f7/furi_hal/furi_hal_i2c_types.h index 13f361054..0a35137b0 100644 --- a/targets/f7/furi_hal/furi_hal_i2c_types.h +++ b/targets/f7/furi_hal/furi_hal_i2c_types.h @@ -25,7 +25,7 @@ typedef void (*FuriHalI2cBusEventCallback)(FuriHalI2cBus* bus, FuriHalI2cBusEven /** FuriHal i2c bus */ struct FuriHalI2cBus { I2C_TypeDef* i2c; - FuriHalI2cBusHandle* current_handle; + const FuriHalI2cBusHandle* current_handle; FuriHalI2cBusEventCallback callback; }; @@ -37,7 +37,7 @@ typedef enum { /** FuriHal i2c handle event callback */ typedef void (*FuriHalI2cBusHandleEventCallback)( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, FuriHalI2cBusHandleEvent event); /** FuriHal i2c handle */ diff --git a/targets/f7/furi_hal/furi_hal_light.c b/targets/f7/furi_hal/furi_hal_light.c index 621478d14..b9f34e445 100644 --- a/targets/f7/furi_hal/furi_hal_light.c +++ b/targets/f7/furi_hal/furi_hal_light.c @@ -3,6 +3,7 @@ #include #include #include +#include #define LED_CURRENT_RED (50u) #define LED_CURRENT_GREEN (50u) @@ -45,6 +46,9 @@ void furi_hal_light_set(Light light, uint8_t value) { uint8_t prev = lp5562_get_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite); lp5562_execute_ramp( &furi_hal_i2c_handle_power, LP5562Engine1, LP5562ChannelWhite, prev, value, 100); + // --- RGB BACKLIGHT --- + rgb_backlight_update(value / 255.0f); + // --- RGB BACKLIGHT END --- } furi_hal_i2c_release(&furi_hal_i2c_handle_power); } diff --git a/targets/f7/furi_hal/furi_hal_nfc.c b/targets/f7/furi_hal/furi_hal_nfc.c index ee7a04e45..d8dc0c618 100644 --- a/targets/f7/furi_hal/furi_hal_nfc.c +++ b/targets/f7/furi_hal/furi_hal_nfc.c @@ -8,7 +8,7 @@ #define TAG "FuriHalNfc" -const FuriHalNfcTechBase* furi_hal_nfc_tech[FuriHalNfcTechNum] = { +const FuriHalNfcTechBase* const furi_hal_nfc_tech[FuriHalNfcTechNum] = { [FuriHalNfcTechIso14443a] = &furi_hal_nfc_iso14443a, [FuriHalNfcTechIso14443b] = &furi_hal_nfc_iso14443b, [FuriHalNfcTechIso15693] = &furi_hal_nfc_iso15693, @@ -18,7 +18,7 @@ const FuriHalNfcTechBase* furi_hal_nfc_tech[FuriHalNfcTechNum] = { FuriHalNfc furi_hal_nfc; -static FuriHalNfcError furi_hal_nfc_turn_on_osc(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_turn_on_osc(const FuriHalSpiBusHandle* handle) { FuriHalNfcError error = FuriHalNfcErrorNone; furi_hal_nfc_event_start(); @@ -53,7 +53,7 @@ FuriHalNfcError furi_hal_nfc_is_hal_ready(void) { error = furi_hal_nfc_acquire(); if(error != FuriHalNfcErrorNone) break; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; uint8_t chip_id = 0; st25r3916_read_reg(handle, ST25R3916_REG_IC_IDENTITY, &chip_id); if((chip_id & ST25R3916_REG_IC_IDENTITY_ic_type_mask) != @@ -83,7 +83,7 @@ FuriHalNfcError furi_hal_nfc_init(void) { furi_hal_nfc_low_power_mode_start(); } - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; // Set default state st25r3916_direct_cmd(handle, ST25R3916_CMD_SET_DEFAULT); // Increase IO driver strength of MISO and IRQ @@ -282,7 +282,7 @@ FuriHalNfcError furi_hal_nfc_release(void) { FuriHalNfcError furi_hal_nfc_low_power_mode_start(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); st25r3916_clear_reg_bits( @@ -300,7 +300,7 @@ FuriHalNfcError furi_hal_nfc_low_power_mode_start(void) { FuriHalNfcError furi_hal_nfc_low_power_mode_stop(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; do { furi_hal_nfc_init_gpio_isr(); @@ -318,7 +318,7 @@ FuriHalNfcError furi_hal_nfc_low_power_mode_stop(void) { return error; } -static FuriHalNfcError furi_hal_nfc_poller_init_common(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_poller_init_common(const FuriHalSpiBusHandle* handle) { // Disable wake up st25r3916_clear_reg_bits(handle, ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); // Enable correlator @@ -339,7 +339,7 @@ static FuriHalNfcError furi_hal_nfc_poller_init_common(FuriHalSpiBusHandle* hand return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_listener_init_common(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_listener_init_common(const FuriHalSpiBusHandle* handle) { UNUSED(handle); // No common listener configuration return FuriHalNfcErrorNone; @@ -349,7 +349,7 @@ FuriHalNfcError furi_hal_nfc_set_mode(FuriHalNfcMode mode, FuriHalNfcTech tech) furi_check(mode < FuriHalNfcModeNum); furi_check(tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; FuriHalNfcError error = FuriHalNfcErrorNone; @@ -375,7 +375,7 @@ FuriHalNfcError furi_hal_nfc_set_mode(FuriHalNfcMode mode, FuriHalNfcTech tech) FuriHalNfcError furi_hal_nfc_reset_mode(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); @@ -415,7 +415,7 @@ FuriHalNfcError furi_hal_nfc_reset_mode(void) { FuriHalNfcError furi_hal_nfc_field_detect_start(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_write_reg( handle, @@ -429,7 +429,7 @@ FuriHalNfcError furi_hal_nfc_field_detect_start(void) { FuriHalNfcError furi_hal_nfc_field_detect_stop(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_clear_reg_bits( handle, @@ -441,7 +441,7 @@ FuriHalNfcError furi_hal_nfc_field_detect_stop(void) { bool furi_hal_nfc_field_is_present(void) { bool is_present = false; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; if(st25r3916_check_reg( handle, @@ -456,7 +456,7 @@ bool furi_hal_nfc_field_is_present(void) { FuriHalNfcError furi_hal_nfc_poller_field_on(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; if(!st25r3916_check_reg( handle, @@ -476,7 +476,7 @@ FuriHalNfcError furi_hal_nfc_poller_field_on(void) { } FuriHalNfcError furi_hal_nfc_poller_tx_common( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { furi_check(tx_data); @@ -508,7 +508,7 @@ FuriHalNfcError furi_hal_nfc_poller_tx_common( } FuriHalNfcError furi_hal_nfc_common_fifo_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { FuriHalNfcError err = FuriHalNfcErrorNone; @@ -523,7 +523,7 @@ FuriHalNfcError furi_hal_nfc_common_fifo_tx( FuriHalNfcError furi_hal_nfc_poller_tx(const uint8_t* tx_data, size_t tx_bits) { furi_check(furi_hal_nfc.mode == FuriHalNfcModePoller); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.tx(handle, tx_data, tx_bits); } @@ -531,7 +531,7 @@ FuriHalNfcError furi_hal_nfc_poller_tx(const uint8_t* tx_data, size_t tx_bits) { FuriHalNfcError furi_hal_nfc_poller_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { furi_check(furi_hal_nfc.mode == FuriHalNfcModePoller); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.rx(handle, rx_data, rx_data_size, rx_bits); } @@ -556,12 +556,12 @@ FuriHalNfcError furi_hal_nfc_listener_tx(const uint8_t* tx_data, size_t tx_bits) furi_check(furi_hal_nfc.mode == FuriHalNfcModeListener); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.tx(handle, tx_data, tx_bits); } FuriHalNfcError furi_hal_nfc_common_fifo_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { @@ -581,13 +581,13 @@ FuriHalNfcError furi_hal_nfc_listener_rx(uint8_t* rx_data, size_t rx_data_size, furi_check(furi_hal_nfc.mode == FuriHalNfcModeListener); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.rx( handle, rx_data, rx_data_size, rx_bits); } FuriHalNfcError furi_hal_nfc_trx_reset(void) { - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); @@ -598,7 +598,7 @@ FuriHalNfcError furi_hal_nfc_listener_sleep(void) { furi_check(furi_hal_nfc.mode == FuriHalNfcModeListener); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.sleep(handle); } @@ -607,13 +607,13 @@ FuriHalNfcError furi_hal_nfc_listener_idle(void) { furi_check(furi_hal_nfc.mode == FuriHalNfcModeListener); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.idle(handle); } FuriHalNfcError furi_hal_nfc_listener_enable_rx(void) { - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA); diff --git a/targets/f7/furi_hal/furi_hal_nfc_event.c b/targets/f7/furi_hal/furi_hal_nfc_event.c index 9bcd2f1fe..56681b4fe 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_event.c +++ b/targets/f7/furi_hal/furi_hal_nfc_event.c @@ -48,7 +48,7 @@ FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms) { if(event_flag != (unsigned)FuriFlagErrorTimeout) { if(event_flag & FuriHalNfcEventInternalTypeIrq) { furi_thread_flags_clear(FuriHalNfcEventInternalTypeIrq); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; uint32_t irq = furi_hal_nfc_get_irq(handle); if(irq & ST25R3916_IRQ_MASK_OSC) { event |= FuriHalNfcEventOscOn; @@ -101,7 +101,7 @@ FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms) { } bool furi_hal_nfc_event_wait_for_specific_irq( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint32_t mask, uint32_t timeout_ms) { furi_check(furi_hal_nfc_event); diff --git a/targets/f7/furi_hal/furi_hal_nfc_felica.c b/targets/f7/furi_hal/furi_hal_nfc_felica.c index 6c3f55525..1267b7b13 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_felica.c +++ b/targets/f7/furi_hal/furi_hal_nfc_felica.c @@ -18,7 +18,7 @@ typedef struct { } FuriHalFelicaPtMemory; #pragma pack(pop) -static FuriHalNfcError furi_hal_nfc_felica_poller_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_felica_poller_init(const FuriHalSpiBusHandle* handle) { // Enable Felica mode, AM modulation st25r3916_change_reg_bits( handle, @@ -61,13 +61,13 @@ static FuriHalNfcError furi_hal_nfc_felica_poller_init(FuriHalSpiBusHandle* hand return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_felica_listener_init(const FuriHalSpiBusHandle* handle) { furi_assert(handle); st25r3916_direct_cmd(handle, ST25R3916_CMD_SET_DEFAULT); st25r3916_write_reg( @@ -141,7 +141,7 @@ static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* ha return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_felica_listener_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_felica_listener_deinit(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; } @@ -154,19 +154,19 @@ static FuriHalNfcEvent furi_hal_nfc_felica_listener_wait_event(uint32_t timeout_ } FuriHalNfcError furi_hal_nfc_felica_listener_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { furi_hal_nfc_common_fifo_tx(handle, tx_data, tx_bits); return FuriHalNfcErrorNone; } -FuriHalNfcError furi_hal_nfc_felica_listener_sleep(FuriHalSpiBusHandle* handle) { +FuriHalNfcError furi_hal_nfc_felica_listener_sleep(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; } -FuriHalNfcError furi_hal_nfc_felica_listener_idle(FuriHalSpiBusHandle* handle) { +FuriHalNfcError furi_hal_nfc_felica_listener_idle(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; } @@ -182,7 +182,7 @@ FuriHalNfcError furi_hal_nfc_felica_listener_set_sensf_res_data( furi_check(idm_len == FURI_HAL_FELICA_IDM_PMM_LENGTH); furi_check(pmm_len == FURI_HAL_FELICA_IDM_PMM_LENGTH); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; // Write PT Memory FuriHalFelicaPtMemory pt; pt.system_code = sys_code; diff --git a/targets/f7/furi_hal/furi_hal_nfc_i.h b/targets/f7/furi_hal/furi_hal_nfc_i.h index 084196451..5b0f8e68d 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_i.h +++ b/targets/f7/furi_hal/furi_hal_nfc_i.h @@ -104,7 +104,7 @@ void furi_hal_nfc_timers_deinit(void); * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. * @returns bitmask of zero or more occurred interrupts. */ -uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle); +uint32_t furi_hal_nfc_get_irq(const FuriHalSpiBusHandle* handle); /** * @brief Wait until a specified type of interrupt occurs. @@ -115,7 +115,7 @@ uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle); * @returns true if specified interrupt(s) have occured within timeout, false otherwise. */ bool furi_hal_nfc_event_wait_for_specific_irq( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint32_t mask, uint32_t timeout_ms); @@ -137,7 +137,7 @@ FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms); * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ -FuriHalNfcError furi_hal_nfc_common_listener_rx_start(FuriHalSpiBusHandle* handle); +FuriHalNfcError furi_hal_nfc_common_listener_rx_start(const FuriHalSpiBusHandle* handle); /** * @brief Transmit data using on-chip FIFO. @@ -150,7 +150,7 @@ FuriHalNfcError furi_hal_nfc_common_listener_rx_start(FuriHalSpiBusHandle* handl * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError furi_hal_nfc_common_fifo_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits); @@ -166,7 +166,7 @@ FuriHalNfcError furi_hal_nfc_common_fifo_tx( * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError furi_hal_nfc_common_fifo_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); @@ -182,7 +182,7 @@ FuriHalNfcError furi_hal_nfc_common_fifo_rx( * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError furi_hal_nfc_poller_tx_common( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits); diff --git a/targets/f7/furi_hal/furi_hal_nfc_irq.c b/targets/f7/furi_hal/furi_hal_nfc_irq.c index 90373955f..50a0139fd 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_irq.c +++ b/targets/f7/furi_hal/furi_hal_nfc_irq.c @@ -8,7 +8,7 @@ static void furi_hal_nfc_int_callback(void* context) { furi_hal_nfc_event_set(FuriHalNfcEventInternalTypeIrq); } -uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle) { +uint32_t furi_hal_nfc_get_irq(const FuriHalSpiBusHandle* handle) { uint32_t irq = 0; while(furi_hal_gpio_read_port_pin(gpio_nfc_irq_rfid_pull.port, gpio_nfc_irq_rfid_pull.pin)) { irq |= st25r3916_get_irq(handle); diff --git a/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c b/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c index 1ef23dfa4..be7a17264 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c +++ b/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c @@ -13,7 +13,7 @@ static Iso14443_3aSignal* iso14443_3a_signal = NULL; -static FuriHalNfcError furi_hal_nfc_iso14443a_common_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443a_common_init(const FuriHalSpiBusHandle* handle) { // Common NFC-A settings, 106 kbps // 1st stage zero = 600kHz, 3rd stage zero = 200 kHz @@ -40,7 +40,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443a_common_init(FuriHalSpiBusHandle* h return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_iso14443a_poller_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443a_poller_init(const FuriHalSpiBusHandle* handle) { // Enable ISO14443A mode, OOK modulation st25r3916_change_reg_bits( handle, @@ -57,7 +57,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443a_poller_init(FuriHalSpiBusHandle* h return furi_hal_nfc_iso14443a_common_init(handle); } -static FuriHalNfcError furi_hal_nfc_iso14443a_poller_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443a_poller_deinit(const FuriHalSpiBusHandle* handle) { st25r3916_change_reg_bits( handle, ST25R3916_REG_ISO14443A_NFC, @@ -67,7 +67,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443a_poller_deinit(FuriHalSpiBusHandle* return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_iso14443a_listener_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443a_listener_init(const FuriHalSpiBusHandle* handle) { furi_check(iso14443_3a_signal == NULL); iso14443_3a_signal = iso14443_3a_signal_alloc(&gpio_spi_r_mosi); @@ -105,7 +105,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443a_listener_init(FuriHalSpiBusHandle* return furi_hal_nfc_iso14443a_common_init(handle); } -static FuriHalNfcError furi_hal_nfc_iso14443a_listener_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443a_listener_deinit(const FuriHalSpiBusHandle* handle) { UNUSED(handle); if(iso14443_3a_signal) { @@ -118,7 +118,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443a_listener_deinit(FuriHalSpiBusHandl static FuriHalNfcEvent furi_hal_nfc_iso14443_3a_listener_wait_event(uint32_t timeout_ms) { FuriHalNfcEvent event = furi_hal_nfc_wait_event_common(timeout_ms); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; if(event & FuriHalNfcEventListenerActive) { st25r3916_set_reg_bits( @@ -131,7 +131,7 @@ static FuriHalNfcEvent furi_hal_nfc_iso14443_3a_listener_wait_event(uint32_t tim FuriHalNfcError furi_hal_nfc_iso14443a_poller_trx_short_frame(FuriHalNfcaShortFrame frame) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; // Disable crc check st25r3916_set_reg_bits(handle, ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); @@ -185,7 +185,7 @@ FuriHalNfcError furi_check(tx_data); FuriHalNfcError err = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; // Prepare tx st25r3916_direct_cmd(handle, ST25R3916_CMD_CLEAR_FIFO); @@ -220,7 +220,7 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_set_col_res_data( FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; // Set 4 or 7 bytes UID if(uid_len == 4) { @@ -255,7 +255,7 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_set_col_res_data( } FuriHalNfcError furi_hal_nfc_iso4443a_listener_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { FuriHalNfcError error = FuriHalNfcErrorNone; @@ -284,7 +284,7 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( furi_check(iso14443_3a_signal); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSPARENT_MODE); // Reconfigure gpio for Transparent mode @@ -303,7 +303,7 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( return FuriHalNfcErrorNone; } -FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_sleep(FuriHalSpiBusHandle* handle) { +FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_sleep(const FuriHalSpiBusHandle* handle) { // Enable auto collision resolution st25r3916_clear_reg_bits( handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); @@ -313,7 +313,7 @@ FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_sleep(FuriHalSpiBusHandle* han return FuriHalNfcErrorNone; } -FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_idle(FuriHalSpiBusHandle* handle) { +FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_idle(const FuriHalSpiBusHandle* handle) { // Enable auto collision resolution st25r3916_clear_reg_bits( handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); diff --git a/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c b/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c index bb1a63515..315223e2f 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c +++ b/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c @@ -1,7 +1,7 @@ #include "furi_hal_nfc_i.h" #include "furi_hal_nfc_tech_i.h" -static FuriHalNfcError furi_hal_nfc_iso14443b_common_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443b_common_init(const FuriHalSpiBusHandle* handle) { // Common NFC-B settings, 106kbps // 1st stage zero = 60kHz, 3rd stage zero = 200 kHz @@ -40,7 +40,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443b_common_init(FuriHalSpiBusHandle* h return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_iso14443b_poller_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443b_poller_init(const FuriHalSpiBusHandle* handle) { // Enable ISO14443B mode, AM modulation st25r3916_change_reg_bits( handle, @@ -84,7 +84,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443b_poller_init(FuriHalSpiBusHandle* h return furi_hal_nfc_iso14443b_common_init(handle); } -static FuriHalNfcError furi_hal_nfc_iso14443b_poller_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443b_poller_deinit(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; } diff --git a/targets/f7/furi_hal/furi_hal_nfc_iso15693.c b/targets/f7/furi_hal/furi_hal_nfc_iso15693.c index 0fd5dbca5..028c60927 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_iso15693.c +++ b/targets/f7/furi_hal/furi_hal_nfc_iso15693.c @@ -74,7 +74,7 @@ static void furi_hal_nfc_iso15693_poller_free(FuriHalNfcIso15693Poller* instance free(instance); } -static FuriHalNfcError furi_hal_nfc_iso15693_common_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso15693_common_init(const FuriHalSpiBusHandle* handle) { // Common NFC-V settings, 26.48 kbps // 1st stage zero = 12 kHz, 3rd stage zero = 80 kHz, low-pass = 600 kHz @@ -112,7 +112,7 @@ static FuriHalNfcError furi_hal_nfc_iso15693_common_init(FuriHalSpiBusHandle* ha return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_iso15693_poller_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso15693_poller_init(const FuriHalSpiBusHandle* handle) { furi_check(furi_hal_nfc_iso15693_poller == NULL); furi_hal_nfc_iso15693_poller = furi_hal_nfc_iso15693_poller_alloc(); @@ -141,7 +141,7 @@ static FuriHalNfcError furi_hal_nfc_iso15693_poller_init(FuriHalSpiBusHandle* ha return furi_hal_nfc_iso15693_common_init(handle); } -static FuriHalNfcError furi_hal_nfc_iso15693_poller_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso15693_poller_deinit(const FuriHalSpiBusHandle* handle) { UNUSED(handle); furi_check(furi_hal_nfc_iso15693_poller); @@ -238,7 +238,7 @@ static FuriHalNfcError iso15693_3_poller_decode_frame( } static FuriHalNfcError furi_hal_nfc_iso15693_poller_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { FuriHalNfcIso15693Poller* instance = furi_hal_nfc_iso15693_poller; @@ -252,7 +252,7 @@ static FuriHalNfcError furi_hal_nfc_iso15693_poller_tx( } static FuriHalNfcError furi_hal_nfc_iso15693_poller_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { @@ -284,14 +284,16 @@ static FuriHalNfcError furi_hal_nfc_iso15693_poller_rx( return error; } -static void furi_hal_nfc_iso15693_listener_transparent_mode_enter(FuriHalSpiBusHandle* handle) { +static void + furi_hal_nfc_iso15693_listener_transparent_mode_enter(const FuriHalSpiBusHandle* handle) { st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSPARENT_MODE); furi_hal_spi_bus_handle_deinit(handle); furi_hal_nfc_deinit_gpio_isr(); } -static void furi_hal_nfc_iso15693_listener_transparent_mode_exit(FuriHalSpiBusHandle* handle) { +static void + furi_hal_nfc_iso15693_listener_transparent_mode_exit(const FuriHalSpiBusHandle* handle) { // Configure gpio back to SPI and exit transparent mode furi_hal_nfc_init_gpio_isr(); furi_hal_spi_bus_handle_init(handle); @@ -299,7 +301,7 @@ static void furi_hal_nfc_iso15693_listener_transparent_mode_exit(FuriHalSpiBusHa st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA); } -static FuriHalNfcError furi_hal_nfc_iso15693_listener_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso15693_listener_init(const FuriHalSpiBusHandle* handle) { furi_check(furi_hal_nfc_iso15693_listener == NULL); furi_hal_nfc_iso15693_listener = furi_hal_nfc_iso15693_listener_alloc(); @@ -328,7 +330,7 @@ static FuriHalNfcError furi_hal_nfc_iso15693_listener_init(FuriHalSpiBusHandle* return error; } -static FuriHalNfcError furi_hal_nfc_iso15693_listener_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso15693_listener_deinit(const FuriHalSpiBusHandle* handle) { furi_check(furi_hal_nfc_iso15693_listener); furi_hal_nfc_iso15693_listener_transparent_mode_exit(handle); @@ -387,7 +389,7 @@ static FuriHalNfcEvent furi_hal_nfc_iso15693_wait_event(uint32_t timeout_ms) { } static FuriHalNfcError furi_hal_nfc_iso15693_listener_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { UNUSED(handle); @@ -425,7 +427,7 @@ FuriHalNfcError furi_hal_nfc_iso15693_force_1outof256(void) { } static FuriHalNfcError furi_hal_nfc_iso15693_listener_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { @@ -443,7 +445,7 @@ static FuriHalNfcError furi_hal_nfc_iso15693_listener_rx( return FuriHalNfcErrorNone; } -FuriHalNfcError furi_hal_nfc_iso15693_listener_sleep(FuriHalSpiBusHandle* handle) { +FuriHalNfcError furi_hal_nfc_iso15693_listener_sleep(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; diff --git a/targets/f7/furi_hal/furi_hal_nfc_tech_i.h b/targets/f7/furi_hal/furi_hal_nfc_tech_i.h index e36dc852e..4a62f67c9 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_tech_i.h +++ b/targets/f7/furi_hal/furi_hal_nfc_tech_i.h @@ -25,7 +25,7 @@ extern "C" { * @param[in,out] handle pointer to the NFC chip SPI handle. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ -typedef FuriHalNfcError (*FuriHalNfcChipConfig)(FuriHalSpiBusHandle* handle); +typedef FuriHalNfcError (*FuriHalNfcChipConfig)(const FuriHalSpiBusHandle* handle); /** * @brief Transmit data using technology-specific framing and timings. @@ -36,7 +36,7 @@ typedef FuriHalNfcError (*FuriHalNfcChipConfig)(FuriHalSpiBusHandle* handle); * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ typedef FuriHalNfcError ( - *FuriHalNfcTx)(FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits); + *FuriHalNfcTx)(const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits); /** * @brief Receive data using technology-specific framing and timings. @@ -48,7 +48,7 @@ typedef FuriHalNfcError ( * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ typedef FuriHalNfcError (*FuriHalNfcRx)( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); @@ -69,7 +69,7 @@ typedef FuriHalNfcEvent (*FuriHalNfcWaitEvent)(uint32_t timeout_ms); * @param[in,out] handle pointer to the NFC chip SPI handle. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ -typedef FuriHalNfcError (*FuriHalNfcSleep)(FuriHalSpiBusHandle* handle); +typedef FuriHalNfcError (*FuriHalNfcSleep)(const FuriHalSpiBusHandle* handle); /** * @brief Go to idle in listener mode. @@ -79,7 +79,7 @@ typedef FuriHalNfcError (*FuriHalNfcSleep)(FuriHalSpiBusHandle* handle); * @param[in,out] handle pointer to the NFC chip SPI handle. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ -typedef FuriHalNfcError (*FuriHalNfcIdle)(FuriHalSpiBusHandle* handle); +typedef FuriHalNfcError (*FuriHalNfcIdle)(const FuriHalSpiBusHandle* handle); /** * @brief Technology-specific compenstaion values for pollers. @@ -160,7 +160,7 @@ extern const FuriHalNfcTechBase furi_hal_nfc_felica; * This variable is defined in furi_hal_nfc.c. It will need to be modified * in case when a new technology is to be added. */ -extern const FuriHalNfcTechBase* furi_hal_nfc_tech[]; +extern const FuriHalNfcTechBase* const furi_hal_nfc_tech[]; #ifdef __cplusplus } diff --git a/targets/f7/furi_hal/furi_hal_pwm.h b/targets/f7/furi_hal/furi_hal_pwm.h index 16acca05e..30a5467d2 100644 --- a/targets/f7/furi_hal/furi_hal_pwm.h +++ b/targets/f7/furi_hal/furi_hal_pwm.h @@ -12,6 +12,7 @@ extern "C" { #include typedef enum { + FuriHalPwmOutputIdNone, FuriHalPwmOutputIdTim1PA7, FuriHalPwmOutputIdLptim2PA4, } FuriHalPwmOutputId; diff --git a/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c index 123ebc420..a6ba0b083 100644 --- a/targets/f7/furi_hal/furi_hal_resources.c +++ b/targets/f7/furi_hal/furi_hal_resources.c @@ -73,6 +73,7 @@ const GpioPinRecord gpio_pins[] = { {.pin = &gpio_ext_pa7, .name = "PA7", .channel = FuriHalAdcChannel12, + .pwm_output = FuriHalPwmOutputIdTim1PA7, .number = 2, .debug = false}, {.pin = &gpio_ext_pa6, @@ -83,6 +84,7 @@ const GpioPinRecord gpio_pins[] = { {.pin = &gpio_ext_pa4, .name = "PA4", .channel = FuriHalAdcChannel9, + .pwm_output = FuriHalPwmOutputIdLptim2PA4, .number = 4, .debug = false}, {.pin = &gpio_ext_pb3, diff --git a/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h index ec8794cc1..3e8501b86 100644 --- a/targets/f7/furi_hal/furi_hal_resources.h +++ b/targets/f7/furi_hal/furi_hal_resources.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -40,6 +41,7 @@ typedef struct { const GpioPin* pin; const char* name; const FuriHalAdcChannel channel; + const FuriHalPwmOutputId pwm_output; const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/targets/f7/furi_hal/furi_hal_sd.c b/targets/f7/furi_hal/furi_hal_sd.c index eca5b6da9..152192736 100644 --- a/targets/f7/furi_hal/furi_hal_sd.c +++ b/targets/f7/furi_hal/furi_hal_sd.c @@ -204,7 +204,7 @@ typedef struct { } SD_CardInfo; /** Pointer to currently used SPI Handle */ -FuriHalSpiBusHandle* furi_hal_sd_spi_handle = NULL; +const FuriHalSpiBusHandle* furi_hal_sd_spi_handle = NULL; static inline void sd_spi_select_card(void) { furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, false); diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c index 3f6b575a6..8ad9794a8 100644 --- a/targets/f7/furi_hal/furi_hal_serial.c +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -120,7 +120,7 @@ static void furi_hal_serial_usart_irq_callback(void* context) { } if(USART1->ISR & USART_ISR_PE) { USART1->ICR = USART_ICR_PECF; - event |= FuriHalSerialRxEventFrameError; + event |= FuriHalSerialRxEventParityError; } if(furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_ptr == NULL) { @@ -321,7 +321,7 @@ static void furi_hal_serial_lpuart_irq_callback(void* context) { } if(LPUART1->ISR & USART_ISR_PE) { LPUART1->ICR = USART_ICR_PECF; - event |= FuriHalSerialRxEventFrameError; + event |= FuriHalSerialRxEventParityError; } if(furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_ptr == NULL) { @@ -605,6 +605,92 @@ void furi_hal_serial_set_br(FuriHalSerialHandle* handle, uint32_t baud) { } } +// Avoid duplicating look-up tables between USART and LPUART +static_assert(LL_LPUART_DATAWIDTH_7B == LL_USART_DATAWIDTH_7B); +static_assert(LL_LPUART_DATAWIDTH_8B == LL_USART_DATAWIDTH_8B); +static_assert(LL_LPUART_DATAWIDTH_9B == LL_USART_DATAWIDTH_9B); +static_assert(LL_LPUART_PARITY_NONE == LL_USART_PARITY_NONE); +static_assert(LL_LPUART_PARITY_EVEN == LL_USART_PARITY_EVEN); +static_assert(LL_LPUART_PARITY_ODD == LL_USART_PARITY_ODD); +static_assert(LL_LPUART_STOPBITS_1 == LL_USART_STOPBITS_1); +static_assert(LL_LPUART_STOPBITS_2 == LL_USART_STOPBITS_2); + +static const uint32_t serial_data_bits_lut[] = { + [FuriHalSerialDataBits7] = LL_USART_DATAWIDTH_7B, + [FuriHalSerialDataBits8] = LL_USART_DATAWIDTH_8B, + [FuriHalSerialDataBits9] = LL_USART_DATAWIDTH_9B, +}; + +static const uint32_t serial_parity_lut[] = { + [FuriHalSerialParityNone] = LL_USART_PARITY_NONE, + [FuriHalSerialParityEven] = LL_USART_PARITY_EVEN, + [FuriHalSerialParityOdd] = LL_USART_PARITY_ODD, +}; + +static const uint32_t serial_stop_bits_lut[] = { + [FuriHalSerialStopBits0_5] = LL_USART_STOPBITS_0_5, + [FuriHalSerialStopBits1] = LL_USART_STOPBITS_1, + [FuriHalSerialStopBits1_5] = LL_USART_STOPBITS_1_5, + [FuriHalSerialStopBits2] = LL_USART_STOPBITS_2, +}; + +static void furi_hal_serial_usart_configure_framing( + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits) { + LL_USART_SetDataWidth(USART1, serial_data_bits_lut[data_bits]); + LL_USART_SetParity(USART1, serial_parity_lut[parity]); + LL_USART_SetStopBitsLength(USART1, serial_stop_bits_lut[stop_bits]); +} + +static void furi_hal_serial_lpuart_configure_framing( + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits) { + LL_LPUART_SetDataWidth(LPUART1, serial_data_bits_lut[data_bits]); + LL_LPUART_SetParity(LPUART1, serial_parity_lut[parity]); + // Unsupported non-whole stop bit numbers have been furi_check'ed away + LL_LPUART_SetStopBitsLength(LPUART1, serial_stop_bits_lut[stop_bits]); +} + +void furi_hal_serial_configure_framing( + FuriHalSerialHandle* handle, + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits) { + furi_check(handle); + + // Unsupported combinations + if(data_bits == FuriHalSerialDataBits9) furi_check(parity == FuriHalSerialParityNone); + if(data_bits == FuriHalSerialDataBits6) furi_check(parity != FuriHalSerialParityNone); + + // Extend data word to account for parity bit + if(parity != FuriHalSerialParityNone) data_bits++; + + if(handle->id == FuriHalSerialIdUsart) { + if(LL_USART_IsEnabled(USART1)) { + // Wait for transfer complete flag + while(!LL_USART_IsActiveFlag_TC(USART1)) + ; + LL_USART_Disable(USART1); + furi_hal_serial_usart_configure_framing(data_bits, parity, stop_bits); + LL_USART_Enable(USART1); + } + } else if(handle->id == FuriHalSerialIdLpuart) { + // Unsupported configurations + furi_check(stop_bits == FuriHalSerialStopBits1 || stop_bits == FuriHalSerialStopBits2); + + if(LL_LPUART_IsEnabled(LPUART1)) { + // Wait for transfer complete flag + while(!LL_LPUART_IsActiveFlag_TC(LPUART1)) + ; + LL_LPUART_Disable(LPUART1); + furi_hal_serial_lpuart_configure_framing(data_bits, parity, stop_bits); + LL_LPUART_Enable(LPUART1); + } + } +} + void furi_hal_serial_deinit(FuriHalSerialHandle* handle) { furi_check(handle); furi_hal_serial_async_rx_configure(handle, NULL, NULL); @@ -950,13 +1036,3 @@ const GpioPin* return furi_hal_serial_config[handle->id].gpio[direction]; } - -void furi_hal_serial_send_break(FuriHalSerialHandle* handle) { - furi_check(handle); - - if(handle->id == FuriHalSerialIdUsart) { - LL_USART_RequestBreakSending(USART1); - } else { - LL_LPUART_RequestBreakSending(LPUART1); - } -} diff --git a/targets/f7/furi_hal/furi_hal_serial.h b/targets/f7/furi_hal/furi_hal_serial.h index 6dad8ec31..ca8860a60 100644 --- a/targets/f7/furi_hal/furi_hal_serial.h +++ b/targets/f7/furi_hal/furi_hal_serial.h @@ -16,7 +16,9 @@ extern "C" { /** Initialize Serial * - * Configures GPIO, configures and enables transceiver. + * Configures GPIO, configures and enables transceiver. Default framing settings + * are used: 8 data bits, no parity, 1 stop bit. Override them with + * `furi_hal_serial_configure_framing`. * * @param handle Serial handle * @param baud baud rate @@ -64,6 +66,20 @@ bool furi_hal_serial_is_baud_rate_supported(FuriHalSerialHandle* handle, uint32_ */ void furi_hal_serial_set_br(FuriHalSerialHandle* handle, uint32_t baud); +/** + * @brief Configures framing of a serial interface + * + * @param handle Serial handle + * @param data_bits Data bits + * @param parity Parity + * @param stop_bits Stop bits + */ +void furi_hal_serial_configure_framing( + FuriHalSerialHandle* handle, + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits); + /** Transmits data in semi-blocking mode * * Fills transmission pipe with data, returns as soon as all bytes from buffer @@ -93,6 +109,7 @@ typedef enum { FuriHalSerialRxEventFrameError = (1 << 2), /**< Framing Error: incorrect frame detected */ FuriHalSerialRxEventNoiseError = (1 << 3), /**< Noise Error: noise on the line detected */ FuriHalSerialRxEventOverrunError = (1 << 4), /**< Overrun Error: no space for received data */ + FuriHalSerialRxEventParityError = (1 << 5), /**< Parity Error: incorrect parity bit received */ } FuriHalSerialRxEvent; /** Receive callback @@ -172,7 +189,7 @@ typedef void (*FuriHalSerialDmaRxCallback)( void* context); /** - * @brief Enable an input/output directon + * @brief Enable an input/output direction * * Takes over the respective pin by reconfiguring it to * the appropriate alternative function. @@ -185,7 +202,7 @@ void furi_hal_serial_enable_direction( FuriHalSerialDirection direction); /** - * @brief Disable an input/output directon + * @brief Disable an input/output direction * * Releases the respective pin by reconfiguring it to * initial state, making possible its use for other purposes. @@ -239,12 +256,6 @@ void furi_hal_serial_dma_rx_stop(FuriHalSerialHandle* handle); */ size_t furi_hal_serial_dma_rx(FuriHalSerialHandle* handle, uint8_t* data, size_t len); -/** Send a break sequence (low level for the whole character duration) - * - * @param handle Serial handle - */ -void furi_hal_serial_send_break(FuriHalSerialHandle* handle); - #ifdef __cplusplus } #endif diff --git a/targets/f7/furi_hal/furi_hal_serial_types.h b/targets/f7/furi_hal/furi_hal_serial_types.h index 9f10102e1..a427765dd 100644 --- a/targets/f7/furi_hal/furi_hal_serial_types.h +++ b/targets/f7/furi_hal/furi_hal_serial_types.h @@ -19,4 +19,39 @@ typedef enum { FuriHalSerialDirectionMax, } FuriHalSerialDirection; +/** + * @brief Actual data bits, i.e. not including start/stop and parity bits + * @note 6 data bits are only permitted when parity is enabled + * @note 9 data bits are only permitted when parity is disabled + */ +typedef enum { + FuriHalSerialDataBits6, + FuriHalSerialDataBits7, + FuriHalSerialDataBits8, + FuriHalSerialDataBits9, + + FuriHalSerialDataBitsMax, +} FuriHalSerialDataBits; + +typedef enum { + FuriHalSerialParityNone, + FuriHalSerialParityEven, + FuriHalSerialParityOdd, + + FuriHalSerialParityMax, +} FuriHalSerialParity; + +/** + * @brief Stop bit length + * @note LPUART only supports whole stop bit lengths (i.e. 1 and 2, but not 0.5 and 1.5) + */ +typedef enum { + FuriHalSerialStopBits0_5, + FuriHalSerialStopBits1, + FuriHalSerialStopBits1_5, + FuriHalSerialStopBits2, + + FuriHalSerialStopBits2Max, +} FuriHalSerialStopBits; + typedef struct FuriHalSerialHandle FuriHalSerialHandle; diff --git a/targets/f7/furi_hal/furi_hal_spi.c b/targets/f7/furi_hal/furi_hal_spi.c index 2a7cb7c25..9997d278d 100644 --- a/targets/f7/furi_hal/furi_hal_spi.c +++ b/targets/f7/furi_hal/furi_hal_spi.c @@ -38,17 +38,17 @@ void furi_hal_spi_bus_deinit(FuriHalSpiBus* bus) { bus->callback(bus, FuriHalSpiBusEventDeinit); } -void furi_hal_spi_bus_handle_init(FuriHalSpiBusHandle* handle) { +void furi_hal_spi_bus_handle_init(const FuriHalSpiBusHandle* handle) { furi_check(handle); handle->callback(handle, FuriHalSpiBusHandleEventInit); } -void furi_hal_spi_bus_handle_deinit(FuriHalSpiBusHandle* handle) { +void furi_hal_spi_bus_handle_deinit(const FuriHalSpiBusHandle* handle) { furi_check(handle); handle->callback(handle, FuriHalSpiBusHandleEventDeinit); } -void furi_hal_spi_acquire(FuriHalSpiBusHandle* handle) { +void furi_hal_spi_acquire(const FuriHalSpiBusHandle* handle) { furi_check(handle); furi_hal_power_insomnia_enter(); @@ -62,7 +62,7 @@ void furi_hal_spi_acquire(FuriHalSpiBusHandle* handle) { handle->callback(handle, FuriHalSpiBusHandleEventActivate); } -void furi_hal_spi_release(FuriHalSpiBusHandle* handle) { +void furi_hal_spi_release(const FuriHalSpiBusHandle* handle) { furi_check(handle); furi_check(handle->bus->current_handle == handle); @@ -77,7 +77,7 @@ void furi_hal_spi_release(FuriHalSpiBusHandle* handle) { furi_hal_power_insomnia_exit(); } -static void furi_hal_spi_bus_end_txrx(FuriHalSpiBusHandle* handle, uint32_t timeout) { +static void furi_hal_spi_bus_end_txrx(const FuriHalSpiBusHandle* handle, uint32_t timeout) { UNUSED(timeout); // FIXME while(LL_SPI_GetTxFIFOLevel(handle->bus->spi) != LL_SPI_TX_FIFO_EMPTY) ; @@ -89,7 +89,7 @@ static void furi_hal_spi_bus_end_txrx(FuriHalSpiBusHandle* handle, uint32_t time } bool furi_hal_spi_bus_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* buffer, size_t size, uint32_t timeout) { @@ -102,7 +102,7 @@ bool furi_hal_spi_bus_rx( } bool furi_hal_spi_bus_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* buffer, size_t size, uint32_t timeout) { @@ -128,7 +128,7 @@ bool furi_hal_spi_bus_tx( } bool furi_hal_spi_bus_trx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, @@ -192,7 +192,7 @@ static void spi_dma_isr(void* context) { } bool furi_hal_spi_bus_trx_dma( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, diff --git a/targets/f7/furi_hal/furi_hal_spi_config.c b/targets/f7/furi_hal/furi_hal_spi_config.c index 8a694961a..ece0c05f7 100644 --- a/targets/f7/furi_hal/furi_hal_spi_config.c +++ b/targets/f7/furi_hal/furi_hal_spi_config.c @@ -147,7 +147,7 @@ FuriHalSpiBus furi_hal_spi_bus_d = { /* SPI Bus Handles */ inline static void furi_hal_spi_bus_r_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -193,7 +193,7 @@ inline static void furi_hal_spi_bus_r_handle_event_callback( } inline static void furi_hal_spi_bus_external_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -239,7 +239,7 @@ inline static void furi_hal_spi_bus_external_handle_event_callback( } inline static void furi_hal_spi_bus_nfc_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -305,12 +305,12 @@ inline static void furi_hal_spi_bus_nfc_handle_event_callback( } static void furi_hal_spi_bus_handle_subghz_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_8m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { .bus = &furi_hal_spi_bus_r, .callback = furi_hal_spi_bus_handle_subghz_event_callback, .miso = &gpio_spi_r_miso, @@ -320,12 +320,12 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { }; static void furi_hal_spi_bus_handle_nfc_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_nfc_handle_event_callback(handle, event, &furi_hal_spi_preset_2edge_low_8m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { .bus = &furi_hal_spi_bus_r, .callback = furi_hal_spi_bus_handle_nfc_event_callback, .miso = &gpio_spi_r_miso, @@ -335,13 +335,13 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { }; static void furi_hal_spi_bus_handle_external_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_external_handle_event_callback( handle, event, &furi_hal_spi_preset_1edge_low_2m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { .bus = &furi_hal_spi_bus_r, .callback = furi_hal_spi_bus_handle_external_event_callback, .miso = &gpio_ext_pa6, @@ -351,7 +351,7 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { }; inline static void furi_hal_spi_bus_d_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -392,12 +392,12 @@ inline static void furi_hal_spi_bus_d_handle_event_callback( } static void furi_hal_spi_bus_handle_display_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_4m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_display_event_callback, .miso = &gpio_spi_d_miso, @@ -407,12 +407,12 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { }; static void furi_hal_spi_bus_handle_sd_fast_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_16m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_sd_fast_event_callback, .miso = &gpio_spi_d_miso, @@ -422,12 +422,12 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { }; static void furi_hal_spi_bus_handle_sd_slow_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_sd_slow_event_callback, .miso = &gpio_spi_d_miso, diff --git a/targets/f7/furi_hal/furi_hal_spi_config.h b/targets/f7/furi_hal/furi_hal_spi_config.h index eab633a19..e90cd7061 100644 --- a/targets/f7/furi_hal/furi_hal_spi_config.h +++ b/targets/f7/furi_hal/furi_hal_spi_config.h @@ -28,10 +28,10 @@ extern FuriHalSpiBus furi_hal_spi_bus_r; extern FuriHalSpiBus furi_hal_spi_bus_d; /** CC1101 on `furi_hal_spi_bus_r` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz; /** ST25R3916 on `furi_hal_spi_bus_r` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc; /** External on `furi_hal_spi_bus_r` * Preset: `furi_hal_spi_preset_1edge_low_2m` @@ -45,16 +45,16 @@ extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc; * Bus pins are floating on inactive state, CS high after initialization * */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_external; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_external; /** ST7567(Display) on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_display; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_display; /** SdCard in fast mode on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast; /** SdCard in slow mode on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow; #ifdef __cplusplus } diff --git a/targets/f7/furi_hal/furi_hal_spi_types.h b/targets/f7/furi_hal/furi_hal_spi_types.h index ecc18d50d..9bf138ac0 100644 --- a/targets/f7/furi_hal/furi_hal_spi_types.h +++ b/targets/f7/furi_hal/furi_hal_spi_types.h @@ -31,7 +31,7 @@ typedef void (*FuriHalSpiBusEventCallback)(FuriHalSpiBus* bus, FuriHalSpiBusEven struct FuriHalSpiBus { SPI_TypeDef* spi; FuriHalSpiBusEventCallback callback; - FuriHalSpiBusHandle* current_handle; + const FuriHalSpiBusHandle* current_handle; }; /** FuriHal spi handle states */ @@ -44,7 +44,7 @@ typedef enum { /** FuriHal spi handle event callback */ typedef void (*FuriHalSpiBusHandleEventCallback)( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event); /** FuriHal spi handle */ diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index 19aa0f766..dc6add277 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -51,7 +51,7 @@ typedef struct { volatile SubGhzRegulation regulation; const GpioPin* async_mirror_pin; - int8_t rolling_counter_mult; + int32_t rolling_counter_mult; bool ext_leds_and_amp : 1; bool dangerous_frequency_i : 1; } FuriHalSubGhz; @@ -65,11 +65,11 @@ volatile FuriHalSubGhz furi_hal_subghz = { .dangerous_frequency_i = false, }; -int8_t furi_hal_subghz_get_rolling_counter_mult(void) { +int32_t furi_hal_subghz_get_rolling_counter_mult(void) { return furi_hal_subghz.rolling_counter_mult; } -void furi_hal_subghz_set_rolling_counter_mult(int8_t mult) { +void furi_hal_subghz_set_rolling_counter_mult(int32_t mult) { furi_hal_subghz.rolling_counter_mult = mult; } diff --git a/targets/f7/furi_hal/furi_hal_subghz.h b/targets/f7/furi_hal/furi_hal_subghz.h index 20843520a..b08d41ba4 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.h +++ b/targets/f7/furi_hal/furi_hal_subghz.h @@ -174,14 +174,14 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value); bool furi_hal_subghz_is_tx_allowed(uint32_t value); /** Get the current rolling protocols counter ++/-- value - * @return int8_t current value + * @return int32_t current value */ -int8_t furi_hal_subghz_get_rolling_counter_mult(void); +int32_t furi_hal_subghz_get_rolling_counter_mult(void); /** Set the current rolling protocols counter ++/-- value - * @param mult int8_t = -1, -10, -100, 0, 1, 10, 100 + * @param mult int32_t = -1, -10, -50, 0, 1, 10, 50 */ -void furi_hal_subghz_set_rolling_counter_mult(int8_t mult); +void furi_hal_subghz_set_rolling_counter_mult(int32_t mult); /** Set frequency * diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c index 3408789dd..f9c1d3a42 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.c +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.c @@ -122,7 +122,7 @@ static const struct CdcConfigDescriptorSingle cdc_cfg_desc_single = { .bFunctionLength = sizeof(struct usb_cdc_acm_desc), .bDescriptorType = USB_DTYPE_CS_INTERFACE, .bDescriptorSubType = USB_DTYPE_CDC_ACM, - .bmCapabilities = USB_CDC_CAP_BRK, + .bmCapabilities = 0, }, .cdc_union = { @@ -235,7 +235,7 @@ static const struct CdcConfigDescriptorDual .bFunctionLength = sizeof(struct usb_cdc_acm_desc), .bDescriptorType = USB_DTYPE_CS_INTERFACE, .bDescriptorSubType = USB_DTYPE_CDC_ACM, - .bmCapabilities = USB_CDC_CAP_BRK, + .bmCapabilities = 0, }, .cdc_union = { @@ -330,7 +330,7 @@ static const struct CdcConfigDescriptorDual .bFunctionLength = sizeof(struct usb_cdc_acm_desc), .bDescriptorType = USB_DTYPE_CS_INTERFACE, .bDescriptorSubType = USB_DTYPE_CDC_ACM, - .bmCapabilities = USB_CDC_CAP_BRK, + .bmCapabilities = 0, }, .cdc_union = { @@ -392,10 +392,11 @@ static void cdc_on_suspend(usbd_device* dev); static usbd_respond cdc_ep_config(usbd_device* dev, uint8_t cfg); static usbd_respond cdc_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback); + static usbd_device* usb_dev; -static FuriHalUsbInterface* cdc_if_cur = NULL; -static bool connected = false; -static CdcCallbacks* callbacks[IF_NUM_MAX] = {NULL}; +static volatile FuriHalUsbInterface* cdc_if_cur = NULL; +static volatile bool connected = false; +static volatile CdcCallbacks* callbacks[IF_NUM_MAX] = {NULL}; static void* cb_ctx[IF_NUM_MAX]; FuriHalUsbInterface usb_cdc_single = { @@ -506,8 +507,10 @@ uint8_t furi_hal_cdc_get_ctrl_line_state(uint8_t if_num) { void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { if(if_num == 0) { usbd_ep_write(usb_dev, CDC0_TXD_EP, buf, len); - } else { + } else if(if_num == 1) { usbd_ep_write(usb_dev, CDC1_TXD_EP, buf, len); + } else { + furi_crash(); } } @@ -515,8 +518,10 @@ int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { int32_t len = 0; if(if_num == 0) { len = usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); - } else { + } else if(if_num == 1) { len = usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); + } else { + furi_crash(); } return (len < 0) ? 0 : len; } @@ -680,13 +685,6 @@ static usbd_respond cdc_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal dev->status.data_ptr = &cdc_config[if_num]; dev->status.data_count = sizeof(cdc_config[0]); return usbd_ack; - case USB_CDC_SEND_BREAK: - if(callbacks[if_num] != NULL) { - if(callbacks[if_num]->break_callback != NULL) { - callbacks[if_num]->break_callback(cb_ctx[if_num], req->wValue); - } - } - return usbd_ack; default: return usbd_fail; } diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.h b/targets/f7/furi_hal/furi_hal_usb_cdc.h index 995e9009a..50d456698 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.h +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.h @@ -9,13 +9,22 @@ extern "C" { #endif +typedef enum { + CdcStateDisconnected, + CdcStateConnected, +} CdcState; + +typedef enum { + CdcCtrlLineDTR = (1 << 0), + CdcCtrlLineRTS = (1 << 1), +} CdcCtrlLine; + typedef struct { void (*tx_ep_callback)(void* context); void (*rx_ep_callback)(void* context); - void (*state_callback)(void* context, uint8_t state); - void (*ctrl_line_callback)(void* context, uint8_t state); + void (*state_callback)(void* context, CdcState state); + void (*ctrl_line_callback)(void* context, CdcCtrlLine ctrl_lines); void (*config_callback)(void* context, struct usb_cdc_line_coding* config); - void (*break_callback)(void* context, uint16_t duration); } CdcCallbacks; void furi_hal_cdc_set_callbacks(uint8_t if_num, CdcCallbacks* cb, void* context); diff --git a/targets/f7/furi_hal/furi_hal_usb_hid.c b/targets/f7/furi_hal/furi_hal_usb_hid.c index c83261226..04f2ae400 100644 --- a/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -12,9 +12,6 @@ #define HID_INTERVAL 2 -#define HID_VID_DEFAULT 0x046D -#define HID_PID_DEFAULT 0xC529 - struct HidIntfDescriptor { struct usb_interface_descriptor hid; struct usb_hid_descriptor hid_desc; diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 357019ea2..8d34925ec 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -11,13 +11,16 @@ #endif /* CMSIS_device_header */ #include CMSIS_device_header +#include #define configENABLE_FPU 1 #define configENABLE_MPU 0 #define configUSE_PREEMPTION 1 #define configSUPPORT_STATIC_ALLOCATION 1 -#define configSUPPORT_DYNAMIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configENABLE_HEAP_PROTECTOR 1 +#define configHEAP_CLEAR_MEMORY_ON_FREE 1 #define configUSE_MALLOC_FAILED_HOOK 0 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 @@ -30,7 +33,7 @@ #define configUSE_POSIX_ERRNO 1 /* Heap size determined automatically by linker */ -// #define configTOTAL_HEAP_SIZE ((size_t)0) +#define configTOTAL_HEAP_SIZE ((uint32_t) & __heap_end__ - (uint32_t) & __heap_start__) #define configMAX_TASK_NAME_LEN (32) #define configGENERATE_RUN_TIME_STATS 1 @@ -146,7 +149,7 @@ standard names. */ /* Normal assert() semantics without relying on the provision of an assert.h header file. */ -#ifdef DEBUG +#ifdef FURI_DEBUG #define configASSERT(x) \ if((x) == 0) { \ furi_crash("FreeRTOS Assert"); \ diff --git a/targets/f7/inc/stm32wb55_linker.h b/targets/f7/inc/stm32wb55_linker.h index 4b56a11be..e978dff35 100644 --- a/targets/f7/inc/stm32wb55_linker.h +++ b/targets/f7/inc/stm32wb55_linker.h @@ -9,25 +9,28 @@ #ifdef __cplusplus extern "C" { +typedef const char linker_symbol_t; +#else +typedef const void linker_symbol_t; #endif -extern const void _stack_end; /**< end of stack */ -extern const void _stack_size; /**< stack size */ +extern linker_symbol_t _stack_end; /**< end of stack */ +extern linker_symbol_t _stack_size; /**< stack size */ -extern const void _sidata; /**< data initial value start */ -extern const void _sdata; /**< data start */ -extern const void _edata; /**< data end */ +extern linker_symbol_t _sidata; /**< data initial value start */ +extern linker_symbol_t _sdata; /**< data start */ +extern linker_symbol_t _edata; /**< data end */ -extern const void _sbss; /**< bss start */ -extern const void _ebss; /**< bss end */ +extern linker_symbol_t _sbss; /**< bss start */ +extern linker_symbol_t _ebss; /**< bss end */ -extern const void _sMB_MEM2; /**< RAM2a start */ -extern const void _eMB_MEM2; /**< RAM2a end */ +extern linker_symbol_t _sMB_MEM2; /**< RAM2a start */ +extern linker_symbol_t _eMB_MEM2; /**< RAM2a end */ -extern const void __heap_start__; /**< RAM1 Heap start */ -extern const void __heap_end__; /**< RAM1 Heap end */ +extern linker_symbol_t __heap_start__; /**< RAM1 Heap start */ +extern linker_symbol_t __heap_end__; /**< RAM1 Heap end */ -extern const void __free_flash_start__; /**< Free Flash space start */ +extern linker_symbol_t __free_flash_start__; /**< Free Flash space start */ #ifdef __cplusplus } diff --git a/targets/f7/stm32wb55xx_flash.ld b/targets/f7/stm32wb55xx_flash.ld index ef61bb238..c31aee863 100644 --- a/targets/f7/stm32wb55xx_flash.ld +++ b/targets/f7/stm32wb55xx_flash.ld @@ -131,4 +131,55 @@ SECTIONS { MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM2A ._sram2a_free : { . = ALIGN(4); __sram2a_free__ = .; } >RAM2A ._sram2b_start : { . = ALIGN(4); __sram2b_start__ = .; } >RAM2B + + /* Default debug-related rules from ld */ + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1. */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions. */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2. */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2. */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions. */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3. */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF 5. */ + .debug_addr 0 : { *(.debug_addr) } + .debug_line_str 0 : { *(.debug_line_str) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_macro 0 : { *(.debug_macro) } + .debug_names 0 : { *(.debug_names) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } + .debug_sup 0 : { *(.debug_sup) } + .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) } + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } } diff --git a/targets/f7/stm32wb55xx_ram_fw.ld b/targets/f7/stm32wb55xx_ram_fw.ld index 93579788d..c3f5f8a6a 100644 --- a/targets/f7/stm32wb55xx_ram_fw.ld +++ b/targets/f7/stm32wb55xx_ram_fw.ld @@ -129,4 +129,55 @@ SECTIONS { MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM2A ._sram2a_free : { . = ALIGN(4); __sram2a_free__ = .; } >RAM2A ._sram2b_start : { . = ALIGN(4); __sram2b_start__ = .; } >RAM2B + + /* Default debug-related rules from ld */ + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1. */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions. */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2. */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2. */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions. */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3. */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF 5. */ + .debug_addr 0 : { *(.debug_addr) } + .debug_line_str 0 : { *(.debug_line_str) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_macro 0 : { *(.debug_macro) } + .debug_names 0 : { *(.debug_names) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } + .debug_sup 0 : { *(.debug_sup) } + .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) } + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } } diff --git a/targets/f7/target.json b/targets/f7/target.json index f5b3cf3b6..911aa0822 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -50,6 +50,7 @@ "flipper7", "bit_lib", "toolbox", - "datetime" + "datetime", + "ieee754_parse_wrap" ] } diff --git a/targets/furi_hal_include/furi_hal_bt.h b/targets/furi_hal_include/furi_hal_bt.h index 14c2975ac..6da723311 100644 --- a/targets/furi_hal_include/furi_hal_bt.h +++ b/targets/furi_hal_include/furi_hal_bt.h @@ -231,15 +231,6 @@ float furi_hal_bt_get_rssi(void); */ uint32_t furi_hal_bt_get_transmitted_packets(void); -/** Reverse a MAC address byte order in-place - * @param[in] mac mac address to reverse -*/ -void furi_hal_bt_reverse_mac_addr(uint8_t mac_addr[GAP_MAC_ADDR_SIZE]); - -uint32_t furi_hal_bt_get_conn_rssi(uint8_t* rssi); - -bool furi_hal_bt_is_connected(void); - /** Check & switch C2 to given mode * * @param[in] mode mode to switch into diff --git a/targets/furi_hal_include/furi_hal_i2c.h b/targets/furi_hal_include/furi_hal_i2c.h index 7d69cd74d..fe9f0949c 100644 --- a/targets/furi_hal_include/furi_hal_i2c.h +++ b/targets/furi_hal_include/furi_hal_i2c.h @@ -55,14 +55,14 @@ void furi_hal_i2c_init(void); * * @param handle Pointer to FuriHalI2cBusHandle instance */ -void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle); +void furi_hal_i2c_acquire(const FuriHalI2cBusHandle* handle); /** Release I2C bus handle * * @param handle Pointer to FuriHalI2cBusHandle instance acquired in * `furi_hal_i2c_acquire` */ -void furi_hal_i2c_release(FuriHalI2cBusHandle* handle); +void furi_hal_i2c_release(const FuriHalI2cBusHandle* handle); /** Perform I2C TX transfer * @@ -75,7 +75,7 @@ void furi_hal_i2c_release(FuriHalI2cBusHandle* handle); * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_tx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* data, size_t size, @@ -96,7 +96,7 @@ bool furi_hal_i2c_tx( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_tx_ext( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint16_t address, bool ten_bit, const uint8_t* data, @@ -116,7 +116,7 @@ bool furi_hal_i2c_tx_ext( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_rx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, uint8_t* data, size_t size, @@ -136,7 +136,7 @@ bool furi_hal_i2c_rx( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_rx_ext( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint16_t address, bool ten_bit, uint8_t* data, @@ -158,7 +158,7 @@ bool furi_hal_i2c_rx_ext( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_trx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* tx_data, size_t tx_size, @@ -174,7 +174,10 @@ bool furi_hal_i2c_trx( * * @return true if device present and is ready, false otherwise */ -bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint32_t timeout); +bool furi_hal_i2c_is_device_ready( + const FuriHalI2cBusHandle* handle, + uint8_t i2c_addr, + uint32_t timeout); /** Perform I2C device register read (8-bit) * @@ -187,7 +190,7 @@ bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_read_reg_8( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint8_t* data, @@ -204,7 +207,7 @@ bool furi_hal_i2c_read_reg_8( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_read_reg_16( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint16_t* data, @@ -222,7 +225,7 @@ bool furi_hal_i2c_read_reg_16( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_read_mem( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, uint8_t* data, @@ -240,7 +243,7 @@ bool furi_hal_i2c_read_mem( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_write_reg_8( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint8_t data, @@ -257,7 +260,7 @@ bool furi_hal_i2c_write_reg_8( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_write_reg_16( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint16_t data, @@ -275,7 +278,7 @@ bool furi_hal_i2c_write_reg_16( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_write_mem( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, const uint8_t* data, diff --git a/targets/furi_hal_include/furi_hal_power.h b/targets/furi_hal_include/furi_hal_power.h index 06598df86..f5b6ac71b 100644 --- a/targets/furi_hal_include/furi_hal_power.h +++ b/targets/furi_hal_include/furi_hal_power.h @@ -105,10 +105,14 @@ void furi_hal_power_off(void); FURI_NORETURN void furi_hal_power_reset(void); /** OTG enable + * + * @warning this is low level control, use power service instead */ bool furi_hal_power_enable_otg(void); /** OTG disable + * + * @warning this is low level control, use power service instead */ void furi_hal_power_disable_otg(void); diff --git a/targets/furi_hal_include/furi_hal_spi.h b/targets/furi_hal_include/furi_hal_spi.h index d497dff5c..41f3abdaa 100644 --- a/targets/furi_hal_include/furi_hal_spi.h +++ b/targets/furi_hal_include/furi_hal_spi.h @@ -35,13 +35,13 @@ void furi_hal_spi_bus_deinit(FuriHalSpiBus* bus); * * @param handle pointer to FuriHalSpiBusHandle instance */ -void furi_hal_spi_bus_handle_init(FuriHalSpiBusHandle* handle); +void furi_hal_spi_bus_handle_init(const FuriHalSpiBusHandle* handle); /** Deinitialize SPI Bus Handle * * @param handle pointer to FuriHalSpiBusHandle instance */ -void furi_hal_spi_bus_handle_deinit(FuriHalSpiBusHandle* handle); +void furi_hal_spi_bus_handle_deinit(const FuriHalSpiBusHandle* handle); /** Acquire SPI bus * @@ -49,7 +49,7 @@ void furi_hal_spi_bus_handle_deinit(FuriHalSpiBusHandle* handle); * * @param handle pointer to FuriHalSpiBusHandle instance */ -void furi_hal_spi_acquire(FuriHalSpiBusHandle* handle); +void furi_hal_spi_acquire(const FuriHalSpiBusHandle* handle); /** Release SPI bus * @@ -57,7 +57,7 @@ void furi_hal_spi_acquire(FuriHalSpiBusHandle* handle); * * @param handle pointer to FuriHalSpiBusHandle instance */ -void furi_hal_spi_release(FuriHalSpiBusHandle* handle); +void furi_hal_spi_release(const FuriHalSpiBusHandle* handle); /** SPI Receive * @@ -69,7 +69,7 @@ void furi_hal_spi_release(FuriHalSpiBusHandle* handle); * @return true on sucess */ bool furi_hal_spi_bus_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* buffer, size_t size, uint32_t timeout); @@ -84,7 +84,7 @@ bool furi_hal_spi_bus_rx( * @return true on success */ bool furi_hal_spi_bus_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* buffer, size_t size, uint32_t timeout); @@ -100,7 +100,7 @@ bool furi_hal_spi_bus_tx( * @return true on success */ bool furi_hal_spi_bus_trx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, @@ -117,7 +117,7 @@ bool furi_hal_spi_bus_trx( * @return true on success */ bool furi_hal_spi_bus_trx_dma( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, diff --git a/targets/furi_hal_include/furi_hal_usb_hid.h b/targets/furi_hal_include/furi_hal_usb_hid.h index af4a542de..19c9ed659 100644 --- a/targets/furi_hal_include/furi_hal_usb_hid.h +++ b/targets/furi_hal_include/furi_hal_usb_hid.h @@ -9,6 +9,11 @@ extern "C" { #endif +#define HID_MANUF_PRODUCT_NAME_LEN 32 + +#define HID_VID_DEFAULT 0x046D +#define HID_PID_DEFAULT 0xC529 + /** Max number of simultaneously pressed keys (keyboard) */ #define HID_KB_MAX_KEYS 6 /** Max number of simultaneously pressed keys (consumer control) */ @@ -166,10 +171,11 @@ static const uint16_t hid_asciimap[] = { }; typedef struct { + // Note: vid/pid should be uint16_t and are treated as such uint32_t vid; uint32_t pid; - char manuf[32]; - char product[32]; + char manuf[HID_MANUF_PRODUCT_NAME_LEN]; + char product[HID_MANUF_PRODUCT_NAME_LEN]; } FuriHalUsbHidConfig; typedef void (*HidStateCallback)(bool state, void* context); diff --git a/targets/furi_hal_include/furi_hal_version.h b/targets/furi_hal_include/furi_hal_version.h index 2c098d482..1d026539a 100644 --- a/targets/furi_hal_include/furi_hal_version.h +++ b/targets/furi_hal_include/furi_hal_version.h @@ -14,11 +14,12 @@ extern "C" { #endif -#define FURI_HAL_VERSION_NAME_LENGTH 8 -#define FURI_HAL_VERSION_ARRAY_NAME_LENGTH (FURI_HAL_VERSION_NAME_LENGTH + 1) -#define FURI_HAL_BT_ADV_NAME_LENGTH (18 + 1) // 18 characters + null terminator -#define FURI_HAL_VERSION_DEVICE_NAME_LENGTH \ - (1 + FURI_HAL_BT_ADV_NAME_LENGTH) // Used for custom BT name, BLE symbol + name +#define FURI_HAL_VERSION_NAME_LENGTH (8) +#define FURI_HAL_VERSION_ARRAY_NAME_LENGTH (FURI_HAL_VERSION_NAME_LENGTH + 1) +/** 31b BLE Adv - 3b flags - 2b name prefix - 4b service uuid - 3b tx power = 19, + 1b null terminator (not present in packet) */ +#define FURI_HAL_BT_ADV_NAME_LENGTH (20) +/** BLE symbol + name */ +#define FURI_HAL_VERSION_DEVICE_NAME_LENGTH (1 + FURI_HAL_BT_ADV_NAME_LENGTH) /** OTP Versions enum */ typedef enum {