mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 20:49:49 +04:00
Compare commits
70 Commits
nfcrefacto
...
unlshd-061
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b578cf69d | ||
|
|
dafea08581 | ||
|
|
5c50224571 | ||
|
|
b510df47f9 | ||
|
|
2aec3ec5fd | ||
|
|
4b8c017302 | ||
|
|
b579bca227 | ||
|
|
3a676f7afa | ||
|
|
ca479303a1 | ||
|
|
5e649d8c41 | ||
|
|
0bc626ba1d | ||
|
|
3821ee7709 | ||
|
|
cb17189b15 | ||
|
|
80e8167054 | ||
|
|
eede5ed29e | ||
|
|
360fef7777 | ||
|
|
b83da5d3cb | ||
|
|
ae5d28fbc5 | ||
|
|
f4cd7c0100 | ||
|
|
7b68fd30ec | ||
|
|
ac222f1b0c | ||
|
|
b0e8e68909 | ||
|
|
ef5f6e2f70 | ||
|
|
30f6da3fa3 | ||
|
|
3a47154cdb | ||
|
|
d953d35991 | ||
|
|
4b74d13e10 | ||
|
|
98bf353287 | ||
|
|
eb8c751b31 | ||
|
|
9abad8704f | ||
|
|
6716c0f792 | ||
|
|
35f7ec6c07 | ||
|
|
a524fd7674 | ||
|
|
ff27fd3094 | ||
|
|
977ac09fe6 | ||
|
|
1a88e01899 | ||
|
|
6e710c5164 | ||
|
|
fd56ac3400 | ||
|
|
1d801c38f9 | ||
|
|
8afdb5b7b4 | ||
|
|
83624b1dee | ||
|
|
56adcf1ad8 | ||
|
|
ad27f87a0c | ||
|
|
346cf299ee | ||
|
|
05489fda7d | ||
|
|
1b12526357 | ||
|
|
802035d92e | ||
|
|
a24d0f1958 | ||
|
|
da68f2e4ed | ||
|
|
8f16dbb8e7 | ||
|
|
49e458f1b5 | ||
|
|
5cf46d2aa9 | ||
|
|
beedf54e75 | ||
|
|
70ccb89c3d | ||
|
|
5ea43a2a4b | ||
|
|
41f60dbbf4 | ||
|
|
827341ec08 | ||
|
|
5c36043d03 | ||
|
|
cf5811f8d9 | ||
|
|
ec6a169bf8 | ||
|
|
f1dec87c1b | ||
|
|
ab29951a99 | ||
|
|
bbe9f88bbe | ||
|
|
9188bf0013 | ||
|
|
f33ed59567 | ||
|
|
3fd8c80861 | ||
|
|
7b8ac3a5a0 | ||
|
|
6fef957001 | ||
|
|
0de1c9df89 | ||
|
|
a0e8cfbe97 |
@@ -168,10 +168,3 @@ Max butthurt: 13
|
||||
Min level: 1
|
||||
Max level: 3
|
||||
Weight: 4
|
||||
|
||||
Name: L2_Coding_in_the_shell_128x64
|
||||
Min butthurt: 0
|
||||
Max butthurt: 12
|
||||
Min level: 2
|
||||
Max level: 3
|
||||
Weight: 4
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c
|
||||
index 5769ced..c5d3088 100644
|
||||
index 2f947fe..03c4c76 100644
|
||||
--- a/applications/services/notification/notification_app.c
|
||||
+++ b/applications/services/notification/notification_app.c
|
||||
@@ -9,6 +9,7 @@
|
||||
@@ -10,7 +10,7 @@ index 5769ced..c5d3088 100644
|
||||
|
||||
#define TAG "NotificationSrv"
|
||||
|
||||
@@ -589,6 +590,7 @@ int32_t notification_srv(void* p) {
|
||||
@@ -579,6 +580,7 @@ int32_t notification_srv(void* p) {
|
||||
break;
|
||||
case SaveSettingsMessage:
|
||||
notification_save_settings(app);
|
||||
@@ -19,7 +19,7 @@ index 5769ced..c5d3088 100644
|
||||
}
|
||||
|
||||
diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c
|
||||
index 1955012..19d953d 100644
|
||||
index 565d4f1..bae9299 100644
|
||||
--- a/applications/settings/notification_settings/notification_settings_app.c
|
||||
+++ b/applications/settings/notification_settings/notification_settings_app.c
|
||||
@@ -3,6 +3,7 @@
|
||||
@@ -30,20 +30,10 @@ index 1955012..19d953d 100644
|
||||
|
||||
#define MAX_NOTIFICATION_SETTINGS 4
|
||||
|
||||
@@ -20,6 +21,8 @@ static const NotificationSequence sequence_note_c = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
+static VariableItem* temp_item;
|
||||
+
|
||||
#define CONTRAST_COUNT 11
|
||||
const char* const contrast_text[CONTRAST_COUNT] = {
|
||||
"-5",
|
||||
@@ -156,6 +159,59 @@ static void vibro_changed(VariableItem* item) {
|
||||
@@ -162,6 +163,14 @@ 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);
|
||||
@@ -51,102 +41,31 @@ index 1955012..19d953d 100644
|
||||
+ 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;
|
||||
@@ -180,8 +236,40 @@ static NotificationAppSettings* alloc_settings() {
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
@@ -187,7 +196,13 @@ static NotificationAppSettings* alloc_settings() {
|
||||
variable_item_set_current_value_text(item, contrast_text[value_index]);
|
||||
|
||||
+ // RGB Colors
|
||||
+ item = variable_item_list_add(
|
||||
item = variable_item_list_add(
|
||||
- app->variable_item_list, "LCD Backlight", BACKLIGHT_COUNT, backlight_changed, app);
|
||||
+ 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..98f0d3a
|
||||
index 0000000..269b544
|
||||
--- /dev/null
|
||||
+++ b/applications/settings/notification_settings/rgb_backlight.c
|
||||
@@ -0,0 +1,217 @@
|
||||
@@ -0,0 +1,171 @@
|
||||
+/*
|
||||
+ RGB backlight FlipperZero driver
|
||||
+ Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n)
|
||||
@@ -169,9 +88,9 @@ index 0000000..98f0d3a
|
||||
+#include <furi_hal.h>
|
||||
+#include <storage/storage.h>
|
||||
+
|
||||
+#define RGB_BACKLIGHT_SETTINGS_VERSION 6
|
||||
+#define RGB_BACKLIGHT_SETTINGS_VERSION 5
|
||||
+#define RGB_BACKLIGHT_SETTINGS_FILE_NAME ".rgb_backlight.settings"
|
||||
+#define RGB_BACKLIGHT_SETTINGS_PATH INT_PATH(RGB_BACKLIGHT_SETTINGS_FILE_NAME)
|
||||
+#define RGB_BACKLIGHT_SETTINGS_PATH EXT_PATH(RGB_BACKLIGHT_SETTINGS_FILE_NAME)
|
||||
+
|
||||
+#define COLOR_COUNT (sizeof(colors) / sizeof(RGBBacklightColor))
|
||||
+
|
||||
@@ -180,14 +99,11 @@ index 0000000..98f0d3a
|
||||
+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},
|
||||
+ {"Yellow", 255, 150, 0},
|
||||
+ {"Spring", 167, 255, 0},
|
||||
+ {"Lime", 0, 255, 0},
|
||||
+ {"Aqua", 0, 255, 127},
|
||||
@@ -198,8 +114,7 @@ index 0000000..98f0d3a
|
||||
+ {"Magenta", 210, 0, 210},
|
||||
+ {"Pink", 255, 0, 127},
|
||||
+ {"Red", 255, 0, 0},
|
||||
+ {"White", 254, 210, 200},
|
||||
+ {"Custom", 0, 0, 0},
|
||||
+ {"White", 150, 150, 110},
|
||||
+};
|
||||
+
|
||||
+uint8_t rgb_backlight_get_color_count(void) {
|
||||
@@ -211,28 +126,18 @@ index 0000000..98f0d3a
|
||||
+}
|
||||
+
|
||||
+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) {
|
||||
+ //Не загружать данные из внутренней памяти при загрузке в режиме DFU
|
||||
+ FuriHalRtcBootMode bm = furi_hal_rtc_get_boot_mode();
|
||||
+ if(bm == FuriHalRtcBootModeDfu) {
|
||||
+ 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);
|
||||
+ FURI_LOG_I(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);
|
||||
+
|
||||
@@ -245,7 +150,7 @@ index 0000000..98f0d3a
|
||||
+ }
|
||||
+
|
||||
+ if(fs_result) {
|
||||
+ FURI_LOG_D(TAG, "load success");
|
||||
+ FURI_LOG_I(TAG, "load success");
|
||||
+ if(settings.version != RGB_BACKLIGHT_SETTINGS_VERSION) {
|
||||
+ FURI_LOG_E(
|
||||
+ TAG,
|
||||
@@ -270,7 +175,7 @@ index 0000000..98f0d3a
|
||||
+ 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);
|
||||
+ FURI_LOG_I(TAG, "saving settings to \"%s\"", RGB_BACKLIGHT_SETTINGS_PATH);
|
||||
+
|
||||
+ memcpy(&settings, &rgb_settings, settings_size);
|
||||
+
|
||||
@@ -286,7 +191,7 @@ index 0000000..98f0d3a
|
||||
+ }
|
||||
+
|
||||
+ if(fs_result) {
|
||||
+ FURI_LOG_D(TAG, "save success");
|
||||
+ FURI_LOG_I(TAG, "save success");
|
||||
+ } else {
|
||||
+ FURI_LOG_E(TAG, "save failed, %s", storage_file_get_error_desc(file));
|
||||
+ }
|
||||
@@ -308,68 +213,36 @@ index 0000000..98f0d3a
|
||||
+ 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) {
|
||||
+void rgb_backlight_update(uint8_t brightness) {
|
||||
+ 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;
|
||||
+ 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;
|
||||
+ }
|
||||
+ if(last_brightness == brightness && last_color_index == rgb_settings.display_color_index)
|
||||
+ return;
|
||||
+
|
||||
+ last_brightness = brightness;
|
||||
+ last_color_index = rgb_settings.display_color_index;
|
||||
+ }
|
||||
+ 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);
|
||||
+ 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);
|
||||
+ } 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_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..68dacda
|
||||
index 0000000..b63d223
|
||||
--- /dev/null
|
||||
+++ b/applications/settings/notification_settings/rgb_backlight.h
|
||||
@@ -0,0 +1,91 @@
|
||||
@@ -0,0 +1,79 @@
|
||||
+/*
|
||||
+ RGB backlight FlipperZero driver
|
||||
+ Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n)
|
||||
@@ -401,9 +274,6 @@ index 0000000..68dacda
|
||||
+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;
|
||||
+
|
||||
@@ -428,9 +298,8 @@ index 0000000..68dacda
|
||||
+ * @brief Применить текущие настройки RGB-подсветки
|
||||
+ *
|
||||
+ * @param brightness Яркость свечения (0-255)
|
||||
+ * @param bypass Применить настройки принудительно
|
||||
+ */
|
||||
+void rgb_backlight_update(uint8_t brightness, bool bypass);
|
||||
+void rgb_backlight_update(uint8_t brightness);
|
||||
+
|
||||
+/**
|
||||
+ * @brief Установить цвет RGB-подсветки
|
||||
@@ -440,14 +309,6 @@ index 0000000..68dacda
|
||||
+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 Число доступных вариантов цвета
|
||||
@@ -463,7 +324,7 @@ index 0000000..68dacda
|
||||
+const char* rgb_backlight_get_color_text(uint8_t index);
|
||||
\ No newline at end of file
|
||||
diff --git a/firmware/targets/f7/furi_hal/furi_hal_light.c b/firmware/targets/f7/furi_hal/furi_hal_light.c
|
||||
index 83e1603..45798ca 100644
|
||||
index 83e1603..cad5b86 100644
|
||||
--- a/firmware/targets/f7/furi_hal/furi_hal_light.c
|
||||
+++ b/firmware/targets/f7/furi_hal/furi_hal_light.c
|
||||
@@ -3,6 +3,7 @@
|
||||
@@ -492,7 +353,7 @@ index 83e1603..45798ca 100644
|
||||
- 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);
|
||||
+ rgb_backlight_update(value);
|
||||
+ } else {
|
||||
+ furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
+ if(light & LightRed) {
|
||||
|
||||
30
.drone.yml
30
.drone.yml
@@ -22,8 +22,7 @@ steps:
|
||||
- export DIST_SUFFIX=${DRONE_TAG}c
|
||||
- export WORKFLOW_BRANCH_OR_TAG=release-cfw
|
||||
- export FORCE_NO_DIRTY=yes
|
||||
- export FBT_GIT_SUBMODULE_SHALLOW=1
|
||||
- rm -rf applications/main/clock_app/resources/apps/
|
||||
- rm -rf assets/resources/apps/
|
||||
- rm -rf build/
|
||||
- rm -rf dist/
|
||||
- rm -rf .sconsign.dblite
|
||||
@@ -43,14 +42,11 @@ steps:
|
||||
- export DIST_SUFFIX=${DRONE_TAG}
|
||||
- export WORKFLOW_BRANCH_OR_TAG=release-cfw
|
||||
- export FORCE_NO_DIRTY=yes
|
||||
- export FBT_GIT_SUBMODULE_SHALLOW=1
|
||||
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-base.tgz
|
||||
- tar zxvf all-the-apps-base.tgz
|
||||
- cp -R base_pack_build/artifacts-base/* applications/main/clock_app/resources/apps/
|
||||
- cp -R base_pack_build/apps_data/* applications/main/clock_app/resources/apps_data/
|
||||
- cp -R base_pack_build/artifacts-base/* assets/resources/apps/
|
||||
- rm -rf base_pack_build
|
||||
- rm -rf all-the-apps-base.tgz
|
||||
- rm -f build/f7-firmware-C/toolbox/version.*
|
||||
- ./fbt COMPACT=1 DEBUG=0 updater_package
|
||||
- mkdir artifacts-default
|
||||
- mv dist/f7-C/* artifacts-default/
|
||||
@@ -66,12 +62,11 @@ steps:
|
||||
commands:
|
||||
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-extra.tgz
|
||||
- tar zxvf all-the-apps-extra.tgz
|
||||
- cp -R extra_pack_build/artifacts-extra/* applications/main/clock_app/resources/apps/
|
||||
- cp -R extra_pack_build/artifacts-extra/* assets/resources/apps/
|
||||
- rm -rf extra_pack_build
|
||||
- export DIST_SUFFIX=${DRONE_TAG}e
|
||||
- export WORKFLOW_BRANCH_OR_TAG=release-cfw
|
||||
- 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-extra-apps
|
||||
@@ -90,7 +85,6 @@ steps:
|
||||
- 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
|
||||
@@ -109,17 +103,15 @@ steps:
|
||||
- git checkout -- .
|
||||
- rm -f assets/dolphin/external/manifest.txt
|
||||
- cp .ci_files/anims_ofw.txt assets/dolphin/external/manifest.txt
|
||||
- rm -rf applications/main/clock_app/resources/apps/
|
||||
- rm -rf assets/resources/apps/
|
||||
- export DIST_SUFFIX=${DRONE_TAG}n
|
||||
- export WORKFLOW_BRANCH_OR_TAG=no-custom-anims
|
||||
- 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
|
||||
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-base.tgz
|
||||
- tar zxvf all-the-apps-base.tgz
|
||||
- cp -R base_pack_build/artifacts-base/* applications/main/clock_app/resources/apps/
|
||||
- cp -R base_pack_build/apps_data/* applications/main/clock_app/resources/apps_data/
|
||||
- cp -R base_pack_build/artifacts-base/* assets/resources/apps/
|
||||
- rm -rf base_pack_build
|
||||
- rm -rf all-the-apps-base.tgz
|
||||
- rm -f build/f7-firmware-C/toolbox/version.*
|
||||
@@ -396,8 +388,7 @@ steps:
|
||||
- export DIST_SUFFIX=${DRONE_BUILD_NUMBER}c
|
||||
- export WORKFLOW_BRANCH_OR_TAG=dev-cfw
|
||||
- export FORCE_NO_DIRTY=yes
|
||||
- export FBT_GIT_SUBMODULE_SHALLOW=1
|
||||
- rm -rf applications/main/clock_app/resources/apps/
|
||||
- rm -rf assets/resources/apps/
|
||||
- rm -rf build/
|
||||
- rm -rf dist/
|
||||
- rm -rf .sconsign.dblite
|
||||
@@ -418,14 +409,11 @@ steps:
|
||||
- export DIST_SUFFIX=${DRONE_BUILD_NUMBER}
|
||||
- export WORKFLOW_BRANCH_OR_TAG=dev-cfw
|
||||
- export FORCE_NO_DIRTY=yes
|
||||
- export FBT_GIT_SUBMODULE_SHALLOW=1
|
||||
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-base.tgz
|
||||
- tar zxvf all-the-apps-base.tgz
|
||||
- cp -R base_pack_build/artifacts-base/* applications/main/clock_app/resources/apps/
|
||||
- cp -R base_pack_build/apps_data/* applications/main/clock_app/resources/apps_data/
|
||||
- cp -R base_pack_build/artifacts-base/* assets/resources/apps/
|
||||
- rm -rf base_pack_build
|
||||
- rm -rf all-the-apps-base.tgz
|
||||
- rm -f build/f7-firmware-C/toolbox/version.*
|
||||
- ./fbt COMPACT=1 DEBUG=0 updater_package
|
||||
- mkdir artifacts-default
|
||||
- mv dist/f7-C/* artifacts-default/
|
||||
@@ -441,12 +429,11 @@ steps:
|
||||
commands:
|
||||
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-extra.tgz
|
||||
- tar zxvf all-the-apps-extra.tgz
|
||||
- cp -R extra_pack_build/artifacts-extra/* applications/main/clock_app/resources/apps/
|
||||
- cp -R extra_pack_build/artifacts-extra/* assets/resources/apps/
|
||||
- rm -rf extra_pack_build
|
||||
- export DIST_SUFFIX=${DRONE_BUILD_NUMBER}e
|
||||
- export WORKFLOW_BRANCH_OR_TAG=dev-cfw
|
||||
- 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-extra-apps
|
||||
@@ -465,7 +452,6 @@ steps:
|
||||
- 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
|
||||
|
||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,4 +1,3 @@
|
||||
patreon: mmxdev
|
||||
custom:
|
||||
[
|
||||
"https://boosty.to/mmxdev",
|
||||
|
||||
114
.github/workflows/codeql.yml
vendored
114
.github/workflows/codeql.yml
vendored
@@ -1,114 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
run-name: "CodeQL Analyze ${{ github.ref_name }} by @${{ github.ACTOR }}"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["dev"]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners
|
||||
# Consider using larger runners for possible analysis time improvements.
|
||||
runs-on: [ "ubuntu-latest" ]
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ["cpp"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ]
|
||||
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
env:
|
||||
PATH_SARIF_DIR: ".github/results.sarif"
|
||||
PATH_SARIF_FILE: ".github/results.sarif/${{ matrix.language }}.sarif"
|
||||
FBT_NO_SYNC: 0
|
||||
DIST_SUFFIX: "codeql"
|
||||
WORKFLOW_BRANCH_OR_TAG: release-cfw
|
||||
LANG_CATEGORY: "/language:${{matrix.language}}"
|
||||
|
||||
steps:
|
||||
- name: Checkout Firmware Files
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
clean: "true"
|
||||
submodules: "true"
|
||||
fetch-depth: "0"
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
setup-python-dependencies: true
|
||||
|
||||
- name: Resolve CodeQL Build Env
|
||||
uses: github/codeql-action/resolve-environment@v2
|
||||
with:
|
||||
language: ${{ matrix.language }}
|
||||
#debug: true
|
||||
|
||||
- name: Build Firmware
|
||||
shell: bash
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
./fbt COMPACT=1 DEBUG=0 FBT_NO_SYNC=${{ env.FBT_NO_SYNC }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
threads: 4
|
||||
category: "${{ env.LANG_CATEGORY }}"
|
||||
output: "${{ env.PATH_SARIF_DIR }}"
|
||||
upload-database: false
|
||||
upload: "failure-only" # disable the upload here - we will upload in a different action
|
||||
|
||||
- name: Filter dirs for SARIF
|
||||
uses: advanced-security/filter-sarif@v1
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
# filter out all test files unless they contain a sql-injection vulnerability
|
||||
patterns: |
|
||||
-build/**
|
||||
-dist/**
|
||||
-toolchain/**
|
||||
-lib/**
|
||||
input: "${{ env.PATH_SARIF_FILE }}"
|
||||
output: "${{ env.PATH_SARIF_FILE }}"
|
||||
|
||||
- name: Upload CodeQL SARIF
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
category: "${{ env.LANG_CATEGORY }}"
|
||||
sarif_file: "${{ env.PATH_SARIF_FILE }}"
|
||||
|
||||
# optional: for debugging the uploaded sarif
|
||||
# - name: Upload loc as a Build Artifact
|
||||
# uses: actions/upload-artifact@v3
|
||||
# with:
|
||||
# name: sarif-results
|
||||
# path: sarif-results
|
||||
# retention-days: 1
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -38,7 +38,3 @@
|
||||
[submodule "lib/stm32wb_copro"]
|
||||
path = lib/stm32wb_copro
|
||||
url = https://github.com/flipperdevices/stm32wb_copro.git
|
||||
[submodule "subghz_remote"]
|
||||
path = applications/main/subghz_remote
|
||||
url = https://github.com/DarkFlippers/SubGHz_Remote.git
|
||||
branch = ufw_main_app
|
||||
|
||||
@@ -1 +1 @@
|
||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/*
|
||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/*
|
||||
|
||||
16
.vscode/example/tasks.json
vendored
16
.vscode/example/tasks.json
vendored
@@ -28,17 +28,29 @@
|
||||
"command": "./fbt -c"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Flash (SWD)",
|
||||
"label": "[Release] Flash (ST-Link)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Flash (SWD)",
|
||||
"label": "[Debug] Flash (ST-Link)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt FORCE=1 flash"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Flash (blackmagic)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_blackmagic"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Flash (blackmagic)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt FORCE=1 flash_blackmagic"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Flash (JLink)",
|
||||
"group": "build",
|
||||
|
||||
75
CHANGELOG.md
75
CHANGELOG.md
@@ -1,13 +1,31 @@
|
||||
## New changes
|
||||
* SubGHz: Add 4 more systems to Add Manually (untested!)
|
||||
* SubGHz: Add Manually fixes
|
||||
* SubGHz: Added NiceFlor-S to ignore options, removed colons. (by @G2Dolphin | PR #620)
|
||||
* Misc code cleanup
|
||||
* RGB: Fix white color on reboot, move settings, add custom color option
|
||||
* **BLE Spam app** updated to latest version (Android, Windows support) (by @Willy-JL) -> (app can be found in builds ` `, `e`, `n`, `r`)
|
||||
* OFW: Fix double arrows and add proper indication
|
||||
* OFW: SubGHz: add manually fix 12-bits is 0xFFF (or 0xFF0) CAME/NICE 12-bit
|
||||
* OFW: Fix various crashes if debug libraries used
|
||||
* NFC: Add manually MF Classic with custom UID (by @gid9798 | PR #571)
|
||||
* Infrared: Fix crash when frequency is outside of supported range, allow sending broken files by setting frequency to nearest border
|
||||
* Infrared: Updated AC universal asset (Legion LE-F30RH-IN added and other changes) (by @Leptopt1los | PR #577)
|
||||
* SubGHz: Fix Somfy Telis wrong frequency in add manually
|
||||
* SubGHz: Fix frequency out of bounds check causing crash/freeze
|
||||
* RGB Patch: Fix colors
|
||||
* Builds: New build (clean) `c` - comes only with main apps, use in case you don't want preinstalled apps pack
|
||||
* Plugins: Moved into one repo - https://github.com/xMasterX/all-the-plugins
|
||||
* OFW PR 2984: (only buffer changes was merged, other will be later) SubGhz: fix todo (by Skorpionm)
|
||||
* OFW PR 2980: Properly reset the NFC device data (by Astrrra)
|
||||
* OFW: SubGhz: add timeout to subghz_hal_async_tx_test_run
|
||||
* OFW: Improve vscode clangd experience
|
||||
* OFW: Add the Sad song animation
|
||||
* OFW: ufbt: fixed FAP_SRC_DIR
|
||||
* OFW: UI: Clock on Desktop -> Refactoring of our desktop clock
|
||||
* OFW: uFBT: devboard_flash to update WiFi devboard
|
||||
* OFW: FBT: devboard_flash to update WiFi devboard
|
||||
* OFW: Scripts: OB recovery
|
||||
* OFW: Expose additional functions of the crypto engine to user -> **Breaking API change 34.x -> 35.x**
|
||||
* OFW: External apps removed -> In our case - moved into extra plugins repo to separate plugins and firmware
|
||||
* OFW: BadUSB: qFlipper install script for MacOS
|
||||
* OFW: Add compressor.h to the SDK
|
||||
* OFW: fbt: Fix building using path with space
|
||||
* OFW: RPC: md5 in storage list
|
||||
* OFW: Fixes 2957 - subghz decode_raw
|
||||
* OFW: FDX-B temperature in system units
|
||||
* OFW: Infrared: buttons move feature rework
|
||||
|
||||
----
|
||||
|
||||
@@ -16,20 +34,17 @@
|
||||
[-> Download qFlipper (official link)](https://flipperzero.one/update)
|
||||
|
||||
## Please support development of the project
|
||||
|Service|Remark|Link/Wallet|
|
||||
|-|-|-|
|
||||
|**Patreon**||https://patreon.com/mmxdev|
|
||||
|**Boosty**|patreon alternative|https://boosty.to/mmxdev|
|
||||
|cloudtips|only RU payments accepted|https://pay.cloudtips.ru/p/7b3e9d65|
|
||||
|YooMoney|only RU payments accepted|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209|
|
||||
|USDT|(TRC20)|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`|
|
||||
|BCH||`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`|
|
||||
|ETH|(BSC/ERC20-Tokens)|`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)|
|
||||
|BTC||`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`|
|
||||
|DOGE||`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`|
|
||||
|LTC||`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`|
|
||||
|XMR|(Monero)| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
|
||||
|TON||`EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`|
|
||||
* **Boosty** (patreon alternative): https://boosty.to/mmxdev
|
||||
* cloudtips (only RU payments accepted): https://pay.cloudtips.ru/p/7b3e9d65
|
||||
* YooMoney (only RU payments accepted): https://yoomoney.ru/fundraise/XA49mgQLPA0.221209
|
||||
* USDT(TRC20): `TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`
|
||||
* BCH: `qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`
|
||||
* ETH/BSC/ERC20-Tokens: `darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)
|
||||
* BTC: `bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`
|
||||
* DOGE: `D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`
|
||||
* LTC: `ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`
|
||||
* XMR (Monero): `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`
|
||||
* TON: `EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`
|
||||
|
||||
#### Thanks to our sponsors:
|
||||
callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ...
|
||||
@@ -44,15 +59,11 @@ What build I should download and what this name means - `flipper-z-f7-update-(ve
|
||||
`f7` = Hardware version - same for all flipper zero devices<br>
|
||||
`update` = Update package, contains updater, all assets (plugins, IR libs, etc.), and firmware itself<br>
|
||||
`(version)` = Firmware version<br>
|
||||
| Designation | 3 Custom Animation | [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` | ✅ | | | |
|
||||
| `n` | | ✅ | | |
|
||||
| `e` | ✅ | ✅ | ✅ | |
|
||||
| `r` | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
⚠️This is [hardware mod](https://github.com/quen0n/flipperzero-firmware-rgb#readme), works only on modded flippers! do not install on non modded device!
|
||||
` ` = this build comes with 3 custom animations, and default apps preinstalled<br>
|
||||
`c` = this build comes with 3 custom animations, and only main apps (Clean build)<br>
|
||||
`n` = this build comes without our custom animations (we have only 3 of them), only official flipper animations<br>
|
||||
`e` = build has 🎲 [extra apps pack](https://github.com/xMasterX/all-the-plugins) preinstalled<br>
|
||||
`r` = RGB patch (+ extra apps) for flippers with rgb backlight mod (this is hardware mod!) (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<br>
|
||||
Archive of `scripts` folder (contains scripts for FW/plugins development) - `flipper-z-any-scripts-(version).tgz`<br>
|
||||
|
||||
227
ReadMe.md
227
ReadMe.md
@@ -3,20 +3,6 @@
|
||||
<img src="https://user-images.githubusercontent.com/10697207/186202043-26947e28-b1cc-459a-8f20-ffcc7fc0c71c.png" align="center" alt="fzCUSTOM" border="0">
|
||||
</a>
|
||||
</h3>
|
||||
<div align="center" id="badges">
|
||||
<a href="https://discord.unleashedflip.com">
|
||||
<img src="https://img.shields.io/discord/937479784148115456?style=flat-square&logo=discord&label=Discord&color=%237289DA&link=https%3A%2F%2Fdiscord.unleashedflip.com%2F" alt="Discord server"/>
|
||||
</a>
|
||||
<a href="https://t.me/flipperzero_unofficial">
|
||||
<img src="https://img.shields.io/endpoint?label=EN%20Channel&style=flat-square&url=https%3A%2F%2Fmogyo.ro%2Fquart-apis%2Ftgmembercount%3Fchat_id%3Dflipperzero_unofficial" alt="EN TG channel"/>
|
||||
</a>
|
||||
<a href="https://t.me/flipperzero_unofficial_ru">
|
||||
<img src="https://img.shields.io/endpoint?label=RU%20Channel&style=flat-square&url=https%3A%2F%2Fmogyo.ro%2Fquart-apis%2Ftgmembercount%3Fchat_id%3Dflipperzero_unofficial_ru" alt="RU TG channel"/>
|
||||
</a>
|
||||
<a href="https://t.me/flipperzero_unofficial_ua">
|
||||
<img src="https://img.shields.io/endpoint?label=UA%20Channel&style=flat-square&url=https%3A%2F%2Fmogyo.ro%2Fquart-apis%2Ftgmembercount%3Fchat_id%3Dflipperzero_unofficial_ua" alt="UA TG channel"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### Welcome to the Flipper Zero Unleashed Firmware repo!
|
||||
|
||||
@@ -30,11 +16,14 @@
|
||||
|
||||
##### This software is for experimental purposes only and is not meant for any illegal activity/purposes. <br> We do not condone illegal activity and strongly encourage keeping transmissions to legal/valid uses allowed by law. <br> Also, this software is made without any support from Flipper Devices and is in no way related to the official devs.
|
||||
|
||||
<br>
|
||||
Our Discord Community:
|
||||
<br>
|
||||
<a href="https://discord.unleashedflip.com"><img src="https://discordapp.com/api/guilds/937479784148115456/widget.png?style=banner4" alt="Unofficial Discord Community" target="_blank"></a>
|
||||
|
||||
<br>
|
||||
|
||||
## 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)
|
||||
<br>
|
||||
<br>
|
||||
|
||||
## Dev builds (unstable)
|
||||
- https://dev.unleashedflip.com/
|
||||
@@ -43,59 +32,45 @@
|
||||
- https://t.me/unleashed_fw
|
||||
|
||||
# What's changed
|
||||
- **Sub-GHz** *lib & hal*
|
||||
- Regional TX restrictions removed
|
||||
- Extra Sub-GHz frequencies
|
||||
- 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 frequency [(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 - Enable in `Radio Settings -> Time in names = ON`
|
||||
- Read mode UI improvements (shows time when signal was received) (by @wosk)
|
||||
- External CC1101 module support (Hardware SPI used)
|
||||
- **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)
|
||||
- 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 -> Debug TX PIN output settings
|
||||
- **NFC/RFID/iButton**
|
||||
* LFRFID/iButton Fuzzer plugins
|
||||
* Extra Mifare Classic keys
|
||||
* `Add manually` -> Mifare Classic with custom UID
|
||||
* Picopass/iClass plugin (now with emulation support!) included in releases
|
||||
- **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
|
||||
- Clock on Desktop -> `Settings -> Desktop -> Show Clock`
|
||||
- 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)
|
||||
- **BadBT** plugin (BT version of BadKB) [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/ClaraCrazy/Flipper-Xtreme/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
|
||||
* Sub-GHz regional TX restrictions removed
|
||||
* Sub-GHz frequency range can be extended in settings file (Warning: It can damage Flipper's hardware)
|
||||
* Many rolling code protocols now have the ability to save & send captured signals
|
||||
* FAAC SLH (Spa) & BFT Mitto (keeloq secure with seed) manual creation
|
||||
* Sub-GHz static code brute-force plugin
|
||||
* LFRFID Fuzzer plugin
|
||||
* Custom community plugins and games added + all known working apps can be downloaded in extra pack in every release
|
||||
* Extra Sub-GHz frequencies + extra Mifare Classic keys
|
||||
* Picopass/iClass plugin included in releases
|
||||
* Recompiled IR TV Universal Remote for ALL buttons
|
||||
* Universal remote for Projectors, Fans, A/Cs and Audio(soundbars, etc.)
|
||||
* 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)
|
||||
- **BadBT** plugin (BT version of BadKB) [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/ClaraCrazy/Flipper-Xtreme/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)
|
||||
- Sub-GHz -> External CC1101 module support - [(by quen0n)](https://github.com/DarkFlippers/unleashed-firmware/pull/307)
|
||||
- Sub-GHz -> `Add manually` menu extended with new protocols
|
||||
- Sub-GHz -> New frequency analyzer - [(by ClusterM)](https://github.com/DarkFlippers/unleashed-firmware/pull/43)
|
||||
- Sub-GHz -> Save last used frequency [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77)
|
||||
- Sub-GHz -> Press OK in frequency analyzer to use detected frequency in Read modes [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77)
|
||||
- Sub-GHz -> Long press OK button in Sub-GHz Frequency analyzer to switch to Read menu [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/79)
|
||||
- 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)
|
||||
* Sub-GHz -> Short press OK in frequency analyzer to save detected frequency for usage in Read modes
|
||||
* Sub-GHz -> Long press OK button in Sub-GHz Frequency analyzer to switch to Read menu and automatically use selected frequency
|
||||
* SubGHz -> New option to use timestamps + protocol name when you saving file, instead of random name - Enable in `Radio Settings -> Time in names = ON`
|
||||
* SubGHz Bruteforcer 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
|
||||
* SubGHz -> Read mode UI improvements (scrolling text, + shows time when signal was received) (by @wosk)
|
||||
* Sub-GHz -> External CC1101 module support (Hardware SPI used)
|
||||
* SubGHz -> **Hold right in received signal list to delete selected signal**
|
||||
* SubGHz -> **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
|
||||
* SubGHz -> 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)
|
||||
* SubGHz -> Debug mode counter increase settings (+1 -> +5, +10, default: +1)
|
||||
* SubGHz -> Debug PIN output settings for protocol development
|
||||
* Infrared -> `RCA` Protocol
|
||||
* Infrared -> Debug TX PIN output settings
|
||||
* 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!
|
||||
Also check the changelog in releases for latest updates!
|
||||
|
||||
### Current modified and new Sub-GHz protocols list:
|
||||
Thanks to Official team (to their SubGHz Developer, Skorp) for implementing decoders for these protocols in OFW.
|
||||
@@ -123,8 +98,8 @@ Encoders or sending made by @xMasterX:
|
||||
Encoders or sending made by @Eng1n33r(first implementation in Q2 2022) & @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!!! 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)
|
||||
- FAAC SLH (Spa) [External seed calculation required (For info contact me in Discord: @mmx7)]
|
||||
- Keeloq: BFT Mitto -> Update! check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md)
|
||||
- Star Line
|
||||
- Security+ v1 & v2 (encoders was made in OFW)
|
||||
|
||||
@@ -149,99 +124,87 @@ Our team is small and the guys are working on this project as much as they can s
|
||||
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.
|
||||
You can support us by using links or addresses below:
|
||||
|Service|Remark|Link/Wallet|
|
||||
|-|-|-|
|
||||
|**Patreon**||https://patreon.com/mmxdev|
|
||||
|**Boosty**|patreon alternative|https://boosty.to/mmxdev|
|
||||
|cloudtips|only RU payments accepted|https://pay.cloudtips.ru/p/7b3e9d65|
|
||||
|YooMoney|only RU payments accepted|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209|
|
||||
|USDT|(TRC20)|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`|
|
||||
|BCH||`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`|
|
||||
|ETH|(BSC/ERC20-Tokens)|`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)|
|
||||
|BTC||`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`|
|
||||
|DOGE||`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`|
|
||||
|LTC||`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`|
|
||||
|XMR|(Monero)| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
|
||||
|TON||`EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`|
|
||||
* **Boosty** (patreon alternative): https://boosty.to/mmxdev
|
||||
* cloudtips (only RU payments accepted): https://pay.cloudtips.ru/p/7b3e9d65
|
||||
* YooMoney (only RU payments accepted): https://yoomoney.ru/fundraise/XA49mgQLPA0.221209
|
||||
* USDT(TRC20): `TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`
|
||||
* BCH: `qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`
|
||||
* ETH/BSC/ERC20-Tokens: `darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)
|
||||
* BTC: `bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`
|
||||
* DOGE: `D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`
|
||||
* LTC: `ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`
|
||||
* XMR (Monero): `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`
|
||||
* TON: `EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`
|
||||
|
||||
## Community apps included
|
||||
### 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 *(Deafult)* pack](https://github.com/xMasterX/all-the-plugins/tree/dev#default-pack)
|
||||
#### See full list and sources here: https://github.com/xMasterX/all-the-plugins/tree/dev
|
||||
|
||||
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 lock official docs [docs.flipper.net](https://docs.flipper.net/)
|
||||
## [How to install](/documentation/HowToInstall.md) - [versions info](/CHANGELOG.md#recommended-update-option---web-updater): `n`,` `,`e`...
|
||||
## Firmware & Development
|
||||
## [- How to install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)
|
||||
|
||||
### - **[How to build](/documentation/HowToBuild.md#how-to-build-by-yourself) | [Project-structure](#project-structure)**
|
||||
## [- How to build firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToBuild.md)
|
||||
|
||||
### - **CLion IDE** - How to setup workspace for flipper firmware development [by Savely Krasovsky](https://krasovs.ky/2022/11/01/flipper-zero-clion.html)
|
||||
## [- How to connect external CC1101 module](https://github.com/quen0n/flipperzero-ext-cc1101)
|
||||
|
||||
### - **"Hello world!"** - plugin tutorial [English<sub> by DroomOne</sub> ](https://github.com/DroomOne/Flipper-Plugin-Tutorial) | [Russian<sub> by Pavel Yakovlev</sub>](https://yakovlev.me/hello-flipper-zero/)
|
||||
## [- BadUSB: how to add new keyboard layouts](https://github.com/dummy-decoy/flipperzero_badusb_kl)
|
||||
|
||||
### - [How to write your own app](https://flipper.atmanos.com/docs/overview/intro). Docs by atmanos **⚠️outdated API**
|
||||
## [- How to change Flipper name](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/CustomFlipperName.md)
|
||||
|
||||
## Firmware & main Apps feature
|
||||
## [- How to use Mifare Nested plugin to recover keys](https://github.com/AloneLiberty/FlipperNested#how-to-use-it)
|
||||
|
||||
### - System: [How to change Flipper name](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/CustomFlipperName.md)
|
||||
## [- How to make captures to add them into Universal IR remotes](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/InfraredCaptures.md)
|
||||
|
||||
### - BadUSB: [How to add new keyboard layouts](https://github.com/dummy-decoy/flipperzero_badusb_kl)
|
||||
### **Sub-GHz**
|
||||
|
||||
### - Infrared: [How to make captures to add them into Universal IR remotes](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/InfraredCaptures.md)
|
||||
## [- Transmission is blocked? - How to extend Sub-GHz frequency range](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/DangerousSettings.md)
|
||||
|
||||
## **Sub-GHz**
|
||||
## [- How to add extra Sub-GHz frequencies](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzSettings.md)
|
||||
|
||||
### - External Radio: [How to connect CC1101 module](https://github.com/quen0n/flipperzero-ext-cc1101)
|
||||
## [- 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)
|
||||
|
||||
### - Transmission is blocked? [How to extend Sub-GHz frequency range](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/DangerousSettings.md)
|
||||
## [- Configure Sub-GHz Remote App](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemotePlugin.md)
|
||||
|
||||
### - [How to add extra Sub-GHz frequencies](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzSettings.md)
|
||||
### **Plugins**
|
||||
|
||||
### - [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)
|
||||
## [- 🎲 Download Extra plugins for Unleashed](https://github.com/xMasterX/all-the-plugins)
|
||||
|
||||
### - [~~Configure Sub-GHz Remote App~~](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemotePlugin.md) Not recomeded, please use embedded configurator
|
||||
## [- TOTP (Authenticator) config description](https://github.com/akopachov/flipper-zero_authenticator/blob/master/docs/conf-file_description.md)
|
||||
|
||||
## **Plugins**
|
||||
## [- Barcode Generator](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/BarcodeGenerator.md)
|
||||
|
||||
### - TOTP (Authenticator): [config description](https://github.com/akopachov/flipper-zero_authenticator/blob/master/docs/conf-file_description.md)
|
||||
## [- Multi Converter](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/MultiConverter.md)
|
||||
|
||||
### - Mifare Nested plugin: [How to recover keys](https://github.com/AloneLiberty/FlipperNested#how-to-use-it)
|
||||
## [- WAV Player sample files & how to convert](https://github.com/UberGuidoZ/Flipper/tree/main/Wav_Player#readme)
|
||||
|
||||
### - Barcode Generator: [How to use](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/BarcodeGenerator.md)
|
||||
## [- Sub-GHz playlist generator script](https://github.com/darmiel/flipper-scripts/blob/main/playlist/playlist_creator_by_chunk.py)
|
||||
|
||||
### - Multi Converter: [How to use](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/MultiConverter.md)
|
||||
### **Plugins that works with external hardware**
|
||||
|
||||
### - WAV Player: [sample files & how to convert](https://github.com/UberGuidoZ/Flipper/tree/main/Wav_Player#readme)
|
||||
## [- How to use: Unitemp - Temperature sensors reader](https://github.com/quen0n/unitemp-flipperzero#readme)
|
||||
|
||||
### - Sub-GHz playlist: [generator script](https://github.com/darmiel/flipper-scripts/blob/main/playlist/playlist_creator_by_chunk.py)
|
||||
## [- How to use: [NMEA] GPS](https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/gps_nmea_uart/README.md)
|
||||
|
||||
## **Plugins that works with external hardware** [GPIO]
|
||||
## [- How to use: i2c Tools](https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/flipper_i2ctools/README.md)
|
||||
|
||||
### - Unitemp - Temperature sensors reader: [How to use & supported sensors](https://github.com/quen0n/unitemp-flipperzero#readme)
|
||||
## [- How to use: [NRF24] plugins](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/NRF24.md)
|
||||
|
||||
### - [NMEA] GPS: [How to use](https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/gps_nmea_uart/README.md)
|
||||
## [- How to use: [WiFi] Scanner](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module#readme)
|
||||
|
||||
### - i2c Tools [How to use](https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/flipper_i2ctools/README.md)
|
||||
## [- How to use: [ESP8266] Deauther](https://github.com/SequoiaSan/FlipperZero-Wifi-ESP8266-Deauther-Module#readme)
|
||||
|
||||
### - [NRF24] plugins: [How to use](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/NRF24.md)
|
||||
## [- How to use: [ESP32] WiFi Marauder](https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard)
|
||||
|
||||
## [- How to use: [ESP32-CAM] Camera Suite](https://github.com/CodyTolene/Flipper-Zero-Camera-Suite)
|
||||
|
||||
### - [WiFi] Scanner: [How to use](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module#readme) | [Web Flasher](https://sequoiasan.github.io/FlipperZero-WiFi-Scanner_Module/)
|
||||
## [- [WiFi] Scanner - Web Flasher for module firmware](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/)
|
||||
## [- [ESP8266] Deauther - Web Flasher for module firmware](https://sequoiasan.github.io/FlipperZero-Wifi-ESP8266-Deauther-Module/)
|
||||
|
||||
### - [ESP32] WiFi Marauder: [How to use](https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard)<sub> docs by UberGuidoZ</sub> | [Marauder repo](https://github.com/justcallmekoko/ESP32Marauder)
|
||||
## [- Windows: How to Upload .bin to ESP32/ESP8266](https://github.com/SequoiaSan/Guide-How-To-Upload-bin-to-ESP8266-ESP32)
|
||||
|
||||
### - [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](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SentrySafe.md)
|
||||
## [- How to use: [GPIO] SentrySafe plugin](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SentrySafe.md)
|
||||
|
||||
<br>
|
||||
<br>
|
||||
@@ -255,7 +218,13 @@ See full list and sources here: [xMasterX/all-the-plugins](https://github.com/xM
|
||||
|
||||
# Links
|
||||
|
||||
* Official Docs: [docs.flipper.net](https://docs.flipper.net/)
|
||||
* Unofficial Discord: [discord.unleashedflip.com](https://discord.unleashedflip.com)
|
||||
* Hello world - plugin tutorial (English): [https://github.com/DroomOne/Flipper-Plugin-Tutorial](https://github.com/DroomOne/Flipper-Plugin-Tutorial)
|
||||
* Hello world - plugin tutorial (in Russian): [https://yakovlev.me/hello-flipper-zero/](https://yakovlev.me/hello-flipper-zero/)
|
||||
* CLion IDE - How to setup workspace for flipper firmware development: [https://krasovs.ky/2022/11/01/flipper-zero-clion.html](https://krasovs.ky/2022/11/01/flipper-zero-clion.html)
|
||||
* Docs by atmanos / How to write your own app (outdated API): [https://flipper.atmanos.com/docs/overview/intro](https://flipper.atmanos.com/docs/overview/intro)
|
||||
|
||||
* Official Docs: [http://docs.flipperzero.one](http://docs.flipperzero.one)
|
||||
* Official Forum: [forum.flipperzero.one](https://forum.flipperzero.one/)
|
||||
|
||||
# Project structure
|
||||
|
||||
57
SConstruct
57
SConstruct
@@ -67,22 +67,20 @@ if GetOption("fullenv") or any(
|
||||
# Target for self-update package
|
||||
dist_basic_arguments = [
|
||||
"--bundlever",
|
||||
"${UPDATE_VERSION_STRING}",
|
||||
'"${UPDATE_VERSION_STRING}"',
|
||||
]
|
||||
dist_radio_arguments = [
|
||||
"--radio",
|
||||
"${ROOT_DIR.abspath}/${COPRO_STACK_BIN_DIR}/${COPRO_STACK_BIN}",
|
||||
'"${ROOT_DIR.abspath}/${COPRO_STACK_BIN_DIR}/${COPRO_STACK_BIN}"',
|
||||
"--radiotype",
|
||||
"${COPRO_STACK_TYPE}",
|
||||
"${COPRO_DISCLAIMER}",
|
||||
"--obdata",
|
||||
"${ROOT_DIR.abspath}/${COPRO_OB_DATA}",
|
||||
"--stackversion",
|
||||
"${COPRO_CUBE_VERSION}",
|
||||
'"${ROOT_DIR.abspath}/${COPRO_OB_DATA}"',
|
||||
]
|
||||
dist_resource_arguments = [
|
||||
"-r",
|
||||
firmware_env.subst("${RESOURCES_ROOT}"),
|
||||
'"${ROOT_DIR.abspath}/assets/resources"',
|
||||
]
|
||||
dist_splash_arguments = (
|
||||
[
|
||||
@@ -95,7 +93,7 @@ if GetOption("fullenv") or any(
|
||||
|
||||
selfupdate_dist = distenv.DistCommand(
|
||||
"updater_package",
|
||||
(distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES_MANIFEST"]),
|
||||
(distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES"]),
|
||||
DIST_EXTRA=[
|
||||
*dist_basic_arguments,
|
||||
*dist_radio_arguments,
|
||||
@@ -128,8 +126,7 @@ if GetOption("fullenv") or any(
|
||||
|
||||
# Installation over USB & CLI
|
||||
usb_update_package = distenv.AddUsbFlashTarget(
|
||||
"#build/usbinstall.flag",
|
||||
(firmware_env["FW_RESOURCES_MANIFEST"], selfupdate_dist),
|
||||
"#build/usbinstall.flag", (firmware_env["FW_RESOURCES"], selfupdate_dist)
|
||||
)
|
||||
distenv.Alias("flash_usb_full", usb_update_package)
|
||||
|
||||
@@ -167,25 +164,17 @@ Depends(
|
||||
list(app_artifact.validator for app_artifact in external_app_list),
|
||||
)
|
||||
Alias("fap_dist", fap_dist)
|
||||
# distenv.Default(fap_dist)
|
||||
|
||||
distenv.Depends(firmware_env["FW_RESOURCES"], external_apps_artifacts.resources_dist)
|
||||
|
||||
# Copy all faps to device
|
||||
|
||||
fap_deploy = distenv.PhonyTarget(
|
||||
"fap_deploy",
|
||||
[
|
||||
[
|
||||
"${PYTHON3}",
|
||||
"${FBT_SCRIPT_DIR}/storage.py",
|
||||
"-p",
|
||||
"${FLIP_PORT}",
|
||||
"send",
|
||||
"${SOURCE}",
|
||||
"/ext/apps",
|
||||
]
|
||||
],
|
||||
source=firmware_env.Dir(("${RESOURCES_ROOT}/apps")),
|
||||
"${PYTHON3} ${FBT_SCRIPT_DIR}/storage.py -p ${FLIP_PORT} send ${SOURCE} /ext/apps",
|
||||
source=Dir("#/assets/resources/apps"),
|
||||
)
|
||||
Depends(fap_deploy, firmware_env["FW_RESOURCES_MANIFEST"])
|
||||
|
||||
|
||||
# Target for bundling core2 package for qFlipper
|
||||
@@ -196,15 +185,27 @@ copro_dist = distenv.CoproBuilder(
|
||||
distenv.AlwaysBuild(copro_dist)
|
||||
distenv.Alias("copro_dist", copro_dist)
|
||||
|
||||
|
||||
firmware_flash = distenv.AddFwFlashTarget(firmware_env)
|
||||
firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env)
|
||||
distenv.Alias("flash", firmware_flash)
|
||||
|
||||
# To be implemented in fwflash.py
|
||||
firmware_jflash = distenv.AddJFlashTarget(firmware_env)
|
||||
distenv.Alias("jflash", firmware_jflash)
|
||||
|
||||
distenv.PhonyTarget(
|
||||
firmware_bm_flash = distenv.PhonyTarget(
|
||||
"flash_blackmagic",
|
||||
"$GDB $GDBOPTS $SOURCES $GDBFLASH",
|
||||
source=firmware_env["FW_ELF"],
|
||||
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
|
||||
GDBREMOTE="${BLACKMAGIC_ADDR}",
|
||||
GDBFLASH=[
|
||||
"-ex",
|
||||
"load",
|
||||
"-ex",
|
||||
"quit",
|
||||
],
|
||||
)
|
||||
|
||||
gdb_backtrace_all_threads = distenv.PhonyTarget(
|
||||
"gdb_trace_all",
|
||||
"$GDB $GDBOPTS $SOURCES $GDBFLASH",
|
||||
source=firmware_env["FW_ELF"],
|
||||
@@ -323,7 +324,9 @@ distenv.PhonyTarget(
|
||||
)
|
||||
|
||||
# Start Flipper CLI via PySerial's miniterm
|
||||
distenv.PhonyTarget("cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py -p ${FLIP_PORT}")
|
||||
distenv.PhonyTarget(
|
||||
"cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py -p ${FLIP_PORT}"
|
||||
)
|
||||
|
||||
# Update WiFi devboard firmware
|
||||
distenv.PhonyTarget("devboard_flash", "${PYTHON3} ${FBT_SCRIPT_DIR}/wifi_board.py")
|
||||
|
||||
@@ -174,7 +174,7 @@ bool WIEGAND::DoWiegandConversion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO FL-3490: Handle validation failure case!
|
||||
// TODO: Handle validation failure case!
|
||||
} else if(4 == _bitCount) {
|
||||
// 4-bit Wiegand codes have no data integrity check so we just
|
||||
// read the LOW nibble.
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
App(
|
||||
appid="ccid_test",
|
||||
name="CCID Debug",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="ccid_test_app",
|
||||
cdefines=["CCID_TEST"],
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
provides=[
|
||||
"ccid_test",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
)
|
||||
@@ -1,169 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
#include "iso7816_t0_apdu.h"
|
||||
|
||||
typedef enum {
|
||||
EventTypeInput,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewPort* view_port;
|
||||
FuriMessageQueue* event_queue;
|
||||
FuriHalUsbCcidConfig ccid_cfg;
|
||||
} CcidTestApp;
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
InputEvent input;
|
||||
};
|
||||
EventType type;
|
||||
} CcidTestAppEvent;
|
||||
|
||||
typedef enum {
|
||||
CcidTestSubmenuIndexInsertSmartcard,
|
||||
CcidTestSubmenuIndexRemoveSmartcard,
|
||||
CcidTestSubmenuIndexInsertSmartcardReader
|
||||
} SubmenuIndex;
|
||||
|
||||
void icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
iso7816_answer_to_reset(atrBuffer, atrlen);
|
||||
}
|
||||
|
||||
//dataBlock points to the buffer
|
||||
//dataBlockLen tells reader how nany bytes should be read
|
||||
void xfr_datablock_callback(
|
||||
const uint8_t* dataBlock,
|
||||
uint32_t dataBlockLen,
|
||||
uint8_t* responseDataBlock,
|
||||
uint32_t* responseDataBlockLen,
|
||||
void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
struct ISO7816_Command_APDU commandAPDU;
|
||||
iso7816_read_command_apdu(&commandAPDU, dataBlock, dataBlockLen);
|
||||
|
||||
struct ISO7816_Response_APDU responseAPDU;
|
||||
//class not supported
|
||||
responseAPDU.SW1 = 0x6E;
|
||||
responseAPDU.SW2 = 0x00;
|
||||
|
||||
iso7816_write_response_apdu(&responseAPDU, responseDataBlock, responseDataBlockLen);
|
||||
}
|
||||
|
||||
static const CcidCallbacks ccid_cb = {
|
||||
icc_power_on_callback,
|
||||
xfr_datablock_callback,
|
||||
};
|
||||
|
||||
static void ccid_test_app_render_callback(Canvas* canvas, void* ctx) {
|
||||
UNUSED(ctx);
|
||||
canvas_clear(canvas);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 0, 10, "CCID Test App");
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 0, 63, "Hold [back] to exit");
|
||||
}
|
||||
|
||||
static void ccid_test_app_input_callback(InputEvent* input_event, void* ctx) {
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
|
||||
CcidTestAppEvent event;
|
||||
event.type = EventTypeInput;
|
||||
event.input = *input_event;
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
uint32_t ccid_test_exit(void* context) {
|
||||
UNUSED(context);
|
||||
return VIEW_NONE;
|
||||
}
|
||||
|
||||
CcidTestApp* ccid_test_app_alloc() {
|
||||
CcidTestApp* app = malloc(sizeof(CcidTestApp));
|
||||
|
||||
// Gui
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
//viewport
|
||||
app->view_port = view_port_alloc();
|
||||
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
|
||||
view_port_draw_callback_set(app->view_port, ccid_test_app_render_callback, NULL);
|
||||
|
||||
//message queue
|
||||
app->event_queue = furi_message_queue_alloc(8, sizeof(CcidTestAppEvent));
|
||||
furi_check(app->event_queue);
|
||||
view_port_input_callback_set(app->view_port, ccid_test_app_input_callback, app->event_queue);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void ccid_test_app_free(CcidTestApp* app) {
|
||||
furi_assert(app);
|
||||
|
||||
//message queue
|
||||
furi_message_queue_free(app->event_queue);
|
||||
|
||||
//view port
|
||||
gui_remove_view_port(app->gui, app->view_port);
|
||||
view_port_free(app->view_port);
|
||||
|
||||
// Close gui record
|
||||
furi_record_close(RECORD_GUI);
|
||||
app->gui = NULL;
|
||||
|
||||
// Free rest
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t ccid_test_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
//setup view
|
||||
CcidTestApp* app = ccid_test_app_alloc();
|
||||
|
||||
//setup CCID USB
|
||||
// On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
|
||||
app->ccid_cfg.vid = 0x1234;
|
||||
app->ccid_cfg.pid = 0x5678;
|
||||
|
||||
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
|
||||
furi_hal_usb_unlock();
|
||||
furi_hal_ccid_set_callbacks((CcidCallbacks*)&ccid_cb);
|
||||
furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true);
|
||||
|
||||
//handle button events
|
||||
CcidTestAppEvent event;
|
||||
while(1) {
|
||||
FuriStatus event_status =
|
||||
furi_message_queue_get(app->event_queue, &event, FuriWaitForever);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
if(event.type == EventTypeInput) {
|
||||
if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
view_port_update(app->view_port);
|
||||
}
|
||||
|
||||
//tear down USB
|
||||
furi_hal_usb_set_config(usb_mode_prev, NULL);
|
||||
furi_hal_ccid_set_callbacks(NULL);
|
||||
|
||||
//teardown view
|
||||
ccid_test_app_free(app);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/* Implements rudimentary iso7816-3 support for APDU (T=0) */
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <furi.h>
|
||||
#include "iso7816_t0_apdu.h"
|
||||
|
||||
void iso7816_answer_to_reset(uint8_t* dataBuffer, uint32_t* atrlen) {
|
||||
//minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
|
||||
uint8_t AtrBuffer[2] = {
|
||||
0x3B, //TS (direct convention)
|
||||
0x00 // T0 (Y(1): b0000, K: 0 (historical bytes))
|
||||
};
|
||||
*atrlen = 2;
|
||||
|
||||
memcpy(dataBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen));
|
||||
}
|
||||
|
||||
void iso7816_read_command_apdu(
|
||||
struct ISO7816_Command_APDU* command,
|
||||
const uint8_t* dataBuffer,
|
||||
uint32_t dataLen) {
|
||||
UNUSED(dataLen);
|
||||
command->CLA = dataBuffer[0];
|
||||
command->INS = dataBuffer[1];
|
||||
command->P1 = dataBuffer[2];
|
||||
command->P2 = dataBuffer[3];
|
||||
command->Lc = dataBuffer[4];
|
||||
}
|
||||
|
||||
void iso7816_write_response_apdu(
|
||||
const struct ISO7816_Response_APDU* response,
|
||||
uint8_t* dataBuffer,
|
||||
uint32_t* dataLen) {
|
||||
dataBuffer[0] = response->SW1;
|
||||
dataBuffer[1] = response->SW2;
|
||||
*dataLen = 2;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#ifndef _ISO7816_T0_APDU_H_
|
||||
#define _ISO7816_T0_APDU_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct ISO7816_Command_APDU {
|
||||
//header
|
||||
uint8_t CLA;
|
||||
uint8_t INS;
|
||||
uint8_t P1;
|
||||
uint8_t P2;
|
||||
|
||||
//body
|
||||
uint8_t Lc;
|
||||
uint8_t Le;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ISO7816_Response_APDU {
|
||||
uint8_t SW1;
|
||||
uint8_t SW2;
|
||||
} __attribute__((packed));
|
||||
|
||||
void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen);
|
||||
void iso7816_read_command_apdu(
|
||||
struct ISO7816_Command_APDU* command,
|
||||
const uint8_t* dataBuffer,
|
||||
uint32_t dataLen);
|
||||
void iso7816_write_response_apdu(
|
||||
const struct ISO7816_Response_APDU* response,
|
||||
uint8_t* dataBuffer,
|
||||
uint32_t* dataLen);
|
||||
#endif //_ISO7816_T0_APDU_H_
|
||||
@@ -71,7 +71,7 @@ static void direct_draw_run(DirectDraw* instance) {
|
||||
size_t counter = 0;
|
||||
float fps = 0;
|
||||
|
||||
furi_thread_set_current_priority(FuriThreadPriorityIdle);
|
||||
vTaskPrioritySet(furi_thread_get_current_id(), FuriThreadPriorityIdle);
|
||||
|
||||
do {
|
||||
size_t elapsed = DWT->CYCCNT - start;
|
||||
|
||||
@@ -21,51 +21,22 @@ static void rpc_debug_app_tick_event_callback(void* context) {
|
||||
scene_manager_handle_tick_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static void
|
||||
rpc_debug_app_format_hex(const uint8_t* data, size_t data_size, char* buf, size_t buf_size) {
|
||||
if(data == NULL || data_size == 0) {
|
||||
strncpy(buf, "<Data empty>", buf_size);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t byte_width = 3;
|
||||
const size_t line_width = 7;
|
||||
|
||||
data_size = MIN(data_size, buf_size / (byte_width + 1));
|
||||
|
||||
for(size_t i = 0; i < data_size; ++i) {
|
||||
char* p = buf + (i * byte_width);
|
||||
char sep = !((i + 1) % line_width) ? '\n' : ' ';
|
||||
snprintf(p, byte_width + 1, "%02X%c", data[i], sep);
|
||||
}
|
||||
|
||||
buf[buf_size - 1] = '\0';
|
||||
}
|
||||
|
||||
static void rpc_debug_app_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
|
||||
static void rpc_debug_app_rpc_command_callback(RpcAppSystemEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
RpcDebugApp* app = context;
|
||||
furi_assert(app->rpc);
|
||||
|
||||
if(event->type == RpcAppEventTypeSessionClose) {
|
||||
if(event == RpcAppEventSessionClose) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
rpc_system_app_set_callback(app->rpc, NULL, NULL);
|
||||
app->rpc = NULL;
|
||||
} else if(event->type == RpcAppEventTypeAppExit) {
|
||||
} else if(event == RpcAppEventAppExit) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
rpc_system_app_confirm(app->rpc, true);
|
||||
} else if(event->type == RpcAppEventTypeDataExchange) {
|
||||
furi_assert(event->data.type == RpcAppSystemEventDataTypeBytes);
|
||||
|
||||
rpc_debug_app_format_hex(
|
||||
event->data.bytes.ptr, event->data.bytes.size, app->text_store, TEXT_STORE_SIZE);
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange);
|
||||
rpc_system_app_confirm(app->rpc, RpcAppEventAppExit, true);
|
||||
} else {
|
||||
rpc_system_app_confirm(app->rpc, false);
|
||||
rpc_system_app_confirm(app->rpc, event, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
#include "../rpc_debug_app.h"
|
||||
|
||||
static void rpc_debug_app_scene_start_format_hex(
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
char* buf,
|
||||
size_t buf_size) {
|
||||
furi_assert(data);
|
||||
furi_assert(buf);
|
||||
|
||||
const size_t byte_width = 3;
|
||||
const size_t line_width = 7;
|
||||
|
||||
data_size = MIN(data_size, buf_size / (byte_width + 1));
|
||||
|
||||
for(size_t i = 0; i < data_size; ++i) {
|
||||
char* p = buf + (i * byte_width);
|
||||
char sep = !((i + 1) % line_width) ? '\n' : ' ';
|
||||
snprintf(p, byte_width + 1, "%02X%c", data[i], sep);
|
||||
}
|
||||
|
||||
buf[buf_size - 1] = '\0';
|
||||
}
|
||||
|
||||
static void rpc_debug_app_scene_receive_data_exchange_callback(
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
void* context) {
|
||||
RpcDebugApp* app = context;
|
||||
if(data) {
|
||||
rpc_debug_app_scene_start_format_hex(data, data_size, app->text_store, TEXT_STORE_SIZE);
|
||||
} else {
|
||||
strncpy(app->text_store, "<Data empty>", TEXT_STORE_SIZE);
|
||||
}
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange);
|
||||
}
|
||||
|
||||
void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) {
|
||||
RpcDebugApp* app = context;
|
||||
strncpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE);
|
||||
@@ -7,6 +42,8 @@ void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) {
|
||||
text_box_set_text(app->text_box, app->text_store);
|
||||
text_box_set_font(app->text_box, TextBoxFontHex);
|
||||
|
||||
rpc_system_app_set_data_exchange_callback(
|
||||
app->rpc, rpc_debug_app_scene_receive_data_exchange_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextBox);
|
||||
}
|
||||
|
||||
@@ -16,7 +53,6 @@ bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneMana
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RpcDebugAppCustomEventRpcDataExchange) {
|
||||
rpc_system_app_confirm(app->rpc, true);
|
||||
notification_message(app->notifications, &sequence_blink_cyan_100);
|
||||
notification_message(app->notifications, &sequence_display_backlight_on);
|
||||
text_box_set_text(app->text_box, app->text_store);
|
||||
@@ -30,4 +66,5 @@ bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneMana
|
||||
void rpc_debug_app_scene_receive_data_exchange_on_exit(void* context) {
|
||||
RpcDebugApp* app = context;
|
||||
text_box_reset(app->text_box);
|
||||
rpc_system_app_set_data_exchange_callback(app->rpc, NULL, NULL);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ static void subghz_test_packet_rx_callback(bool level, uint32_t duration, void*
|
||||
subghz_decoder_princeton_for_testing_parse(instance->decoder, level, duration);
|
||||
}
|
||||
|
||||
//todo
|
||||
static void subghz_test_packet_rx_pt_callback(SubGhzDecoderPrinceton* parser, void* context) {
|
||||
UNUSED(parser);
|
||||
furi_assert(context);
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
cdefines=["APP_UNIT_TESTS"],
|
||||
requires=["system_settings"],
|
||||
provides=["delay_test"],
|
||||
resources="resources",
|
||||
order=100,
|
||||
)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ void bt_test_alloc() {
|
||||
}
|
||||
|
||||
void bt_test_free() {
|
||||
furi_check(bt_test);
|
||||
furi_assert(bt_test);
|
||||
free(bt_test->nvm_ram_buff_ref);
|
||||
free(bt_test->nvm_ram_buff_dut);
|
||||
bt_keys_storage_free(bt_test->bt_keys_storage);
|
||||
@@ -89,7 +89,7 @@ static void bt_test_keys_remove_test_file() {
|
||||
}
|
||||
|
||||
MU_TEST(bt_test_keys_storage_serial_profile) {
|
||||
furi_check(bt_test);
|
||||
furi_assert(bt_test);
|
||||
|
||||
bt_test_keys_remove_test_file();
|
||||
bt_test_keys_storage_profile();
|
||||
|
||||
@@ -26,6 +26,7 @@ void test_furi_memmgr() {
|
||||
mu_assert_int_eq(66, ((uint8_t*)ptr)[i]);
|
||||
}
|
||||
|
||||
// TODO: fix realloc to copy only old size, and write testcase that leftover of reallocated memory is zero-initialized
|
||||
free(ptr);
|
||||
|
||||
// allocate and zero-initialize array (calloc)
|
||||
|
||||
@@ -69,7 +69,7 @@ MU_TEST(mu_test_furi_string_mem) {
|
||||
mu_check(string != NULL);
|
||||
mu_check(!furi_string_empty(string));
|
||||
|
||||
// TODO FL-3493: how to test furi_string_reserve?
|
||||
// TODO: how to test furi_string_reserve?
|
||||
|
||||
// test furi_string_reset
|
||||
furi_string_reset(string);
|
||||
|
||||
@@ -5,11 +5,6 @@
|
||||
#include "../minunit.h"
|
||||
|
||||
#define DATA_SIZE 4
|
||||
#define EEPROM_ADDRESS 0b10101000
|
||||
#define EEPROM_ADDRESS_HIGH (EEPROM_ADDRESS | 0b10)
|
||||
#define EEPROM_SIZE 512
|
||||
#define EEPROM_PAGE_SIZE 16
|
||||
#define EEPROM_WRITE_DELAY_MS 6
|
||||
|
||||
static void furi_hal_i2c_int_setup() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
@@ -19,14 +14,6 @@ static void furi_hal_i2c_int_teardown() {
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
|
||||
static void furi_hal_i2c_ext_setup() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
|
||||
}
|
||||
|
||||
static void furi_hal_i2c_ext_teardown() {
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_i2c_int_1b) {
|
||||
bool ret = false;
|
||||
uint8_t data_one = 0;
|
||||
@@ -116,116 +103,14 @@ MU_TEST(furi_hal_i2c_int_1b_fail) {
|
||||
mu_assert(data_one != 0, "9 invalid data");
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_i2c_int_ext_3b) {
|
||||
bool ret = false;
|
||||
uint8_t data_many[DATA_SIZE] = {0};
|
||||
|
||||
// 3 byte: read
|
||||
data_many[0] = LP5562_CHANNEL_BLUE_CURRENT_REGISTER;
|
||||
ret = furi_hal_i2c_tx_ext(
|
||||
&furi_hal_i2c_handle_power,
|
||||
LP5562_ADDRESS,
|
||||
false,
|
||||
data_many,
|
||||
1,
|
||||
FuriHalI2cBeginStart,
|
||||
FuriHalI2cEndAwaitRestart,
|
||||
LP5562_I2C_TIMEOUT);
|
||||
mu_assert(ret, "3 tx failed");
|
||||
|
||||
// Send a RESTART condition, then read the 3 bytes one after the other
|
||||
ret = furi_hal_i2c_rx_ext(
|
||||
&furi_hal_i2c_handle_power,
|
||||
LP5562_ADDRESS,
|
||||
false,
|
||||
data_many + 1,
|
||||
1,
|
||||
FuriHalI2cBeginRestart,
|
||||
FuriHalI2cEndPause,
|
||||
LP5562_I2C_TIMEOUT);
|
||||
mu_assert(ret, "4 rx failed");
|
||||
mu_assert(data_many[1] != 0, "4 invalid data");
|
||||
ret = furi_hal_i2c_rx_ext(
|
||||
&furi_hal_i2c_handle_power,
|
||||
LP5562_ADDRESS,
|
||||
false,
|
||||
data_many + 2,
|
||||
1,
|
||||
FuriHalI2cBeginResume,
|
||||
FuriHalI2cEndPause,
|
||||
LP5562_I2C_TIMEOUT);
|
||||
mu_assert(ret, "5 rx failed");
|
||||
mu_assert(data_many[2] != 0, "5 invalid data");
|
||||
ret = furi_hal_i2c_rx_ext(
|
||||
&furi_hal_i2c_handle_power,
|
||||
LP5562_ADDRESS,
|
||||
false,
|
||||
data_many + 3,
|
||||
1,
|
||||
FuriHalI2cBeginResume,
|
||||
FuriHalI2cEndStop,
|
||||
LP5562_I2C_TIMEOUT);
|
||||
mu_assert(ret, "6 rx failed");
|
||||
mu_assert(data_many[3] != 0, "6 invalid data");
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_i2c_ext_eeprom) {
|
||||
if(!furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, EEPROM_ADDRESS, 100)) {
|
||||
printf("no device connected, skipping\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
uint8_t buffer[EEPROM_SIZE] = {0};
|
||||
|
||||
for(size_t page = 0; page < (EEPROM_SIZE / EEPROM_PAGE_SIZE); ++page) {
|
||||
// Fill page buffer
|
||||
for(size_t page_byte = 0; page_byte < EEPROM_PAGE_SIZE; ++page_byte) {
|
||||
// Each byte is its position in the EEPROM modulo 256
|
||||
uint8_t byte = ((page * EEPROM_PAGE_SIZE) + page_byte) % 256;
|
||||
|
||||
buffer[page_byte] = byte;
|
||||
}
|
||||
|
||||
uint8_t address = (page < 16) ? EEPROM_ADDRESS : EEPROM_ADDRESS_HIGH;
|
||||
|
||||
ret = furi_hal_i2c_write_mem(
|
||||
&furi_hal_i2c_handle_external,
|
||||
address,
|
||||
page * EEPROM_PAGE_SIZE,
|
||||
buffer,
|
||||
EEPROM_PAGE_SIZE,
|
||||
20);
|
||||
|
||||
mu_assert(ret, "EEPROM write failed");
|
||||
furi_delay_ms(EEPROM_WRITE_DELAY_MS);
|
||||
}
|
||||
|
||||
ret = furi_hal_i2c_read_mem(
|
||||
&furi_hal_i2c_handle_external, EEPROM_ADDRESS, 0, buffer, EEPROM_SIZE, 100);
|
||||
|
||||
mu_assert(ret, "EEPROM read failed");
|
||||
|
||||
for(size_t pos = 0; pos < EEPROM_SIZE; ++pos) {
|
||||
mu_assert_int_eq(pos % 256, buffer[pos]);
|
||||
}
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(furi_hal_i2c_int_suite) {
|
||||
MU_SUITE_CONFIGURE(&furi_hal_i2c_int_setup, &furi_hal_i2c_int_teardown);
|
||||
MU_RUN_TEST(furi_hal_i2c_int_1b);
|
||||
MU_RUN_TEST(furi_hal_i2c_int_3b);
|
||||
MU_RUN_TEST(furi_hal_i2c_int_ext_3b);
|
||||
MU_RUN_TEST(furi_hal_i2c_int_1b_fail);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(furi_hal_i2c_ext_suite) {
|
||||
MU_SUITE_CONFIGURE(&furi_hal_i2c_ext_setup, &furi_hal_i2c_ext_teardown);
|
||||
MU_RUN_TEST(furi_hal_i2c_ext_eeprom);
|
||||
}
|
||||
|
||||
int run_minunit_test_furi_hal() {
|
||||
MU_RUN_SUITE(furi_hal_i2c_int_suite);
|
||||
MU_RUN_SUITE(furi_hal_i2c_ext_suite);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ static void infrared_test_alloc() {
|
||||
}
|
||||
|
||||
static void infrared_test_free() {
|
||||
furi_check(test);
|
||||
furi_assert(test);
|
||||
infrared_free_decoder(test->decoder_handler);
|
||||
infrared_free_encoder(test->encoder_handler);
|
||||
flipper_format_free(test->ff);
|
||||
|
||||
@@ -311,7 +311,7 @@ MU_TEST(test_bit_lib_test_parity) {
|
||||
}
|
||||
|
||||
MU_TEST(test_bit_lib_remove_bit_every_nth) {
|
||||
// TODO FL-3494: more tests
|
||||
// TODO: more tests
|
||||
uint8_t data_i[1] = {0b00001111};
|
||||
uint8_t data_o[1] = {0b00011111};
|
||||
size_t length;
|
||||
|
||||
@@ -22,7 +22,7 @@ MU_TEST(manifest_iteration_test) {
|
||||
ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(storage);
|
||||
do {
|
||||
// Open manifest file
|
||||
if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("unit_tests/Manifest_test"))) {
|
||||
if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("unit_tests/Manifest"))) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,458 +0,0 @@
|
||||
#ifdef FW_CFG_unit_tests
|
||||
|
||||
#include <lib/nfc/nfc.h>
|
||||
#include <lib/nfc/helpers/iso14443_crc.h>
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define NFC_MAX_BUFFER_SIZE (256)
|
||||
|
||||
typedef enum {
|
||||
NfcTransportLogLevelWarning,
|
||||
NfcTransportLogLevelInfo,
|
||||
} NfcTransportLogLevel;
|
||||
|
||||
FuriMessageQueue* poller_queue = NULL;
|
||||
FuriMessageQueue* listener_queue = NULL;
|
||||
|
||||
typedef enum {
|
||||
NfcMessageTypeTx,
|
||||
NfcMessageTypeTimeout,
|
||||
NfcMessageTypeAbort,
|
||||
} NfcMessageType;
|
||||
|
||||
typedef struct {
|
||||
uint16_t data_bits;
|
||||
uint8_t data[NFC_MAX_BUFFER_SIZE];
|
||||
} NfcMessageData;
|
||||
|
||||
typedef struct {
|
||||
NfcMessageType type;
|
||||
NfcMessageData data;
|
||||
} NfcMessage;
|
||||
|
||||
typedef enum {
|
||||
NfcStateIdle,
|
||||
NfcStateReady,
|
||||
NfcStateReset,
|
||||
} NfcState;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3aColResStatusIdle,
|
||||
Iso14443_3aColResStatusInProgress,
|
||||
Iso14443_3aColResStatusDone,
|
||||
} Iso14443_3aColResStatus;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_3aSensResp sens_resp;
|
||||
Iso14443_3aSddResp sdd_resp[2];
|
||||
Iso14443_3aSelResp sel_resp[2];
|
||||
} Iso14443_3aColResData;
|
||||
|
||||
struct Nfc {
|
||||
NfcState state;
|
||||
|
||||
Iso14443_3aColResStatus col_res_status;
|
||||
Iso14443_3aColResData col_res_data;
|
||||
|
||||
NfcEventCallback callback;
|
||||
void* context;
|
||||
|
||||
NfcMode mode;
|
||||
|
||||
FuriThread* worker_thread;
|
||||
};
|
||||
|
||||
static void nfc_test_print(
|
||||
NfcTransportLogLevel log_level,
|
||||
const char* message,
|
||||
uint8_t* buffer,
|
||||
uint16_t bits) {
|
||||
FuriString* str = furi_string_alloc();
|
||||
size_t bytes = (bits + 7) / 8;
|
||||
|
||||
for(size_t i = 0; i < bytes; i++) {
|
||||
furi_string_cat_printf(str, " %02X", buffer[i]);
|
||||
}
|
||||
if(log_level == NfcTransportLogLevelWarning) {
|
||||
FURI_LOG_W(message, "%s", furi_string_get_cstr(str));
|
||||
} else {
|
||||
FURI_LOG_I(message, "%s", furi_string_get_cstr(str));
|
||||
}
|
||||
|
||||
furi_string_free(str);
|
||||
}
|
||||
|
||||
static void nfc_prepare_col_res_data(
|
||||
Nfc* instance,
|
||||
uint8_t* uid,
|
||||
uint8_t uid_len,
|
||||
uint8_t* atqa,
|
||||
uint8_t sak) {
|
||||
memcpy(instance->col_res_data.sens_resp.sens_resp, atqa, 2);
|
||||
|
||||
if(uid_len == 7) {
|
||||
instance->col_res_data.sdd_resp[0].nfcid[0] = 0x88;
|
||||
memcpy(&instance->col_res_data.sdd_resp[0].nfcid[1], uid, 3);
|
||||
uint8_t bss = 0;
|
||||
for(size_t i = 0; i < 4; i++) {
|
||||
bss ^= instance->col_res_data.sdd_resp[0].nfcid[i];
|
||||
}
|
||||
instance->col_res_data.sdd_resp[0].bss = bss;
|
||||
instance->col_res_data.sel_resp[0].sak = 0x04;
|
||||
|
||||
memcpy(instance->col_res_data.sdd_resp[1].nfcid, &uid[3], 4);
|
||||
bss = 0;
|
||||
for(size_t i = 0; i < 4; i++) {
|
||||
bss ^= instance->col_res_data.sdd_resp[1].nfcid[i];
|
||||
}
|
||||
instance->col_res_data.sdd_resp[1].bss = bss;
|
||||
instance->col_res_data.sel_resp[1].sak = sak;
|
||||
|
||||
} else {
|
||||
furi_crash("Not supporting not 7 bytes");
|
||||
}
|
||||
}
|
||||
|
||||
Nfc* nfc_alloc() {
|
||||
Nfc* instance = malloc(sizeof(Nfc));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void nfc_free(Nfc* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void nfc_config(Nfc* instance, NfcMode mode, NfcTech tech) {
|
||||
UNUSED(instance);
|
||||
UNUSED(tech);
|
||||
|
||||
instance->mode = mode;
|
||||
}
|
||||
|
||||
void nfc_set_fdt_poll_fc(Nfc* instance, uint32_t fdt_poll_fc) {
|
||||
UNUSED(instance);
|
||||
UNUSED(fdt_poll_fc);
|
||||
}
|
||||
|
||||
void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc) {
|
||||
UNUSED(instance);
|
||||
UNUSED(fdt_listen_fc);
|
||||
}
|
||||
|
||||
void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc) {
|
||||
UNUSED(instance);
|
||||
UNUSED(mask_rx_time_fc);
|
||||
}
|
||||
|
||||
void nfc_set_fdt_poll_poll_us(Nfc* instance, uint32_t fdt_poll_poll_us) {
|
||||
UNUSED(instance);
|
||||
UNUSED(fdt_poll_poll_us);
|
||||
}
|
||||
|
||||
void nfc_set_guard_time_us(Nfc* instance, uint32_t guard_time_us) {
|
||||
UNUSED(instance);
|
||||
UNUSED(guard_time_us);
|
||||
}
|
||||
|
||||
NfcError nfc_iso14443a_listener_set_col_res_data(
|
||||
Nfc* instance,
|
||||
uint8_t* uid,
|
||||
uint8_t uid_len,
|
||||
uint8_t* atqa,
|
||||
uint8_t sak) {
|
||||
furi_check(instance);
|
||||
furi_check(uid);
|
||||
furi_check(atqa);
|
||||
|
||||
nfc_prepare_col_res_data(instance, uid, uid_len, atqa, sak);
|
||||
|
||||
return NfcErrorNone;
|
||||
}
|
||||
|
||||
static int32_t nfc_worker_poller(void* context) {
|
||||
Nfc* instance = context;
|
||||
furi_check(instance->callback);
|
||||
|
||||
instance->state = NfcStateReady;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
NfcEvent event = {};
|
||||
|
||||
while(true) {
|
||||
event.type = NfcEventTypePollerReady;
|
||||
command = instance->callback(event, instance->context);
|
||||
if(command == NfcCommandStop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
instance->state = NfcStateIdle;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, uint16_t rx_bits) {
|
||||
furi_check(instance->col_res_status != Iso14443_3aColResStatusDone);
|
||||
BitBuffer* tx_buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE);
|
||||
|
||||
bool processed = false;
|
||||
|
||||
if((rx_bits == 7) && (rx_data[0] == 0x52)) {
|
||||
instance->col_res_status = Iso14443_3aColResStatusInProgress;
|
||||
bit_buffer_copy_bytes(
|
||||
tx_buffer,
|
||||
instance->col_res_data.sens_resp.sens_resp,
|
||||
sizeof(instance->col_res_data.sens_resp.sens_resp));
|
||||
nfc_listener_tx(instance, tx_buffer);
|
||||
processed = true;
|
||||
} else if(rx_bits == 2 * 8) {
|
||||
if((rx_data[0] == 0x93) && (rx_data[1] == 0x20)) {
|
||||
bit_buffer_copy_bytes(
|
||||
tx_buffer,
|
||||
(const uint8_t*)&instance->col_res_data.sdd_resp[0],
|
||||
sizeof(Iso14443_3aSddResp));
|
||||
nfc_listener_tx(instance, tx_buffer);
|
||||
processed = true;
|
||||
} else if((rx_data[0] == 0x95) && (rx_data[1] == 0x20)) {
|
||||
bit_buffer_copy_bytes(
|
||||
tx_buffer,
|
||||
(const uint8_t*)&instance->col_res_data.sdd_resp[1],
|
||||
sizeof(Iso14443_3aSddResp));
|
||||
nfc_listener_tx(instance, tx_buffer);
|
||||
processed = true;
|
||||
}
|
||||
} else if(rx_bits == 9 * 8) {
|
||||
if((rx_data[0] == 0x93) && (rx_data[1] == 0x70)) {
|
||||
bit_buffer_set_size_bytes(tx_buffer, 1);
|
||||
bit_buffer_set_byte(tx_buffer, 0, instance->col_res_data.sel_resp[0].sak);
|
||||
iso14443_crc_append(Iso14443CrcTypeA, tx_buffer);
|
||||
nfc_listener_tx(instance, tx_buffer);
|
||||
processed = true;
|
||||
} else if((rx_data[0] == 0x95) && (rx_data[1] == 0x70)) {
|
||||
bit_buffer_set_size_bytes(tx_buffer, 1);
|
||||
bit_buffer_set_byte(tx_buffer, 0, instance->col_res_data.sel_resp[1].sak);
|
||||
iso14443_crc_append(Iso14443CrcTypeA, tx_buffer);
|
||||
nfc_listener_tx(instance, tx_buffer);
|
||||
instance->col_res_status = Iso14443_3aColResStatusDone;
|
||||
NfcEvent event = {.type = NfcEventTypeListenerActivated};
|
||||
instance->callback(event, instance->context);
|
||||
|
||||
processed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!processed) {
|
||||
NfcMessage message = {.type = NfcMessageTypeTimeout};
|
||||
furi_message_queue_put(poller_queue, &message, FuriWaitForever);
|
||||
}
|
||||
|
||||
bit_buffer_free(tx_buffer);
|
||||
}
|
||||
|
||||
static int32_t nfc_worker_listener(void* context) {
|
||||
Nfc* instance = context;
|
||||
furi_check(instance->callback);
|
||||
|
||||
NfcMessage message = {};
|
||||
|
||||
NfcEventData event_data = {};
|
||||
event_data.buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE);
|
||||
NfcEvent nfc_event = {.data = event_data};
|
||||
|
||||
while(true) {
|
||||
furi_message_queue_get(listener_queue, &message, FuriWaitForever);
|
||||
bit_buffer_copy_bits(event_data.buffer, message.data.data, message.data.data_bits);
|
||||
if((message.data.data[0] == 0x52) && (message.data.data_bits == 7)) {
|
||||
instance->col_res_status = Iso14443_3aColResStatusIdle;
|
||||
}
|
||||
|
||||
if(message.type == NfcMessageTypeAbort) {
|
||||
break;
|
||||
} else if(message.type == NfcMessageTypeTx) {
|
||||
nfc_test_print(
|
||||
NfcTransportLogLevelInfo, "RDR", message.data.data, message.data.data_bits);
|
||||
if(instance->col_res_status != Iso14443_3aColResStatusDone) {
|
||||
nfc_worker_listener_pass_col_res(
|
||||
instance, message.data.data, message.data.data_bits);
|
||||
} else {
|
||||
instance->state = NfcStateReady;
|
||||
nfc_event.type = NfcEventTypeRxEnd;
|
||||
instance->callback(nfc_event, instance->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->state = NfcStateIdle;
|
||||
instance->col_res_status = Iso14443_3aColResStatusIdle;
|
||||
memset(&instance->col_res_data, 0, sizeof(instance->col_res_data));
|
||||
bit_buffer_free(nfc_event.data.buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) {
|
||||
furi_check(instance);
|
||||
furi_check(instance->worker_thread == NULL);
|
||||
|
||||
if(instance->mode == NfcModeListener) {
|
||||
furi_check(listener_queue == NULL);
|
||||
// Check that poller didn't start
|
||||
furi_check(poller_queue == NULL);
|
||||
} else {
|
||||
furi_check(poller_queue == NULL);
|
||||
// Check that poller is started after listener
|
||||
furi_check(listener_queue);
|
||||
}
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
|
||||
if(instance->mode == NfcModeListener) {
|
||||
listener_queue = furi_message_queue_alloc(4, sizeof(NfcMessage));
|
||||
} else {
|
||||
poller_queue = furi_message_queue_alloc(4, sizeof(NfcMessage));
|
||||
}
|
||||
|
||||
instance->worker_thread = furi_thread_alloc();
|
||||
furi_thread_set_context(instance->worker_thread, instance);
|
||||
furi_thread_set_priority(instance->worker_thread, FuriThreadPriorityHigh);
|
||||
furi_thread_set_stack_size(instance->worker_thread, 8 * 1024);
|
||||
|
||||
if(instance->mode == NfcModeListener) {
|
||||
furi_thread_set_name(instance->worker_thread, "NfcWorkerListener");
|
||||
furi_thread_set_callback(instance->worker_thread, nfc_worker_listener);
|
||||
} else {
|
||||
furi_thread_set_name(instance->worker_thread, "NfcWorkerPoller");
|
||||
furi_thread_set_callback(instance->worker_thread, nfc_worker_poller);
|
||||
}
|
||||
|
||||
furi_thread_start(instance->worker_thread);
|
||||
}
|
||||
|
||||
void nfc_stop(Nfc* instance) {
|
||||
furi_check(instance);
|
||||
furi_check(instance->worker_thread);
|
||||
|
||||
if(instance->mode == NfcModeListener) {
|
||||
NfcMessage message = {.type = NfcMessageTypeAbort};
|
||||
furi_message_queue_put(listener_queue, &message, FuriWaitForever);
|
||||
furi_thread_join(instance->worker_thread);
|
||||
|
||||
furi_message_queue_free(listener_queue);
|
||||
listener_queue = NULL;
|
||||
|
||||
furi_thread_free(instance->worker_thread);
|
||||
instance->worker_thread = NULL;
|
||||
} else {
|
||||
furi_thread_join(instance->worker_thread);
|
||||
|
||||
furi_message_queue_free(poller_queue);
|
||||
poller_queue = NULL;
|
||||
|
||||
furi_thread_free(instance->worker_thread);
|
||||
instance->worker_thread = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Called from worker thread
|
||||
|
||||
NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer) {
|
||||
furi_check(instance);
|
||||
furi_check(poller_queue);
|
||||
furi_check(listener_queue);
|
||||
furi_check(tx_buffer);
|
||||
|
||||
NfcMessage message = {};
|
||||
message.type = NfcMessageTypeTx;
|
||||
message.data.data_bits = bit_buffer_get_size(tx_buffer);
|
||||
bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer));
|
||||
|
||||
furi_message_queue_put(poller_queue, &message, FuriWaitForever);
|
||||
|
||||
return NfcErrorNone;
|
||||
}
|
||||
|
||||
NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* tx_buffer) {
|
||||
return nfc_listener_tx(instance, tx_buffer);
|
||||
}
|
||||
|
||||
NfcError
|
||||
nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) {
|
||||
furi_check(instance);
|
||||
furi_check(tx_buffer);
|
||||
furi_check(rx_buffer);
|
||||
furi_check(poller_queue);
|
||||
furi_check(listener_queue);
|
||||
UNUSED(fwt);
|
||||
|
||||
NfcError error = NfcErrorNone;
|
||||
|
||||
NfcMessage message = {};
|
||||
message.type = NfcMessageTypeTx;
|
||||
message.data.data_bits = bit_buffer_get_size(tx_buffer);
|
||||
bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer));
|
||||
// Tx
|
||||
furi_check(furi_message_queue_put(listener_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
// Rx
|
||||
FuriStatus status = furi_message_queue_get(poller_queue, &message, 50);
|
||||
|
||||
if(status == FuriStatusErrorTimeout) {
|
||||
error = NfcErrorTimeout;
|
||||
} else if(message.type == NfcMessageTypeTx) {
|
||||
bit_buffer_copy_bits(rx_buffer, message.data.data, message.data.data_bits);
|
||||
nfc_test_print(
|
||||
NfcTransportLogLevelWarning, "TAG", message.data.data, message.data.data_bits);
|
||||
} else if(message.type == NfcMessageTypeTimeout) {
|
||||
error = NfcErrorTimeout;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
NfcError nfc_iso14443a_poller_trx_custom_parity(
|
||||
Nfc* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
return nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt);
|
||||
}
|
||||
|
||||
// Technology specific API
|
||||
|
||||
NfcError nfc_iso14443a_poller_trx_short_frame(
|
||||
Nfc* instance,
|
||||
NfcIso14443aShortFrame frame,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
UNUSED(frame);
|
||||
|
||||
BitBuffer* tx_buffer = bit_buffer_alloc(32);
|
||||
bit_buffer_set_size(tx_buffer, 7);
|
||||
bit_buffer_set_byte(tx_buffer, 0, 0x52);
|
||||
|
||||
NfcError error = nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt);
|
||||
|
||||
bit_buffer_free(tx_buffer);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
NfcError nfc_iso14443a_poller_trx_sdd_frame(
|
||||
Nfc* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
return nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt);
|
||||
}
|
||||
|
||||
NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) {
|
||||
UNUSED(instance);
|
||||
|
||||
return NfcErrorNone;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,66 +0,0 @@
|
||||
Filetype: Flipper NFC device
|
||||
Version: 3
|
||||
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic
|
||||
Device type: NTAG213
|
||||
# UID, ATQA and SAK are common for all formats
|
||||
UID: 04 AC 6B 72 BA 6C 80
|
||||
ATQA: 00 44
|
||||
SAK: 00
|
||||
# Mifare Ultralight specific data
|
||||
Data format version: 1
|
||||
Signature: 2D AE BC AF 84 B8 85 87 C2 FB FE 76 13 58 86 72 8E 1D 3C B5 DA 24 23 44 E5 63 4D 4C 82 FB D7 18
|
||||
Mifare version: 00 04 04 02 01 00 0F 03
|
||||
Counter 0: 0
|
||||
Tearing 0: 00
|
||||
Counter 1: 0
|
||||
Tearing 1: 00
|
||||
Counter 2: 0
|
||||
Tearing 2: 00
|
||||
Pages total: 45
|
||||
Pages read: 45
|
||||
Page 0: 04 AC 6B 4B
|
||||
Page 1: 72 BA 6C 80
|
||||
Page 2: 24 48 00 00
|
||||
Page 3: E1 10 12 00
|
||||
Page 4: 00 00 41 50
|
||||
Page 5: 00 00 31 31
|
||||
Page 6: 00 20 09 28
|
||||
Page 7: 00 03 31 59
|
||||
Page 8: 91 DF D3 00
|
||||
Page 9: 00 00 00 00
|
||||
Page 10: 00 00 00 00
|
||||
Page 11: 00 00 00 00
|
||||
Page 12: 00 00 00 00
|
||||
Page 13: 00 00 00 00
|
||||
Page 14: 00 00 00 00
|
||||
Page 15: 00 00 00 00
|
||||
Page 16: 00 00 00 00
|
||||
Page 17: 00 00 00 00
|
||||
Page 18: 00 00 00 00
|
||||
Page 19: 00 00 00 00
|
||||
Page 20: 00 00 00 00
|
||||
Page 21: 00 00 00 00
|
||||
Page 22: 00 00 00 00
|
||||
Page 23: 00 00 00 00
|
||||
Page 24: 00 00 00 00
|
||||
Page 25: 00 00 00 00
|
||||
Page 26: 00 00 00 00
|
||||
Page 27: 00 00 00 00
|
||||
Page 28: 00 00 00 00
|
||||
Page 29: 00 00 00 00
|
||||
Page 30: 00 00 00 00
|
||||
Page 31: 00 00 00 00
|
||||
Page 32: 00 00 00 00
|
||||
Page 33: 00 00 00 00
|
||||
Page 34: 00 00 00 00
|
||||
Page 35: 00 00 00 00
|
||||
Page 36: 00 00 00 00
|
||||
Page 37: 00 00 00 00
|
||||
Page 38: 00 00 00 00
|
||||
Page 39: 00 00 00 00
|
||||
Page 40: 00 00 00 BD
|
||||
Page 41: 04 00 00 04
|
||||
Page 42: C0 05 00 00
|
||||
Page 43: 95 3F 52 FF
|
||||
Page 44: 00 00 00 00
|
||||
Failed authentication attempts: 0
|
||||
@@ -1,156 +0,0 @@
|
||||
Filetype: Flipper NFC device
|
||||
Version: 3
|
||||
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic
|
||||
Device type: NTAG215
|
||||
# UID, ATQA and SAK are common for all formats
|
||||
UID: 04 51 5C FA 6F 73 81
|
||||
ATQA: 00 44
|
||||
SAK: 00
|
||||
# Mifare Ultralight specific data
|
||||
Data format version: 1
|
||||
Signature: 42 21 E4 6C 79 6A 81 5E EA 0D 93 6D 85 EE 4B 0C 2A 00 D5 77 F1 C5 67 F3 63 75 F8 EB 86 48 5E 6B
|
||||
Mifare version: 00 04 04 02 01 00 11 03
|
||||
Counter 0: 0
|
||||
Tearing 0: 00
|
||||
Counter 1: 0
|
||||
Tearing 1: 00
|
||||
Counter 2: 00
|
||||
Tearing 2: 00
|
||||
Pages total: 135
|
||||
Pages read: 135
|
||||
Page 0: 04 51 5C 81
|
||||
Page 1: FA 6F 73 81
|
||||
Page 2: 67 48 0F E0
|
||||
Page 3: F1 10 FF EE
|
||||
Page 4: A5 00 00 00
|
||||
Page 5: 90 42 74 71
|
||||
Page 6: FD 8F 50 61
|
||||
Page 7: C5 65 1B 54
|
||||
Page 8: EF 68 D0 8E
|
||||
Page 9: 3D 35 DB 83
|
||||
Page 10: D3 00 29 F6
|
||||
Page 11: 42 2A A5 5C
|
||||
Page 12: F1 69 0A FC
|
||||
Page 13: B6 44 E9 6B
|
||||
Page 14: 77 41 88 81
|
||||
Page 15: 86 31 CB AD
|
||||
Page 16: B1 DE F1 AB
|
||||
Page 17: DF 96 C2 C5
|
||||
Page 18: C1 26 99 96
|
||||
Page 19: 85 AF 9F 0E
|
||||
Page 20: 58 FE ED DC
|
||||
Page 21: 0A 0A 00 01
|
||||
Page 22: 03 C1 05 02
|
||||
Page 23: 38 39 34 33
|
||||
Page 24: 49 2D 4E 5C
|
||||
Page 25: 5B 21 0F 44
|
||||
Page 26: 3F 3F 76 69
|
||||
Page 27: B4 72 D8 38
|
||||
Page 28: A0 35 53 51
|
||||
Page 29: 53 EB A6 7C
|
||||
Page 30: 3E 8B 97 C0
|
||||
Page 31: 00 7A 45 13
|
||||
Page 32: 3A 8B D4 0F
|
||||
Page 33: 31 C2 32 CC
|
||||
Page 34: B4 24 A6 1B
|
||||
Page 35: D3 F5 4A 1F
|
||||
Page 36: CD 8F 1D 64
|
||||
Page 37: 01 F4 DF C2
|
||||
Page 38: 11 16 C2 C5
|
||||
Page 39: 30 6D 49 AF
|
||||
Page 40: 10 D4 7C 3C
|
||||
Page 41: 6E 36 4E 08
|
||||
Page 42: 95 76 BC 84
|
||||
Page 43: 35 50 DD F0
|
||||
Page 44: 21 0F EE D9
|
||||
Page 45: 85 19 54 5F
|
||||
Page 46: 3E A9 04 20
|
||||
Page 47: 1B 97 E4 39
|
||||
Page 48: FF 0A 45 F6
|
||||
Page 49: 13 D4 3E DD
|
||||
Page 50: 97 42 FC 67
|
||||
Page 51: 6A AC 78 96
|
||||
Page 52: D1 DA 25 23
|
||||
Page 53: BF 4D B3 76
|
||||
Page 54: F1 21 ED 15
|
||||
Page 55: BD 55 11 C4
|
||||
Page 56: 4E 8C E9 23
|
||||
Page 57: C0 C4 6D 5A
|
||||
Page 58: 58 25 FF 95
|
||||
Page 59: 3C 2B 7A 57
|
||||
Page 60: 66 BE A0 61
|
||||
Page 61: BC FC 4A 31
|
||||
Page 62: 4D AC EE 81
|
||||
Page 63: BE 1A 86 04
|
||||
Page 64: F6 D7 5E B3
|
||||
Page 65: E7 A8 A2 86
|
||||
Page 66: E9 40 AB 47
|
||||
Page 67: C8 36 E4 3E
|
||||
Page 68: A7 4D D3 EA
|
||||
Page 69: 83 9A 64 F7
|
||||
Page 70: 96 6B 5D BF
|
||||
Page 71: 4E A2 A6 0F
|
||||
Page 72: BD 3D BE 7C
|
||||
Page 73: 22 0C 68 51
|
||||
Page 74: 0F 9A B8 AE
|
||||
Page 75: 38 2C C4 CD
|
||||
Page 76: 53 D8 DD 18
|
||||
Page 77: A6 5D 35 87
|
||||
Page 78: C9 6D 99 59
|
||||
Page 79: 61 9F B6 DC
|
||||
Page 80: E6 22 0F 99
|
||||
Page 81: 39 82 79 60
|
||||
Page 82: 58 2E BE F7
|
||||
Page 83: EF F7 95 62
|
||||
Page 84: D5 06 1B 58
|
||||
Page 85: 65 05 A9 08
|
||||
Page 86: 75 ED 5D 90
|
||||
Page 87: 5A E1 7E C9
|
||||
Page 88: 35 D6 29 BB
|
||||
Page 89: D0 67 6C F9
|
||||
Page 90: A0 FF 0B 93
|
||||
Page 91: 22 EA A3 3F
|
||||
Page 92: E2 BD BD 58
|
||||
Page 93: BE 93 D9 94
|
||||
Page 94: 41 CC 7E 40
|
||||
Page 95: E6 8C 5A 43
|
||||
Page 96: 65 C1 24 94
|
||||
Page 97: B9 97 61 13
|
||||
Page 98: AD 74 FF 21
|
||||
Page 99: 0F EC F6 03
|
||||
Page 100: 89 5D 89 E5
|
||||
Page 101: 8D 11 F8 D7
|
||||
Page 102: 33 43 79 2E
|
||||
Page 103: 23 E5 29 B5
|
||||
Page 104: 53 98 13 FF
|
||||
Page 105: E8 79 8B 33
|
||||
Page 106: 45 6C 34 38
|
||||
Page 107: 3B 69 28 D7
|
||||
Page 108: D2 80 B0 2F
|
||||
Page 109: D0 18 D5 DD
|
||||
Page 110: 6C 2D D9 97
|
||||
Page 111: CA 78 B4 A2
|
||||
Page 112: B7 3E B8 79
|
||||
Page 113: A2 BE 54 E4
|
||||
Page 114: C8 28 0C 4A
|
||||
Page 115: 81 E7 EC 1C
|
||||
Page 116: 39 93 6F 70
|
||||
Page 117: 75 77 5C FC
|
||||
Page 118: 66 58 0C 1C
|
||||
Page 119: 9F 70 2E C8
|
||||
Page 120: 52 4A 52 BD
|
||||
Page 121: 56 D5 6A 15
|
||||
Page 122: 54 1B 33 90
|
||||
Page 123: 44 11 C1 07
|
||||
Page 124: 11 5C BA 80
|
||||
Page 125: 10 14 20 9A
|
||||
Page 126: 4A D8 E6 36
|
||||
Page 127: DA B8 59 E5
|
||||
Page 128: 5E 48 95 DA
|
||||
Page 129: 96 6A 26 85
|
||||
Page 130: 01 00 0F BD
|
||||
Page 131: 00 00 00 04
|
||||
Page 132: 5F 00 00 00
|
||||
Page 133: 00 00 00 00
|
||||
Page 134: 00 00 00 00
|
||||
Failed authentication attempts: 0
|
||||
@@ -1,252 +0,0 @@
|
||||
Filetype: Flipper NFC device
|
||||
Version: 2
|
||||
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card
|
||||
Device type: NTAG216
|
||||
# UID, ATQA and SAK are common for all formats
|
||||
UID: 04 D9 65 0A 32 5E 80
|
||||
ATQA: 44 00
|
||||
SAK: 00
|
||||
# Mifare Ultralight specific data
|
||||
Data format version: 1
|
||||
Signature: 48 2A F2 01 0F F2 F5 A7 9A D5 79 6E CB 14 54 48 98 D1 57 5D 8A 23 A9 B0 E8 20 02 3E CD C8 16 DB
|
||||
Mifare version: 00 04 04 02 01 00 13 03
|
||||
Counter 0: 0
|
||||
Tearing 0: 00
|
||||
Counter 1: 0
|
||||
Tearing 1: 00
|
||||
Counter 2: 0
|
||||
Tearing 2: 00
|
||||
Pages total: 231
|
||||
Pages read: 231
|
||||
Page 0: 04 D9 65 30
|
||||
Page 1: 0A 32 5E 80
|
||||
Page 2: E6 48 00 00
|
||||
Page 3: E1 10 6D 00
|
||||
Page 4: 03 37 D1 01
|
||||
Page 5: 33 55 04 6D
|
||||
Page 6: 2E 79 6F 75
|
||||
Page 7: 74 75 62 65
|
||||
Page 8: 2E 63 6F 6D
|
||||
Page 9: 2F 77 61 74
|
||||
Page 10: 63 68 3F 76
|
||||
Page 11: 3D 62 78 71
|
||||
Page 12: 4C 73 72 6C
|
||||
Page 13: 61 6B 4B 38
|
||||
Page 14: 26 66 65 61
|
||||
Page 15: 74 75 72 65
|
||||
Page 16: 3D 79 6F 75
|
||||
Page 17: 74 75 2E 62
|
||||
Page 18: 65 FE 00 00
|
||||
Page 19: 00 00 00 00
|
||||
Page 20: 00 00 00 00
|
||||
Page 21: 00 00 00 00
|
||||
Page 22: 00 00 00 00
|
||||
Page 23: 00 00 00 00
|
||||
Page 24: 00 00 00 00
|
||||
Page 25: 00 00 00 00
|
||||
Page 26: 00 00 00 00
|
||||
Page 27: 00 00 00 00
|
||||
Page 28: 00 00 00 00
|
||||
Page 29: 00 00 00 00
|
||||
Page 30: 00 00 00 00
|
||||
Page 31: 00 00 00 00
|
||||
Page 32: 00 00 00 00
|
||||
Page 33: 00 00 00 00
|
||||
Page 34: 00 00 00 00
|
||||
Page 35: 00 00 00 00
|
||||
Page 36: 00 00 00 00
|
||||
Page 37: 00 00 00 00
|
||||
Page 38: 00 00 00 00
|
||||
Page 39: 00 00 00 00
|
||||
Page 40: 00 00 00 00
|
||||
Page 41: 00 00 00 00
|
||||
Page 42: 00 00 00 00
|
||||
Page 43: 00 00 00 00
|
||||
Page 44: 00 00 00 00
|
||||
Page 45: 00 00 00 00
|
||||
Page 46: 00 00 00 00
|
||||
Page 47: 00 00 00 00
|
||||
Page 48: 00 00 00 00
|
||||
Page 49: 00 00 00 00
|
||||
Page 50: 00 00 00 00
|
||||
Page 51: 00 00 00 00
|
||||
Page 52: 00 00 00 00
|
||||
Page 53: 00 00 00 00
|
||||
Page 54: 00 00 00 00
|
||||
Page 55: 00 00 00 00
|
||||
Page 56: 00 00 00 00
|
||||
Page 57: 00 00 00 00
|
||||
Page 58: 00 00 00 00
|
||||
Page 59: 00 00 00 00
|
||||
Page 60: 00 00 00 00
|
||||
Page 61: 00 00 00 00
|
||||
Page 62: 00 00 00 00
|
||||
Page 63: 00 00 00 00
|
||||
Page 64: 00 00 00 00
|
||||
Page 65: 00 00 00 00
|
||||
Page 66: 00 00 00 00
|
||||
Page 67: 00 00 00 00
|
||||
Page 68: 00 00 00 00
|
||||
Page 69: 00 00 00 00
|
||||
Page 70: 00 00 00 00
|
||||
Page 71: 00 00 00 00
|
||||
Page 72: 00 00 00 00
|
||||
Page 73: 00 00 00 00
|
||||
Page 74: 00 00 00 00
|
||||
Page 75: 00 00 00 00
|
||||
Page 76: 00 00 00 00
|
||||
Page 77: 00 00 00 00
|
||||
Page 78: 00 00 00 00
|
||||
Page 79: 00 00 00 00
|
||||
Page 80: 00 00 00 00
|
||||
Page 81: 00 00 00 00
|
||||
Page 82: 00 00 00 00
|
||||
Page 83: 00 00 00 00
|
||||
Page 84: 00 00 00 00
|
||||
Page 85: 00 00 00 00
|
||||
Page 86: 00 00 00 00
|
||||
Page 87: 00 00 00 00
|
||||
Page 88: 00 00 00 00
|
||||
Page 89: 00 00 00 00
|
||||
Page 90: 00 00 00 00
|
||||
Page 91: 00 00 00 00
|
||||
Page 92: 00 00 00 00
|
||||
Page 93: 00 00 00 00
|
||||
Page 94: 00 00 00 00
|
||||
Page 95: 00 00 00 00
|
||||
Page 96: 00 00 00 00
|
||||
Page 97: 00 00 00 00
|
||||
Page 98: 00 00 00 00
|
||||
Page 99: 00 00 00 00
|
||||
Page 100: 00 00 00 00
|
||||
Page 101: 00 00 00 00
|
||||
Page 102: 00 00 00 00
|
||||
Page 103: 00 00 00 00
|
||||
Page 104: 00 00 00 00
|
||||
Page 105: 00 00 00 00
|
||||
Page 106: 00 00 00 00
|
||||
Page 107: 00 00 00 00
|
||||
Page 108: 00 00 00 00
|
||||
Page 109: 00 00 00 00
|
||||
Page 110: 00 00 00 00
|
||||
Page 111: 00 00 00 00
|
||||
Page 112: 00 00 00 00
|
||||
Page 113: 00 00 00 00
|
||||
Page 114: 00 00 00 00
|
||||
Page 115: 00 00 00 00
|
||||
Page 116: 00 00 00 00
|
||||
Page 117: 00 00 00 00
|
||||
Page 118: 00 00 00 00
|
||||
Page 119: 00 00 00 00
|
||||
Page 120: 00 00 00 00
|
||||
Page 121: 00 00 00 00
|
||||
Page 122: 00 00 00 00
|
||||
Page 123: 00 00 00 00
|
||||
Page 124: 00 00 00 00
|
||||
Page 125: 00 00 00 00
|
||||
Page 126: 00 00 00 00
|
||||
Page 127: 00 00 00 00
|
||||
Page 128: 00 00 00 00
|
||||
Page 129: 00 00 00 00
|
||||
Page 130: 00 00 00 00
|
||||
Page 131: 00 00 00 00
|
||||
Page 132: 00 00 00 00
|
||||
Page 133: 00 00 00 00
|
||||
Page 134: 00 00 00 00
|
||||
Page 135: 00 00 00 00
|
||||
Page 136: 00 00 00 00
|
||||
Page 137: 00 00 00 00
|
||||
Page 138: 00 00 00 00
|
||||
Page 139: 00 00 00 00
|
||||
Page 140: 00 00 00 00
|
||||
Page 141: 00 00 00 00
|
||||
Page 142: 00 00 00 00
|
||||
Page 143: 00 00 00 00
|
||||
Page 144: 00 00 00 00
|
||||
Page 145: 00 00 00 00
|
||||
Page 146: 00 00 00 00
|
||||
Page 147: 00 00 00 00
|
||||
Page 148: 00 00 00 00
|
||||
Page 149: 00 00 00 00
|
||||
Page 150: 00 00 00 00
|
||||
Page 151: 00 00 00 00
|
||||
Page 152: 00 00 00 00
|
||||
Page 153: 00 00 00 00
|
||||
Page 154: 00 00 00 00
|
||||
Page 155: 00 00 00 00
|
||||
Page 156: 00 00 00 00
|
||||
Page 157: 00 00 00 00
|
||||
Page 158: 00 00 00 00
|
||||
Page 159: 00 00 00 00
|
||||
Page 160: 00 00 00 00
|
||||
Page 161: 00 00 00 00
|
||||
Page 162: 00 00 00 00
|
||||
Page 163: 00 00 00 00
|
||||
Page 164: 00 00 00 00
|
||||
Page 165: 00 00 00 00
|
||||
Page 166: 00 00 00 00
|
||||
Page 167: 00 00 00 00
|
||||
Page 168: 00 00 00 00
|
||||
Page 169: 00 00 00 00
|
||||
Page 170: 00 00 00 00
|
||||
Page 171: 00 00 00 00
|
||||
Page 172: 00 00 00 00
|
||||
Page 173: 00 00 00 00
|
||||
Page 174: 00 00 00 00
|
||||
Page 175: 00 00 00 00
|
||||
Page 176: 00 00 00 00
|
||||
Page 177: 00 00 00 00
|
||||
Page 178: 00 00 00 00
|
||||
Page 179: 00 00 00 00
|
||||
Page 180: 00 00 00 00
|
||||
Page 181: 00 00 00 00
|
||||
Page 182: 00 00 00 00
|
||||
Page 183: 00 00 00 00
|
||||
Page 184: 00 00 00 00
|
||||
Page 185: 00 00 00 00
|
||||
Page 186: 00 00 00 00
|
||||
Page 187: 00 00 00 00
|
||||
Page 188: 00 00 00 00
|
||||
Page 189: 00 00 00 00
|
||||
Page 190: 00 00 00 00
|
||||
Page 191: 00 00 00 00
|
||||
Page 192: 00 00 00 00
|
||||
Page 193: 00 00 00 00
|
||||
Page 194: 00 00 00 00
|
||||
Page 195: 00 00 00 00
|
||||
Page 196: 00 00 00 00
|
||||
Page 197: 00 00 00 00
|
||||
Page 198: 00 00 00 00
|
||||
Page 199: 00 00 00 00
|
||||
Page 200: 00 00 00 00
|
||||
Page 201: 00 00 00 00
|
||||
Page 202: 00 00 00 00
|
||||
Page 203: 00 00 00 00
|
||||
Page 204: 00 00 00 00
|
||||
Page 205: 00 00 00 00
|
||||
Page 206: 00 00 00 00
|
||||
Page 207: 00 00 00 00
|
||||
Page 208: 00 00 00 00
|
||||
Page 209: 00 00 00 00
|
||||
Page 210: 00 00 00 00
|
||||
Page 211: 00 00 00 00
|
||||
Page 212: 00 00 00 00
|
||||
Page 213: 00 00 00 00
|
||||
Page 214: 00 00 00 00
|
||||
Page 215: 00 00 00 00
|
||||
Page 216: 00 00 00 00
|
||||
Page 217: 00 00 00 00
|
||||
Page 218: 00 00 00 00
|
||||
Page 219: 00 00 00 00
|
||||
Page 220: 00 00 00 00
|
||||
Page 221: 00 00 00 00
|
||||
Page 222: 00 00 00 00
|
||||
Page 223: 00 00 00 00
|
||||
Page 224: 00 00 00 00
|
||||
Page 225: 00 00 00 00
|
||||
Page 226: 00 00 00 BD
|
||||
Page 227: 04 00 00 FF
|
||||
Page 228: 00 05 00 00
|
||||
Page 229: 00 00 00 00
|
||||
Page 230: 00 00 00 00
|
||||
Failed authentication attempts: 0
|
||||
@@ -1,41 +0,0 @@
|
||||
Filetype: Flipper NFC device
|
||||
Version: 3
|
||||
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic
|
||||
Device type: Mifare Ultralight 11
|
||||
# UID, ATQA and SAK are common for all formats
|
||||
UID: 04 15 74 F2 B0 5E 81
|
||||
ATQA: 00 44
|
||||
SAK: 00
|
||||
# Mifare Ultralight specific data
|
||||
Data format version: 1
|
||||
Signature: A4 37 7D E5 8C 2F 88 D8 04 60 41 6E 3A C8 CD DB 19 94 26 12 C5 D0 12 B0 EB 88 05 72 89 F2 A5 61
|
||||
Mifare version: 00 04 03 01 01 00 0B 03
|
||||
Counter 0: 0
|
||||
Tearing 0: BD
|
||||
Counter 1: 0
|
||||
Tearing 1: BD
|
||||
Counter 2: 0
|
||||
Tearing 2: BD
|
||||
Pages total: 20
|
||||
Pages read: 20
|
||||
Page 0: 04 15 74 ED
|
||||
Page 1: F2 B0 5E 81
|
||||
Page 2: 9D 48 F8 FF
|
||||
Page 3: C1 31 3E 3F
|
||||
Page 4: B0 00 F0 02
|
||||
Page 5: 2F B3 45 A0
|
||||
Page 6: D4 9C 02 F2
|
||||
Page 7: 4A B1 ED FF
|
||||
Page 8: C8 01 00 02
|
||||
Page 9: 4F B3 46 70
|
||||
Page 10: EE F6 60 B0
|
||||
Page 11: B6 C6 12 1B
|
||||
Page 12: B9 1E 49 C3
|
||||
Page 13: 49 DF 7A 57
|
||||
Page 14: 08 52 2A 11
|
||||
Page 15: 28 0A 28 59
|
||||
Page 16: 00 00 00 FF
|
||||
Page 17: 00 05 00 00
|
||||
Page 18: FF FF FF FF
|
||||
Page 19: 00 00 00 00
|
||||
Failed authentication attempts: 0
|
||||
@@ -1,62 +0,0 @@
|
||||
Filetype: Flipper NFC device
|
||||
Version: 3
|
||||
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic
|
||||
Device type: Mifare Ultralight 21
|
||||
# UID, ATQA and SAK are common for all formats
|
||||
UID: 34 BF AB B1 AE 73 D6
|
||||
ATQA: 00 44
|
||||
SAK: 00
|
||||
# Mifare Ultralight specific data
|
||||
Data format version: 1
|
||||
Signature: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Mifare version: 00 34 21 01 01 00 0E 03
|
||||
Counter 0: 0
|
||||
Tearing 0: 00
|
||||
Counter 1: 0
|
||||
Tearing 1: 00
|
||||
Counter 2: 0
|
||||
Tearing 2: 00
|
||||
Pages total: 41
|
||||
Pages read: 41
|
||||
Page 0: 34 BF AB A8
|
||||
Page 1: B1 AE 73 D6
|
||||
Page 2: BA 00 70 08
|
||||
Page 3: FF FF FF FC
|
||||
Page 4: 45 D9 BB A0
|
||||
Page 5: 5D 9D FA 00
|
||||
Page 6: 80 70 38 40
|
||||
Page 7: 12 30 02 00
|
||||
Page 8: 00 00 00 00
|
||||
Page 9: 00 00 00 00
|
||||
Page 10: AC A1 0D E4
|
||||
Page 11: 80 70 38 40
|
||||
Page 12: 00 57 A0 01
|
||||
Page 13: 00 08 C1 40
|
||||
Page 14: 00 00 00 00
|
||||
Page 15: AC A1 0D E4
|
||||
Page 16: 00 00 00 00
|
||||
Page 17: 00 00 00 00
|
||||
Page 18: 00 00 00 00
|
||||
Page 19: 00 00 00 00
|
||||
Page 20: 00 00 00 00
|
||||
Page 21: 00 00 00 00
|
||||
Page 22: 00 00 00 00
|
||||
Page 23: 00 00 00 00
|
||||
Page 24: 00 00 00 00
|
||||
Page 25: 00 00 00 00
|
||||
Page 26: 00 00 00 00
|
||||
Page 27: 00 00 00 00
|
||||
Page 28: 00 00 00 00
|
||||
Page 29: 00 00 00 00
|
||||
Page 30: 00 00 00 00
|
||||
Page 31: 00 00 00 00
|
||||
Page 32: 00 00 00 00
|
||||
Page 33: 00 00 00 00
|
||||
Page 34: 00 00 00 00
|
||||
Page 35: 00 00 00 00
|
||||
Page 36: 00 00 00 BD
|
||||
Page 37: 00 00 00 FF
|
||||
Page 38: 00 05 00 00
|
||||
Page 39: FF FF FF FF
|
||||
Page 40: 00 00 00 00
|
||||
Failed authentication attempts: 0
|
||||
@@ -18,8 +18,6 @@
|
||||
#include <cli/cli.h>
|
||||
#include <loader/loader.h>
|
||||
#include <protobuf_version.h>
|
||||
|
||||
#include <FreeRTOS.h>
|
||||
#include <semphr.h>
|
||||
|
||||
LIST_DEF(MsgList, PB_Main, M_POD_OPLIST)
|
||||
@@ -38,7 +36,7 @@ typedef struct {
|
||||
FuriStreamBuffer* output_stream;
|
||||
SemaphoreHandle_t close_session_semaphore;
|
||||
SemaphoreHandle_t terminate_semaphore;
|
||||
uint32_t timeout;
|
||||
TickType_t timeout;
|
||||
} RpcSessionContext;
|
||||
|
||||
static RpcSessionContext rpc_session[TEST_RPC_SESSIONS];
|
||||
@@ -69,6 +67,7 @@ static RpcSessionContext rpc_session[TEST_RPC_SESSIONS];
|
||||
} while(0)
|
||||
|
||||
static void output_bytes_callback(void* ctx, uint8_t* got_bytes, size_t got_size);
|
||||
static void clean_directory(Storage* fs_api, const char* clean_dir);
|
||||
static void
|
||||
test_rpc_add_empty_to_list(MsgList_t msg_list, PB_CommandStatus status, uint32_t command_id);
|
||||
static void test_rpc_encode_and_feed(MsgList_t msg_list, uint8_t session);
|
||||
@@ -150,41 +149,11 @@ static void test_rpc_teardown_second_session(void) {
|
||||
rpc_session[1].session = NULL;
|
||||
}
|
||||
|
||||
static void test_rpc_storage_clean_directory(Storage* fs_api, const char* clean_dir) {
|
||||
furi_check(fs_api);
|
||||
furi_check(clean_dir);
|
||||
storage_simply_remove_recursive(fs_api, clean_dir);
|
||||
FS_Error error = storage_common_mkdir(fs_api, clean_dir);
|
||||
furi_check(error == FSE_OK);
|
||||
}
|
||||
|
||||
static void test_rpc_storage_create_file(Storage* fs_api, const char* path, size_t size) {
|
||||
File* file = storage_file_alloc(fs_api);
|
||||
|
||||
bool success = false;
|
||||
do {
|
||||
if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) break;
|
||||
if(!storage_file_seek(file, size, true)) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
|
||||
furi_check(success);
|
||||
}
|
||||
|
||||
static void test_rpc_storage_setup(void) {
|
||||
test_rpc_setup();
|
||||
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
test_rpc_storage_clean_directory(fs_api, TEST_DIR_NAME);
|
||||
test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file100", 100);
|
||||
test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file250", 250);
|
||||
test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file500", 200);
|
||||
test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file1000", 1000);
|
||||
test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file2500", 2500);
|
||||
test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file5000", 5000);
|
||||
clean_directory(fs_api, TEST_DIR_NAME);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
@@ -192,7 +161,7 @@ static void test_rpc_storage_teardown(void) {
|
||||
test_rpc_teardown();
|
||||
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
test_rpc_storage_clean_directory(fs_api, TEST_DIR_NAME);
|
||||
clean_directory(fs_api, TEST_DIR_NAME);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
@@ -210,6 +179,36 @@ static void test_rpc_session_terminated_callback(void* context) {
|
||||
xSemaphoreGive(callbacks_context->terminate_semaphore);
|
||||
}
|
||||
|
||||
static void clean_directory(Storage* fs_api, const char* clean_dir) {
|
||||
furi_check(fs_api);
|
||||
furi_check(clean_dir);
|
||||
|
||||
File* dir = storage_file_alloc(fs_api);
|
||||
if(storage_dir_open(dir, clean_dir)) {
|
||||
FileInfo fileinfo;
|
||||
char* name = malloc(MAX_NAME_LENGTH + 1);
|
||||
while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
|
||||
size_t size = strlen(clean_dir) + strlen(name) + 1 + 1;
|
||||
char* fullname = malloc(size);
|
||||
snprintf(fullname, size, "%s/%s", clean_dir, name);
|
||||
if(file_info_is_dir(&fileinfo)) {
|
||||
clean_directory(fs_api, fullname);
|
||||
}
|
||||
FS_Error error = storage_common_remove(fs_api, fullname);
|
||||
furi_check(error == FSE_OK);
|
||||
free(fullname);
|
||||
}
|
||||
free(name);
|
||||
} else {
|
||||
FS_Error error = storage_common_mkdir(fs_api, clean_dir);
|
||||
(void)error;
|
||||
furi_check(error == FSE_OK);
|
||||
}
|
||||
|
||||
storage_dir_close(dir);
|
||||
storage_file_free(dir);
|
||||
}
|
||||
|
||||
static void test_rpc_print_message_list(MsgList_t msg_list) {
|
||||
#if DEBUG_PRINT
|
||||
MsgList_reverse(msg_list);
|
||||
@@ -283,40 +282,24 @@ static void test_rpc_add_ping_to_list(MsgList_t msg_list, bool request, uint32_t
|
||||
response->which_content = (request == PING_REQUEST) ? PB_Main_system_ping_request_tag :
|
||||
PB_Main_system_ping_response_tag;
|
||||
}
|
||||
static void test_rpc_fill_basic_message(PB_Main* message, uint16_t tag, uint32_t command_id) {
|
||||
message->command_id = command_id;
|
||||
message->command_status = PB_CommandStatus_OK;
|
||||
message->cb_content.funcs.encode = NULL;
|
||||
message->which_content = tag;
|
||||
message->has_next = false;
|
||||
}
|
||||
|
||||
static void test_rpc_create_storage_list_request(
|
||||
PB_Main* message,
|
||||
const char* path,
|
||||
bool include_md5,
|
||||
uint32_t command_id,
|
||||
uint32_t filter_max_size) {
|
||||
furi_check(message);
|
||||
furi_check(path);
|
||||
test_rpc_fill_basic_message(message, PB_Main_storage_list_request_tag, command_id);
|
||||
message->content.storage_list_request.path = strdup(path);
|
||||
message->content.storage_list_request.include_md5 = include_md5;
|
||||
message->content.storage_list_request.filter_max_size = filter_max_size;
|
||||
}
|
||||
|
||||
static void test_rpc_create_simple_message(
|
||||
PB_Main* message,
|
||||
uint16_t tag,
|
||||
const char* str,
|
||||
uint32_t command_id) {
|
||||
uint32_t command_id,
|
||||
bool flag) {
|
||||
furi_check(message);
|
||||
|
||||
char* str_copy = NULL;
|
||||
if(str) {
|
||||
str_copy = strdup(str);
|
||||
}
|
||||
test_rpc_fill_basic_message(message, tag, command_id);
|
||||
message->command_id = command_id;
|
||||
message->command_status = PB_CommandStatus_OK;
|
||||
message->cb_content.funcs.encode = NULL;
|
||||
message->which_content = tag;
|
||||
message->has_next = false;
|
||||
switch(tag) {
|
||||
case PB_Main_storage_info_request_tag:
|
||||
message->content.storage_info_request.path = str_copy;
|
||||
@@ -324,6 +307,10 @@ static void test_rpc_create_simple_message(
|
||||
case PB_Main_storage_stat_request_tag:
|
||||
message->content.storage_stat_request.path = str_copy;
|
||||
break;
|
||||
case PB_Main_storage_list_request_tag:
|
||||
message->content.storage_list_request.path = str_copy;
|
||||
message->content.storage_list_request.include_md5 = flag;
|
||||
break;
|
||||
case PB_Main_storage_mkdir_request_tag:
|
||||
message->content.storage_mkdir_request.path = str_copy;
|
||||
break;
|
||||
@@ -546,7 +533,7 @@ static bool test_rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_
|
||||
RpcSessionContext* session_context = istream->state;
|
||||
size_t bytes_received = 0;
|
||||
|
||||
uint32_t now = furi_get_tick();
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
int32_t time_left = session_context->timeout - now;
|
||||
time_left = MAX(time_left, 0);
|
||||
bytes_received =
|
||||
@@ -586,29 +573,11 @@ static void
|
||||
message->content.storage_list_response.file[2].name = str;
|
||||
}
|
||||
|
||||
static bool test_rpc_system_storage_list_filter(
|
||||
const FileInfo* fileinfo,
|
||||
const char* name,
|
||||
size_t filter_max_size) {
|
||||
bool result = false;
|
||||
|
||||
do {
|
||||
if(!path_contains_only_ascii(name)) break;
|
||||
if(filter_max_size) {
|
||||
if(fileinfo->size > filter_max_size) break;
|
||||
}
|
||||
result = true;
|
||||
} while(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void test_rpc_storage_list_create_expected_list(
|
||||
MsgList_t msg_list,
|
||||
const char* path,
|
||||
uint32_t command_id,
|
||||
bool append_md5,
|
||||
size_t filter_max_size) {
|
||||
bool append_md5) {
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
File* dir = storage_file_alloc(fs_api);
|
||||
|
||||
@@ -646,7 +615,7 @@ static void test_rpc_storage_list_create_expected_list(
|
||||
i = 0;
|
||||
}
|
||||
|
||||
if(test_rpc_system_storage_list_filter(&fileinfo, name, filter_max_size)) {
|
||||
if(path_contains_only_ascii(name)) {
|
||||
list->file[i].type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR :
|
||||
PB_Storage_File_FileType_FILE;
|
||||
list->file[i].size = fileinfo.size;
|
||||
@@ -690,7 +659,7 @@ static void test_rpc_decode_and_compare(MsgList_t expected_msg_list, uint8_t ses
|
||||
furi_check(!MsgList_empty_p(expected_msg_list));
|
||||
furi_check(session < TEST_RPC_SESSIONS);
|
||||
|
||||
rpc_session[session].timeout = furi_get_tick() + MAX_RECEIVE_OUTPUT_TIMEOUT;
|
||||
rpc_session[session].timeout = xTaskGetTickCount() + MAX_RECEIVE_OUTPUT_TIMEOUT;
|
||||
pb_istream_t istream = {
|
||||
.callback = test_rpc_pb_stream_read,
|
||||
.state = &rpc_session[session],
|
||||
@@ -714,7 +683,7 @@ static void test_rpc_decode_and_compare(MsgList_t expected_msg_list, uint8_t ses
|
||||
pb_release(&PB_Main_msg, &result);
|
||||
}
|
||||
|
||||
rpc_session[session].timeout = furi_get_tick() + 50;
|
||||
rpc_session[session].timeout = xTaskGetTickCount() + 50;
|
||||
if(pb_decode_ex(&istream, &PB_Main_msg, &result, PB_DECODE_DELIMITED)) {
|
||||
mu_fail("decoded more than expected");
|
||||
}
|
||||
@@ -729,21 +698,17 @@ static void test_rpc_free_msg_list(MsgList_t msg_list) {
|
||||
MsgList_clear(msg_list);
|
||||
}
|
||||
|
||||
static void test_rpc_storage_list_run(
|
||||
const char* path,
|
||||
uint32_t command_id,
|
||||
bool md5,
|
||||
size_t filter_max_size) {
|
||||
static void test_rpc_storage_list_run(const char* path, uint32_t command_id, bool md5) {
|
||||
PB_Main request;
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_storage_list_request(&request, path, md5, command_id, filter_max_size);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_list_request_tag, path, command_id, md5);
|
||||
if(!strcmp(path, "/")) {
|
||||
test_rpc_storage_list_create_expected_list_root(expected_msg_list, command_id);
|
||||
} else {
|
||||
test_rpc_storage_list_create_expected_list(
|
||||
expected_msg_list, path, command_id, md5, filter_max_size);
|
||||
test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id, md5);
|
||||
}
|
||||
test_rpc_encode_and_feed_one(&request, 0);
|
||||
test_rpc_decode_and_compare(expected_msg_list, 0);
|
||||
@@ -753,32 +718,25 @@ static void test_rpc_storage_list_run(
|
||||
}
|
||||
|
||||
MU_TEST(test_storage_list) {
|
||||
test_rpc_storage_list_run("/", ++command_id, false, 0);
|
||||
test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, false, 0);
|
||||
test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, false, 0);
|
||||
test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, false, 0);
|
||||
test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, false, 0);
|
||||
test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, false, 0);
|
||||
test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, false, 0);
|
||||
test_rpc_storage_list_run("error_path", ++command_id, false, 0);
|
||||
test_rpc_storage_list_run("/", ++command_id, false);
|
||||
test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, false);
|
||||
test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, false);
|
||||
test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, false);
|
||||
test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, false);
|
||||
test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, false);
|
||||
test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, false);
|
||||
test_rpc_storage_list_run("error_path", ++command_id, false);
|
||||
}
|
||||
|
||||
MU_TEST(test_storage_list_md5) {
|
||||
test_rpc_storage_list_run("/", ++command_id, true, 0);
|
||||
test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, true, 0);
|
||||
test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, true, 0);
|
||||
test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, true, 0);
|
||||
test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, true, 0);
|
||||
test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, true, 0);
|
||||
test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, true, 0);
|
||||
test_rpc_storage_list_run("error_path", ++command_id, true, 0);
|
||||
}
|
||||
|
||||
MU_TEST(test_storage_list_size) {
|
||||
test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 0);
|
||||
test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 1);
|
||||
test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 1000);
|
||||
test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 2500);
|
||||
test_rpc_storage_list_run("/", ++command_id, true);
|
||||
test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, true);
|
||||
test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, true);
|
||||
test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, true);
|
||||
test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, true);
|
||||
test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, true);
|
||||
test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, true);
|
||||
test_rpc_storage_list_run("error_path", ++command_id, true);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -846,7 +804,8 @@ static void test_storage_read_run(const char* path, uint32_t command_id) {
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_add_read_to_list_by_reading_real_file(expected_msg_list, path, command_id);
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_read_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_read_request_tag, path, command_id, false);
|
||||
test_rpc_encode_and_feed_one(&request, 0);
|
||||
test_rpc_decode_and_compare(expected_msg_list, 0);
|
||||
|
||||
@@ -900,7 +859,8 @@ static void test_rpc_storage_info_run(const char* path, uint32_t command_id) {
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_info_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_info_request_tag, path, command_id, false);
|
||||
|
||||
PB_Main* response = MsgList_push_new(expected_msg_list);
|
||||
response->command_id = command_id;
|
||||
@@ -932,7 +892,8 @@ static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) {
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_stat_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_stat_request_tag, path, command_id, false);
|
||||
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
FileInfo fileinfo;
|
||||
@@ -1044,7 +1005,11 @@ static void test_storage_write_read_run(
|
||||
test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, *command_id);
|
||||
|
||||
test_rpc_create_simple_message(
|
||||
MsgList_push_raw(input_msg_list), PB_Main_storage_read_request_tag, path, ++*command_id);
|
||||
MsgList_push_raw(input_msg_list),
|
||||
PB_Main_storage_read_request_tag,
|
||||
path,
|
||||
++*command_id,
|
||||
false);
|
||||
test_rpc_add_read_or_write_to_list(
|
||||
expected_msg_list,
|
||||
READ_RESPONSE,
|
||||
@@ -1117,7 +1082,8 @@ MU_TEST(test_storage_interrupt_continuous_same_system) {
|
||||
MsgList_push_new(input_msg_list),
|
||||
PB_Main_storage_mkdir_request_tag,
|
||||
TEST_DIR "dir1",
|
||||
command_id + 1);
|
||||
command_id + 1,
|
||||
false);
|
||||
test_rpc_add_read_or_write_to_list(
|
||||
input_msg_list,
|
||||
WRITE_REQUEST,
|
||||
@@ -1197,7 +1163,8 @@ static void test_storage_delete_run(
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_delete_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_delete_request_tag, path, command_id, false);
|
||||
request.content.storage_delete_request.recursive = recursive;
|
||||
test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
|
||||
|
||||
@@ -1278,7 +1245,8 @@ static void test_storage_mkdir_run(const char* path, size_t command_id, PB_Comma
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_mkdir_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_mkdir_request_tag, path, command_id, false);
|
||||
test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
|
||||
|
||||
test_rpc_encode_and_feed_one(&request, 0);
|
||||
@@ -1329,11 +1297,12 @@ static void test_storage_md5sum_run(
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_md5sum_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_md5sum_request_tag, path, command_id, false);
|
||||
if(status == PB_CommandStatus_OK) {
|
||||
PB_Main* response = MsgList_push_new(expected_msg_list);
|
||||
test_rpc_create_simple_message(
|
||||
response, PB_Main_storage_md5sum_response_tag, md5sum, command_id);
|
||||
response, PB_Main_storage_md5sum_response_tag, md5sum, command_id, false);
|
||||
response->command_status = status;
|
||||
} else {
|
||||
test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
|
||||
@@ -1492,7 +1461,6 @@ MU_TEST_SUITE(test_rpc_storage) {
|
||||
MU_RUN_TEST(test_storage_stat);
|
||||
MU_RUN_TEST(test_storage_list);
|
||||
MU_RUN_TEST(test_storage_list_md5);
|
||||
MU_RUN_TEST(test_storage_list_size);
|
||||
MU_RUN_TEST(test_storage_read);
|
||||
MU_RUN_TEST(test_storage_write_read);
|
||||
MU_RUN_TEST(test_storage_write);
|
||||
@@ -1791,7 +1759,8 @@ MU_TEST(test_rpc_multisession_storage) {
|
||||
MsgList_push_raw(input_0),
|
||||
PB_Main_storage_read_request_tag,
|
||||
TEST_DIR "file0.txt",
|
||||
++command_id);
|
||||
++command_id,
|
||||
false);
|
||||
test_rpc_add_read_or_write_to_list(
|
||||
expected_0, READ_RESPONSE, TEST_DIR "file0.txt", pattern, sizeof(pattern), 1, command_id);
|
||||
|
||||
@@ -1799,7 +1768,8 @@ MU_TEST(test_rpc_multisession_storage) {
|
||||
MsgList_push_raw(input_1),
|
||||
PB_Main_storage_read_request_tag,
|
||||
TEST_DIR "file1.txt",
|
||||
++command_id);
|
||||
++command_id,
|
||||
false);
|
||||
test_rpc_add_read_or_write_to_list(
|
||||
expected_1, READ_RESPONSE, TEST_DIR "file1.txt", pattern, sizeof(pattern), 1, command_id);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <lib/subghz/devices/devices.h>
|
||||
#include <lib/subghz/devices/cc1101_configs.h>
|
||||
|
||||
#define TAG "SubGhzTest"
|
||||
#define TAG "SubGhz TEST"
|
||||
#define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes")
|
||||
#define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo")
|
||||
#define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s")
|
||||
@@ -42,6 +42,8 @@ static void subghz_test_rx_callback(
|
||||
|
||||
static void subghz_test_init(void) {
|
||||
environment_handler = subghz_environment_alloc();
|
||||
subghz_environment_set_came_atomo_rainbow_table_file_name(
|
||||
environment_handler, CAME_ATOMO_DIR_NAME);
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
environment_handler, NICE_FLOR_S_DIR_NAME);
|
||||
subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
|
||||
@@ -96,9 +98,9 @@ static bool subghz_decoder_test(const char* path, const char* name_decoder) {
|
||||
}
|
||||
subghz_file_encoder_worker_free(file_worker_encoder_handler);
|
||||
}
|
||||
FURI_LOG_T(TAG, "Decoder count parse %d", subghz_test_decoder_count);
|
||||
FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
|
||||
if(furi_get_tick() - test_start > TEST_TIMEOUT) {
|
||||
printf("Test decoder %s ERROR TimeOut\r\n", name_decoder);
|
||||
printf("\033[0;31mTest decoder %s ERROR TimeOut\033[0m\r\n", name_decoder);
|
||||
return false;
|
||||
} else {
|
||||
return subghz_test_decoder_count ? true : false;
|
||||
@@ -135,9 +137,9 @@ static bool subghz_decode_random_test(const char* path) {
|
||||
}
|
||||
subghz_file_encoder_worker_free(file_worker_encoder_handler);
|
||||
}
|
||||
FURI_LOG_D(TAG, "Decoder count parse %d", subghz_test_decoder_count);
|
||||
FURI_LOG_D(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
|
||||
if(furi_get_tick() - test_start > TEST_TIMEOUT * 10) {
|
||||
printf("Random test ERROR TimeOut\r\n");
|
||||
printf("\033[0;31mRandom test ERROR TimeOut\033[0m\r\n");
|
||||
return false;
|
||||
} else if(subghz_test_decoder_count == TEST_RANDOM_COUNT_PARSE) {
|
||||
return true;
|
||||
@@ -198,9 +200,10 @@ static bool subghz_encoder_test(const char* path) {
|
||||
subghz_transmitter_free(transmitter);
|
||||
}
|
||||
flipper_format_free(fff_data_file);
|
||||
FURI_LOG_T(TAG, "Decoder count parse %d", subghz_test_decoder_count);
|
||||
FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
|
||||
if(furi_get_tick() - test_start > TEST_TIMEOUT) {
|
||||
printf("Test encoder %s ERROR TimeOut\r\n", furi_string_get_cstr(temp_str));
|
||||
printf(
|
||||
"\033[0;31mTest encoder %s ERROR TimeOut\033[0m\r\n", furi_string_get_cstr(temp_str));
|
||||
subghz_test_decoder_count = 0;
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
|
||||
@@ -65,8 +65,8 @@ const UnitTest unit_tests[] = {
|
||||
void minunit_print_progress() {
|
||||
static const char progress[] = {'\\', '|', '/', '-'};
|
||||
static uint8_t progress_counter = 0;
|
||||
static uint32_t last_tick = 0;
|
||||
uint32_t current_tick = furi_get_tick();
|
||||
static TickType_t last_tick = 0;
|
||||
TickType_t current_tick = xTaskGetTickCount();
|
||||
if(current_tick - last_tick > 20) {
|
||||
last_tick = current_tick;
|
||||
printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]);
|
||||
@@ -90,7 +90,7 @@ void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// TODO FL-3491: lock device while test running
|
||||
// TODO: lock device while test running
|
||||
if(loader_is_locked(loader)) {
|
||||
printf("RPC: stop all applications to run tests\r\n");
|
||||
notification_message(notification, &sequence_blink_magenta_100);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <cc1101.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define TAG "SubGhzDeviceCc1101Ext"
|
||||
#define TAG "SubGhz_Device_CC1101_Ext"
|
||||
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO &gpio_ext_pc3
|
||||
@@ -357,7 +357,7 @@ bool subghz_device_cc1101_ext_rx_pipe_not_empty() {
|
||||
(CC1101_STATUS_RXBYTES) | CC1101_BURST,
|
||||
(uint8_t*)status);
|
||||
furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
// TODO: Find reason why RXFIFO_OVERFLOW doesnt work correctly
|
||||
// TODO: you can add a buffer overflow flag if needed
|
||||
if(status->NUM_RXBYTES > 0) {
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "cc1101_ext.h"
|
||||
#include <lib/subghz/devices/cc1101_configs.h>
|
||||
|
||||
#define TAG "SubGhzDeviceCc1101Ext"
|
||||
#define TAG "SubGhzDeviceCC1101Ext"
|
||||
|
||||
static bool subghz_device_cc1101_ext_interconnect_is_frequency_valid(uint32_t frequency) {
|
||||
bool ret = subghz_device_cc1101_ext_is_frequency_valid(frequency);
|
||||
|
||||
@@ -19,7 +19,7 @@ We recommend to use the `APP_ASSETS_PATH` macro to get the path to the Apps Asse
|
||||
|
||||
## What is the difference between the Apps Assets folder and the Apps Data folder?
|
||||
|
||||
The Apps Assets folder is used to store the data <u>provided</u> with the application. For example, if you want to create a game, you can store game levels (content data) in the Apps Assets folder.
|
||||
The Apps Assets folder is used to store the data <u>provided</u> with the application. For example, if you want to create a game, you can store game levels (contant data) in the Apps Assets folder.
|
||||
|
||||
The Apps Data folder is used to store data <u>generated</u> by the application. For example, if you want to create a game, you can save the progress of the game (user-generated data) in the Apps Data folder.
|
||||
|
||||
@@ -55,4 +55,4 @@ When app is launched, the `files` folder will be unpacked to the Apps Assets fol
|
||||
|
||||
The data is unpacked when the application starts, if the application is launched for the first time, or if the data within the application is updated.
|
||||
|
||||
When an application is compiled, the contents of the "files" folder are hashed and stored within the application itself. When the application starts, this hash is compared to the hash stored in the `.assets.signature` file. If the hashes differ or the `.assets.signature` file does not exist, the application folder is deleted and the new data is unpacked.
|
||||
When an application is compiled, the contents of the "files" folder are hashed and stored within the application itself. When the application starts, this hash is compared to the hash stored in the `.assets.signature` file. If the hashes differ or the `.assets.signature` file does not exist, the application folder is deleted and the new data is unpacked.
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
|
||||
// Define log tag
|
||||
#define TAG "ExampleAppsAssets"
|
||||
#define TAG "example_apps_assets"
|
||||
|
||||
static void example_apps_data_print_file_content(Storage* storage, const char* path) {
|
||||
Stream* stream = file_stream_alloc(storage);
|
||||
|
||||
@@ -19,6 +19,6 @@ We recommend to use the `APP_DATA_PATH` macro to get the path to the Apps Data f
|
||||
|
||||
## What is the difference between the Apps Assets folder and the Apps Data folder?
|
||||
|
||||
The Apps Assets folder is used to store the data <u>provided</u> with the application. For example, if you want to create a game, you can store game levels (content data) in the Apps Assets folder.
|
||||
The Apps Assets folder is used to store the data <u>provided</u> with the application. For example, if you want to create a game, you can store game levels (contant data) in the Apps Assets folder.
|
||||
|
||||
The Apps Data folder is used to store data <u>generated</u> by the application. For example, if you want to create a game, you can save the progress of the game (user-generated data) in the Apps Data folder.
|
||||
The Apps Data folder is used to store data <u>generated</u> by the application. For example, if you want to create a game, you can save the progress of the game (user-generated data) in the Apps Data folder.
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <storage/storage.h>
|
||||
|
||||
// Define log tag
|
||||
#define TAG "ExampleAppsData"
|
||||
#define TAG "example_apps_data"
|
||||
|
||||
// Application entry point
|
||||
int32_t example_apps_data_main(void* p) {
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="example_plugins_app",
|
||||
stack_size=2 * 1024,
|
||||
fap_category="Examples",
|
||||
sources=["*.c", "!plugin*.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -22,7 +21,6 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="example_plugin1_ep",
|
||||
requires=["example_plugins", "example_plugins_multi"],
|
||||
sources=["plugin1.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -30,5 +28,4 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="example_plugin2_ep",
|
||||
requires=["example_plugins_multi"],
|
||||
sources=["plugin2.c"],
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define TAG "ExamplePlugins"
|
||||
#define TAG "example_plugins"
|
||||
|
||||
int32_t example_plugins_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "ExamplePlugins"
|
||||
#define TAG "example_plugins"
|
||||
|
||||
int32_t example_plugins_multi_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
/* Common interface between a plugin and host application */
|
||||
/* Common interface between a plugin and host applicaion */
|
||||
|
||||
#define PLUGIN_APP_ID "example_plugins"
|
||||
#define PLUGIN_API_VERSION 1
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="example_advanced_plugins_app",
|
||||
stack_size=2 * 1024,
|
||||
fap_category="Examples",
|
||||
sources=["*.c*", "!plugin*.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
|
||||
#define TAG "ExampleAdvancedPlugins"
|
||||
#define TAG "example_advanced_plugins"
|
||||
|
||||
int32_t example_advanced_plugins_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
/* Common interface between a plugin and host application */
|
||||
/* Common interface between a plugin and host applicaion */
|
||||
|
||||
#define PLUGIN_APP_ID "example_plugins_advanced"
|
||||
#define PLUGIN_API_VERSION 1
|
||||
|
||||
@@ -90,7 +90,7 @@ static void example_thermo_request_temperature(ExampleThermoContext* context) {
|
||||
bool success = false;
|
||||
do {
|
||||
/* Each communication with a 1-wire device starts by a reset.
|
||||
The function will return true if a device responded with a presence pulse. */
|
||||
The functon will return true if a device responded with a presence pulse. */
|
||||
if(!onewire_host_reset(onewire)) break;
|
||||
/* After the reset, a ROM operation must follow.
|
||||
If there is only one device connected, the "Skip ROM" command is most appropriate
|
||||
@@ -130,7 +130,7 @@ static void example_thermo_read_temperature(ExampleThermoContext* context) {
|
||||
size_t attempts_left = 10;
|
||||
do {
|
||||
/* Each communication with a 1-wire device starts by a reset.
|
||||
The function will return true if a device responded with a presence pulse. */
|
||||
The functon will return true if a device responded with a presence pulse. */
|
||||
if(!onewire_host_reset(onewire)) continue;
|
||||
|
||||
/* After the reset, a ROM operation must follow.
|
||||
@@ -221,7 +221,8 @@ static void example_thermo_draw_callback(Canvas* canvas, void* ctx) {
|
||||
canvas_draw_line(canvas, 0, 16, 128, 16);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, middle_x, 30, AlignCenter, AlignBottom, "Connect thermometer");
|
||||
canvas_draw_str_aligned(
|
||||
canvas, middle_x, 30, AlignCenter, AlignBottom, "Connnect thermometer");
|
||||
|
||||
snprintf(
|
||||
text_store,
|
||||
@@ -236,7 +237,7 @@ static void example_thermo_draw_callback(Canvas* canvas, void* ctx) {
|
||||
float temp;
|
||||
char temp_units;
|
||||
|
||||
/* The application is locale-aware.
|
||||
/* The applicaton is locale-aware.
|
||||
Change Settings->System->Units to check it out. */
|
||||
switch(locale_get_measurement_unit()) {
|
||||
case LocaleMeasurementUnitsMetric:
|
||||
@@ -354,7 +355,7 @@ int32_t example_thermo_main(void* p) {
|
||||
/* Allocate all of the necessary structures */
|
||||
ExampleThermoContext* context = example_thermo_context_alloc();
|
||||
|
||||
/* Start the application's main loop. It won't return until the application was requested to exit. */
|
||||
/* Start the applicaton's main loop. It won't return until the application was requested to exit. */
|
||||
example_thermo_run(context);
|
||||
|
||||
/* Release all unneeded resources */
|
||||
|
||||
@@ -95,17 +95,18 @@ void archive_free(ArchiveApp* archive) {
|
||||
}
|
||||
|
||||
void archive_show_loading_popup(ArchiveApp* context, bool show) {
|
||||
TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
|
||||
ViewStack* view_stack = context->view_stack;
|
||||
Loading* loading = context->loading;
|
||||
|
||||
if(show) {
|
||||
// Raise timer priority so that animations can play
|
||||
furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
|
||||
vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
|
||||
view_stack_add_view(view_stack, loading_get_view(loading));
|
||||
} else {
|
||||
view_stack_remove_view(view_stack, loading_get_view(loading));
|
||||
// Restore default timer priority
|
||||
furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
|
||||
vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
stack_size=2 * 1024,
|
||||
icon="A_BadUsb_14",
|
||||
order=70,
|
||||
resources="resources",
|
||||
fap_libs=["assets"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="USB",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "ducky_script_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define TAG "BadUsb"
|
||||
#define TAG "BadUSB"
|
||||
#define WORKER_TAG TAG "Worker"
|
||||
|
||||
#define BADUSB_ASCII_TO_KEY(script, x) \
|
||||
@@ -294,7 +294,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil
|
||||
return delay_val;
|
||||
} else if(delay_val < 0) { // Script error
|
||||
bad_usb->st.error_line = bad_usb->st.line_cur - 1;
|
||||
FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_usb->st.line_cur - 1U);
|
||||
FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur - 1U);
|
||||
return SCRIPT_STATE_ERROR;
|
||||
} else {
|
||||
return (delay_val + bad_usb->defdelay);
|
||||
@@ -333,7 +333,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil
|
||||
return delay_val;
|
||||
} else if(delay_val < 0) {
|
||||
bad_usb->st.error_line = bad_usb->st.line_cur;
|
||||
FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_usb->st.line_cur);
|
||||
FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur);
|
||||
return SCRIPT_STATE_ERROR;
|
||||
} else {
|
||||
return (delay_val + bad_usb->defdelay);
|
||||
|
||||
@@ -24,10 +24,10 @@ typedef enum {
|
||||
|
||||
typedef struct {
|
||||
BadUsbWorkerState state;
|
||||
size_t line_cur;
|
||||
size_t line_nb;
|
||||
uint16_t line_cur;
|
||||
uint16_t line_nb;
|
||||
uint32_t delay_remain;
|
||||
size_t error_line;
|
||||
uint16_t error_line;
|
||||
char error[64];
|
||||
} BadUsbState;
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ static int32_t ducky_fnc_string(BadUsbScript* bad_usb, const char* line, int32_t
|
||||
furi_string_cat(bad_usb->string_print, "\n");
|
||||
}
|
||||
|
||||
if(bad_usb->stringdelay == 0) { // stringdelay not set - run command immediately
|
||||
if(bad_usb->stringdelay == 0) { // stringdelay not set - run command immidiately
|
||||
bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print));
|
||||
if(!state) {
|
||||
return ducky_error(bad_usb, "Invalid string %s", line);
|
||||
@@ -171,7 +171,7 @@ static const DuckyCmd ducky_commands[] = {
|
||||
{"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1},
|
||||
};
|
||||
|
||||
#define TAG "BadUsb"
|
||||
#define TAG "BadUSB"
|
||||
#define WORKER_TAG TAG "Worker"
|
||||
|
||||
int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line) {
|
||||
|
||||
@@ -82,7 +82,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
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);
|
||||
furi_string_printf(disp_str, "line %u", model->state.error_line);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
|
||||
furi_string_reset(disp_str);
|
||||
@@ -105,7 +105,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
}
|
||||
canvas_set_font(canvas, FontBigNumbers);
|
||||
furi_string_printf(
|
||||
disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
|
||||
disp_str, "%u", ((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);
|
||||
@@ -124,7 +124,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
}
|
||||
canvas_set_font(canvas, FontBigNumbers);
|
||||
furi_string_printf(
|
||||
disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
|
||||
disp_str, "%u", ((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);
|
||||
@@ -142,7 +142,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
}
|
||||
canvas_set_font(canvas, FontBigNumbers);
|
||||
furi_string_printf(
|
||||
disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
|
||||
disp_str, "%u", ((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);
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
icon="A_Clock_14",
|
||||
stack_size=2 * 1024,
|
||||
order=81,
|
||||
resources="resources",
|
||||
fap_icon="icon.png",
|
||||
fap_category="Tools",
|
||||
)
|
||||
|
||||
@@ -43,11 +43,6 @@ GpioApp* gpio_app_alloc() {
|
||||
|
||||
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// Dialog view
|
||||
app->dialog = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, GpioAppViewExitConfirm, dialog_ex_get_view(app->dialog));
|
||||
|
||||
app->var_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
@@ -84,12 +79,10 @@ void gpio_app_free(GpioApp* app) {
|
||||
view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUart);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUartCfg);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUartCloseRpc);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewExitConfirm);
|
||||
variable_item_list_free(app->var_item_list);
|
||||
widget_free(app->widget);
|
||||
gpio_test_free(app->gpio_test);
|
||||
gpio_usb_uart_free(app->gpio_usb_uart);
|
||||
dialog_ex_free(app->dialog);
|
||||
|
||||
// View dispatcher
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include "views/gpio_test.h"
|
||||
#include "views/gpio_usb_uart.h"
|
||||
#include <assets_icons.h>
|
||||
@@ -24,7 +23,6 @@ struct GpioApp {
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
Widget* widget;
|
||||
DialogEx* dialog;
|
||||
|
||||
VariableItemList* var_item_list;
|
||||
VariableItem* var_item_flow;
|
||||
@@ -41,5 +39,4 @@ typedef enum {
|
||||
GpioAppViewUsbUart,
|
||||
GpioAppViewUsbUartCfg,
|
||||
GpioAppViewUsbUartCloseRpc,
|
||||
GpioAppViewExitConfirm,
|
||||
} GpioAppView;
|
||||
|
||||
@@ -18,12 +18,10 @@ GPIOItems* gpio_items_alloc() {
|
||||
}
|
||||
|
||||
items->pins = malloc(sizeof(GpioPinRecord) * items->count);
|
||||
size_t index = 0;
|
||||
for(size_t i = 0; i < gpio_pins_count; i++) {
|
||||
for(size_t i = 0; i < items->count; i++) {
|
||||
if(!gpio_pins[i].debug) {
|
||||
items->pins[index].pin = gpio_pins[i].pin;
|
||||
items->pins[index].name = gpio_pins[i].name;
|
||||
index++;
|
||||
items->pins[i].pin = gpio_pins[i].pin;
|
||||
items->pins[i].name = gpio_pins[i].name;
|
||||
}
|
||||
}
|
||||
return items;
|
||||
|
||||
@@ -3,4 +3,3 @@ ADD_SCENE(gpio, test, Test)
|
||||
ADD_SCENE(gpio, usb_uart, UsbUart)
|
||||
ADD_SCENE(gpio, usb_uart_cfg, UsbUartCfg)
|
||||
ADD_SCENE(gpio, usb_uart_close_rpc, UsbUartCloseRpc)
|
||||
ADD_SCENE(gpio, exit_confirm, ExitConfirm)
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
#include "gpio_app_i.h"
|
||||
|
||||
void gpio_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||
GpioApp* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void gpio_scene_exit_confirm_on_enter(void* context) {
|
||||
GpioApp* app = context;
|
||||
DialogEx* dialog = app->dialog;
|
||||
|
||||
dialog_ex_set_context(dialog, app);
|
||||
dialog_ex_set_left_button_text(dialog, "Exit");
|
||||
dialog_ex_set_right_button_text(dialog, "Stay");
|
||||
dialog_ex_set_header(dialog, "Exit USB-UART?", 22, 12, AlignLeft, AlignTop);
|
||||
dialog_ex_set_result_callback(dialog, gpio_scene_exit_confirm_dialog_callback);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewExitConfirm);
|
||||
}
|
||||
|
||||
bool gpio_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
GpioApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == DialogExResultRight) {
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
} else if(event.event == DialogExResultLeft) {
|
||||
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, GpioSceneStart);
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void gpio_scene_exit_confirm_on_exit(void* context) {
|
||||
GpioApp* app = context;
|
||||
|
||||
// Clean view
|
||||
dialog_ex_reset(app->dialog);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ void gpio_scene_usb_uart_on_enter(void* context) {
|
||||
uint32_t prev_state = scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUart);
|
||||
if(prev_state == 0) {
|
||||
scene_usb_uart = malloc(sizeof(SceneUsbUartBridge));
|
||||
scene_usb_uart->cfg.vcp_ch = 0;
|
||||
scene_usb_uart->cfg.vcp_ch = 0; // TODO: settings load
|
||||
scene_usb_uart->cfg.uart_ch = 0;
|
||||
scene_usb_uart->cfg.flow_pins = 0;
|
||||
scene_usb_uart->cfg.baudrate_mode = 0;
|
||||
@@ -42,9 +42,6 @@ bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_set_scene_state(app->scene_manager, GpioSceneUsbUart, 1);
|
||||
scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCfg);
|
||||
return true;
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_next_scene(app->scene_manager, GpioSceneExitConfirm);
|
||||
return true;
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
uint32_t tx_cnt_last = scene_usb_uart->state.tx_cnt;
|
||||
uint32_t rx_cnt_last = scene_usb_uart->state.rx_cnt;
|
||||
|
||||
@@ -17,6 +17,5 @@ App(
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
targets=["f7"],
|
||||
entry_point="ibutton_on_system_start",
|
||||
sources=["ibutton_cli.c"],
|
||||
order=60,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <toolbox/path.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define TAG "IButtonApp"
|
||||
#define TAG "iButtonApp"
|
||||
|
||||
static const NotificationSequence sequence_blink_set_yellow = {
|
||||
&message_blink_set_color_yellow,
|
||||
@@ -39,23 +39,21 @@ static void ibutton_make_app_folder(iButton* ibutton) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void ibutton_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
|
||||
static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
iButton* ibutton = context;
|
||||
|
||||
if(event->type == RpcAppEventTypeSessionClose) {
|
||||
if(event == RpcAppEventSessionClose) {
|
||||
view_dispatcher_send_custom_event(
|
||||
ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose);
|
||||
rpc_system_app_set_callback(ibutton->rpc, NULL, NULL);
|
||||
ibutton->rpc = NULL;
|
||||
} else if(event->type == RpcAppEventTypeAppExit) {
|
||||
} else if(event == RpcAppEventAppExit) {
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
|
||||
} else if(event->type == RpcAppEventTypeLoadFile) {
|
||||
furi_assert(event->data.type == RpcAppSystemEventDataTypeString);
|
||||
furi_string_set(ibutton->file_path, event->data.string);
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoadFile);
|
||||
} else if(event == RpcAppEventLoadFile) {
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoad);
|
||||
} else {
|
||||
rpc_system_app_confirm(ibutton->rpc, false);
|
||||
rpc_system_app_confirm(ibutton->rpc, event, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,23 +195,16 @@ bool ibutton_load_key(iButton* ibutton) {
|
||||
|
||||
bool ibutton_select_and_load_key(iButton* ibutton) {
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
bool success = false;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, IBUTTON_APP_FILENAME_EXTENSION, &I_ibutt_10px);
|
||||
dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px);
|
||||
browser_options.base_path = IBUTTON_APP_FOLDER;
|
||||
|
||||
if(furi_string_empty(ibutton->file_path)) {
|
||||
furi_string_set(ibutton->file_path, browser_options.base_path);
|
||||
}
|
||||
|
||||
do {
|
||||
if(!dialog_file_browser_show(
|
||||
ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options))
|
||||
break;
|
||||
success = ibutton_load_key(ibutton);
|
||||
} while(!success);
|
||||
|
||||
return success;
|
||||
return dialog_file_browser_show(
|
||||
ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options) &&
|
||||
ibutton_load_key(ibutton);
|
||||
}
|
||||
|
||||
bool ibutton_save_key(iButton* ibutton) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
enum iButtonCustomEvent {
|
||||
// Reserve first 100 events for button types and indexes, starting from 0
|
||||
iButtonCustomEventReserved = 100,
|
||||
|
||||
@@ -10,12 +10,8 @@ typedef enum {
|
||||
iButtonCustomEventByteEditResult,
|
||||
iButtonCustomEventWorkerEmulated,
|
||||
iButtonCustomEventWorkerRead,
|
||||
iButtonCustomEventWorkerWriteOK,
|
||||
iButtonCustomEventWorkerWriteSameKey,
|
||||
iButtonCustomEventWorkerWriteNoDetect,
|
||||
iButtonCustomEventWorkerWriteCannotWrite,
|
||||
|
||||
iButtonCustomEventRpcLoadFile,
|
||||
iButtonCustomEventRpcLoad,
|
||||
iButtonCustomEventRpcExit,
|
||||
iButtonCustomEventRpcSessionClose,
|
||||
} iButtonCustomEvent;
|
||||
};
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
#include "scenes/ibutton_scene.h"
|
||||
|
||||
#define IBUTTON_APP_FOLDER ANY_PATH("ibutton")
|
||||
#define IBUTTON_APP_FILENAME_PREFIX "iBtn"
|
||||
#define IBUTTON_APP_FILENAME_EXTENSION ".ibtn"
|
||||
#define IBUTTON_APP_EXTENSION ".ibtn"
|
||||
|
||||
#define IBUTTON_KEY_NAME_SIZE 22
|
||||
|
||||
|
||||
@@ -23,23 +23,28 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
|
||||
if(event.event == iButtonCustomEventRpcLoadFile) {
|
||||
if(event.event == iButtonCustomEventRpcLoad) {
|
||||
bool result = false;
|
||||
const char* file_path = rpc_system_app_get_data(ibutton->rpc);
|
||||
|
||||
if(ibutton_load_key(ibutton)) {
|
||||
popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
|
||||
if(file_path && (furi_string_empty(ibutton->file_path))) {
|
||||
furi_string_set(ibutton->file_path, file_path);
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
|
||||
ibutton_worker_emulate_start(ibutton->worker, ibutton->key);
|
||||
if(ibutton_load_key(ibutton)) {
|
||||
popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
|
||||
|
||||
result = true;
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
|
||||
ibutton_worker_emulate_start(ibutton->worker, ibutton->key);
|
||||
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
rpc_system_app_confirm(ibutton->rpc, result);
|
||||
rpc_system_app_confirm(ibutton->rpc, RpcAppEventLoadFile, result);
|
||||
|
||||
} else if(event.event == iButtonCustomEventRpcExit) {
|
||||
rpc_system_app_confirm(ibutton->rpc, true);
|
||||
rpc_system_app_confirm(ibutton->rpc, RpcAppEventAppExit, true);
|
||||
scene_manager_stop(ibutton->scene_manager);
|
||||
view_dispatcher_stop(ibutton->view_dispatcher);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "../ibutton_i.h"
|
||||
|
||||
#include <toolbox/name_generator.h>
|
||||
#include <toolbox/random_name.h>
|
||||
#include <toolbox/path.h>
|
||||
|
||||
#include <dolphin/dolphin.h>
|
||||
@@ -17,8 +17,7 @@ void ibutton_scene_save_name_on_enter(void* context) {
|
||||
const bool is_new_file = furi_string_empty(ibutton->file_path);
|
||||
|
||||
if(is_new_file) {
|
||||
name_generator_make_auto(
|
||||
ibutton->key_name, IBUTTON_KEY_NAME_SIZE, IBUTTON_APP_FILENAME_PREFIX);
|
||||
set_random_name(ibutton->key_name, IBUTTON_KEY_NAME_SIZE);
|
||||
}
|
||||
|
||||
text_input_set_header_text(text_input, "Name the key");
|
||||
@@ -30,8 +29,8 @@ void ibutton_scene_save_name_on_enter(void* context) {
|
||||
IBUTTON_KEY_NAME_SIZE,
|
||||
is_new_file);
|
||||
|
||||
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
|
||||
IBUTTON_APP_FOLDER, IBUTTON_APP_FILENAME_EXTENSION, ibutton->key_name);
|
||||
ValidatorIsFile* validator_is_file =
|
||||
validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, ibutton->key_name);
|
||||
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput);
|
||||
@@ -49,7 +48,7 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||
"%s/%s%s",
|
||||
IBUTTON_APP_FOLDER,
|
||||
ibutton->key_name,
|
||||
IBUTTON_APP_FILENAME_EXTENSION);
|
||||
IBUTTON_APP_EXTENSION);
|
||||
|
||||
if(ibutton_save_key(ibutton)) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess);
|
||||
|
||||
@@ -5,26 +5,9 @@ typedef enum {
|
||||
iButtonSceneWriteStateBlinkYellow,
|
||||
} iButtonSceneWriteState;
|
||||
|
||||
static inline iButtonCustomEvent
|
||||
ibutton_scene_write_to_custom_event(iButtonWorkerWriteResult result) {
|
||||
switch(result) {
|
||||
case iButtonWorkerWriteOK:
|
||||
return iButtonCustomEventWorkerWriteOK;
|
||||
case iButtonWorkerWriteSameKey:
|
||||
return iButtonCustomEventWorkerWriteSameKey;
|
||||
case iButtonWorkerWriteNoDetect:
|
||||
return iButtonCustomEventWorkerWriteNoDetect;
|
||||
case iButtonWorkerWriteCannotWrite:
|
||||
return iButtonCustomEventWorkerWriteCannotWrite;
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
static void ibutton_scene_write_callback(void* context, iButtonWorkerWriteResult result) {
|
||||
iButton* ibutton = context;
|
||||
view_dispatcher_send_custom_event(
|
||||
ibutton->view_dispatcher, ibutton_scene_write_to_custom_event(result));
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void ibutton_scene_write_on_enter(void* context) {
|
||||
@@ -78,14 +61,16 @@ bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if((event.event == iButtonCustomEventWorkerWriteOK) ||
|
||||
(event.event == iButtonCustomEventWorkerWriteSameKey)) {
|
||||
if((event.event == iButtonWorkerWriteOK) || (event.event == iButtonWorkerWriteSameKey)) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneWriteSuccess);
|
||||
} else if(event.event == iButtonCustomEventWorkerWriteNoDetect) {
|
||||
} else if(event.event == iButtonWorkerWriteNoDetect) {
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateBlink);
|
||||
} else if(event.event == iButtonCustomEventWorkerWriteCannotWrite) {
|
||||
} else if(event.event == iButtonWorkerWriteCannotWrite) {
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageYellowBlink);
|
||||
}
|
||||
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
|
||||
@@ -7,8 +7,6 @@ App(
|
||||
icon="A_Infrared_14",
|
||||
stack_size=3 * 1024,
|
||||
order=40,
|
||||
sources=["*.c", "!infrared_cli.c"],
|
||||
resources="resources",
|
||||
fap_libs=["assets"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="Infrared",
|
||||
@@ -19,10 +17,5 @@ App(
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
targets=["f7"],
|
||||
entry_point="infrared_on_system_start",
|
||||
sources=[
|
||||
"infrared_cli.c",
|
||||
"infrared_brute_force.c",
|
||||
"infrared_signal.c",
|
||||
],
|
||||
order=20,
|
||||
)
|
||||
|
||||
@@ -1,85 +1,69 @@
|
||||
#include "infrared_app_i.h"
|
||||
#include "infrared_i.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define TAG "InfraredApp"
|
||||
|
||||
#define INFRARED_TX_MIN_INTERVAL_MS 50U
|
||||
|
||||
static const NotificationSequence*
|
||||
infrared_notification_sequences[InfraredNotificationMessageCount] = {
|
||||
&sequence_success,
|
||||
&sequence_set_only_green_255,
|
||||
&sequence_reset_green,
|
||||
&sequence_solid_yellow,
|
||||
&sequence_reset_rgb,
|
||||
&sequence_blink_start_cyan,
|
||||
&sequence_blink_start_magenta,
|
||||
&sequence_blink_stop,
|
||||
static const NotificationSequence* infrared_notification_sequences[] = {
|
||||
&sequence_success,
|
||||
&sequence_set_only_green_255,
|
||||
&sequence_reset_green,
|
||||
&sequence_solid_yellow,
|
||||
&sequence_reset_rgb,
|
||||
&sequence_blink_start_cyan,
|
||||
&sequence_blink_start_magenta,
|
||||
&sequence_blink_stop,
|
||||
};
|
||||
|
||||
static void infrared_make_app_folder(InfraredApp* infrared) {
|
||||
static void infrared_make_app_folder(Infrared* infrared) {
|
||||
if(!storage_simply_mkdir(infrared->storage, INFRARED_APP_FOLDER)) {
|
||||
infrared_show_error_message(infrared, "Cannot create\napp folder");
|
||||
dialog_message_show_storage_error(infrared->dialogs, "Cannot create\napp folder");
|
||||
}
|
||||
}
|
||||
|
||||
static bool infrared_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
return scene_manager_handle_custom_event(infrared->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool infrared_back_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
return scene_manager_handle_back_event(infrared->scene_manager);
|
||||
}
|
||||
|
||||
static void infrared_tick_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
scene_manager_handle_tick_event(infrared->scene_manager);
|
||||
}
|
||||
|
||||
static void infrared_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
|
||||
static void infrared_rpc_command_callback(RpcAppSystemEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
furi_assert(infrared->rpc_ctx);
|
||||
|
||||
if(event->type == RpcAppEventTypeSessionClose) {
|
||||
if(event == RpcAppEventSessionClose) {
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcSessionClose);
|
||||
rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL);
|
||||
infrared->rpc_ctx = NULL;
|
||||
} else if(event->type == RpcAppEventTypeAppExit) {
|
||||
} else if(event == RpcAppEventAppExit) {
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcExit);
|
||||
} else if(event->type == RpcAppEventTypeLoadFile) {
|
||||
furi_assert(event->data.type == RpcAppSystemEventDataTypeString);
|
||||
furi_string_set(infrared->file_path, event->data.string);
|
||||
} else if(event == RpcAppEventLoadFile) {
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcLoadFile);
|
||||
} else if(event->type == RpcAppEventTypeButtonPress) {
|
||||
furi_assert(
|
||||
event->data.type == RpcAppSystemEventDataTypeString ||
|
||||
event->data.type == RpcAppSystemEventDataTypeInt32);
|
||||
if(event->data.type == RpcAppSystemEventDataTypeString) {
|
||||
furi_string_set(infrared->button_name, event->data.string);
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressName);
|
||||
} else {
|
||||
infrared->app_state.current_button_index = event->data.i32;
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressIndex);
|
||||
}
|
||||
} else if(event->type == RpcAppEventTypeButtonRelease) {
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcLoad);
|
||||
} else if(event == RpcAppEventButtonPress) {
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPress);
|
||||
} else if(event == RpcAppEventButtonRelease) {
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonRelease);
|
||||
} else {
|
||||
rpc_system_app_confirm(infrared->rpc_ctx, false);
|
||||
rpc_system_app_confirm(infrared->rpc_ctx, event, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,11 +109,10 @@ static void infrared_find_vacant_remote_name(FuriString* name, const char* path)
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static InfraredApp* infrared_alloc() {
|
||||
InfraredApp* infrared = malloc(sizeof(InfraredApp));
|
||||
static Infrared* infrared_alloc() {
|
||||
Infrared* infrared = malloc(sizeof(Infrared));
|
||||
|
||||
infrared->file_path = furi_string_alloc();
|
||||
infrared->button_name = furi_string_alloc();
|
||||
|
||||
InfraredAppState* app_state = &infrared->app_state;
|
||||
app_state->is_learning_new_remote = false;
|
||||
@@ -156,7 +139,7 @@ static InfraredApp* infrared_alloc() {
|
||||
|
||||
infrared->worker = infrared_worker_alloc();
|
||||
infrared->remote = infrared_remote_alloc();
|
||||
infrared->current_signal = infrared_signal_alloc();
|
||||
infrared->received_signal = infrared_signal_alloc();
|
||||
infrared->brute_force = infrared_brute_force_alloc();
|
||||
|
||||
infrared->submenu = submenu_alloc();
|
||||
@@ -167,16 +150,16 @@ static InfraredApp* infrared_alloc() {
|
||||
view_dispatcher_add_view(
|
||||
view_dispatcher, InfraredViewTextInput, text_input_get_view(infrared->text_input));
|
||||
|
||||
infrared->dialog_ex = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
view_dispatcher, InfraredViewDialogEx, dialog_ex_get_view(infrared->dialog_ex));
|
||||
|
||||
infrared->variable_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
infrared->view_dispatcher,
|
||||
InfraredViewVariableItemList,
|
||||
variable_item_list_get_view(infrared->variable_item_list));
|
||||
|
||||
infrared->dialog_ex = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
view_dispatcher, InfraredViewDialogEx, dialog_ex_get_view(infrared->dialog_ex));
|
||||
|
||||
infrared->button_menu = button_menu_alloc();
|
||||
view_dispatcher_add_view(
|
||||
view_dispatcher, InfraredViewButtonMenu, button_menu_get_view(infrared->button_menu));
|
||||
@@ -207,7 +190,7 @@ static InfraredApp* infrared_alloc() {
|
||||
return infrared;
|
||||
}
|
||||
|
||||
static void infrared_free(InfraredApp* infrared) {
|
||||
static void infrared_free(Infrared* infrared) {
|
||||
furi_assert(infrared);
|
||||
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
|
||||
InfraredAppState* app_state = &infrared->app_state;
|
||||
@@ -224,12 +207,12 @@ static void infrared_free(InfraredApp* infrared) {
|
||||
view_dispatcher_remove_view(view_dispatcher, InfraredViewTextInput);
|
||||
text_input_free(infrared->text_input);
|
||||
|
||||
view_dispatcher_remove_view(view_dispatcher, InfraredViewDialogEx);
|
||||
dialog_ex_free(infrared->dialog_ex);
|
||||
|
||||
view_dispatcher_remove_view(infrared->view_dispatcher, InfraredViewVariableItemList);
|
||||
variable_item_list_free(infrared->variable_item_list);
|
||||
|
||||
view_dispatcher_remove_view(view_dispatcher, InfraredViewDialogEx);
|
||||
dialog_ex_free(infrared->dialog_ex);
|
||||
|
||||
view_dispatcher_remove_view(view_dispatcher, InfraredViewButtonMenu);
|
||||
button_menu_free(infrared->button_menu);
|
||||
|
||||
@@ -255,7 +238,7 @@ static void infrared_free(InfraredApp* infrared) {
|
||||
scene_manager_free(infrared->scene_manager);
|
||||
|
||||
infrared_brute_force_free(infrared->brute_force);
|
||||
infrared_signal_free(infrared->current_signal);
|
||||
infrared_signal_free(infrared->received_signal);
|
||||
infrared_remote_free(infrared->remote);
|
||||
infrared_worker_free(infrared->worker);
|
||||
|
||||
@@ -269,67 +252,75 @@ static void infrared_free(InfraredApp* infrared) {
|
||||
infrared->gui = NULL;
|
||||
|
||||
furi_string_free(infrared->file_path);
|
||||
furi_string_free(infrared->button_name);
|
||||
|
||||
// Disable 5v power if was enabled for external module
|
||||
if(furi_hal_power_is_otg_enabled()) {
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
|
||||
free(infrared);
|
||||
}
|
||||
|
||||
bool infrared_add_remote_with_button(
|
||||
const InfraredApp* infrared,
|
||||
Infrared* infrared,
|
||||
const char* button_name,
|
||||
const InfraredSignal* signal) {
|
||||
InfraredSignal* signal) {
|
||||
InfraredRemote* remote = infrared->remote;
|
||||
|
||||
FuriString* new_name = furi_string_alloc_set(INFRARED_DEFAULT_REMOTE_NAME);
|
||||
FuriString* new_path = furi_string_alloc_set(INFRARED_APP_FOLDER);
|
||||
FuriString *new_name, *new_path;
|
||||
new_name = furi_string_alloc_set(INFRARED_DEFAULT_REMOTE_NAME);
|
||||
new_path = furi_string_alloc_set(INFRARED_APP_FOLDER);
|
||||
|
||||
infrared_find_vacant_remote_name(new_name, furi_string_get_cstr(new_path));
|
||||
furi_string_cat_printf(
|
||||
new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION);
|
||||
|
||||
bool success = false;
|
||||
infrared_remote_reset(remote);
|
||||
infrared_remote_set_name(remote, furi_string_get_cstr(new_name));
|
||||
infrared_remote_set_path(remote, furi_string_get_cstr(new_path));
|
||||
|
||||
do {
|
||||
if(!infrared_remote_create(remote, furi_string_get_cstr(new_path))) break;
|
||||
if(!infrared_remote_append_signal(remote, signal, button_name)) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
furi_string_free(new_name);
|
||||
furi_string_free(new_path);
|
||||
return infrared_remote_add_button(remote, button_name, signal);
|
||||
}
|
||||
|
||||
bool infrared_rename_current_remote(Infrared* infrared, const char* name) {
|
||||
InfraredRemote* remote = infrared->remote;
|
||||
const char* remote_path = infrared_remote_get_path(remote);
|
||||
|
||||
if(!strcmp(infrared_remote_get_name(remote), name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
FuriString* new_name;
|
||||
new_name = furi_string_alloc_set(name);
|
||||
|
||||
infrared_find_vacant_remote_name(new_name, remote_path);
|
||||
|
||||
FuriString* new_path;
|
||||
new_path = furi_string_alloc_set(infrared_remote_get_path(remote));
|
||||
if(furi_string_end_with(new_path, INFRARED_APP_EXTENSION)) {
|
||||
size_t filename_start = furi_string_search_rchar(new_path, '/');
|
||||
furi_string_left(new_path, filename_start);
|
||||
}
|
||||
furi_string_cat_printf(
|
||||
new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
FS_Error status = storage_common_rename(
|
||||
storage, infrared_remote_get_path(remote), furi_string_get_cstr(new_path));
|
||||
infrared_remote_set_name(remote, furi_string_get_cstr(new_name));
|
||||
infrared_remote_set_path(remote, furi_string_get_cstr(new_path));
|
||||
|
||||
furi_string_free(new_name);
|
||||
furi_string_free(new_path);
|
||||
|
||||
return success;
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return (status == FSE_OK || status == FSE_EXIST);
|
||||
}
|
||||
|
||||
bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name) {
|
||||
InfraredRemote* remote = infrared->remote;
|
||||
const char* old_path = infrared_remote_get_path(remote);
|
||||
|
||||
if(!strcmp(infrared_remote_get_name(remote), new_name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
FuriString* new_name_fstr = furi_string_alloc_set(new_name);
|
||||
FuriString* new_path_fstr = furi_string_alloc_set(old_path);
|
||||
|
||||
infrared_find_vacant_remote_name(new_name_fstr, old_path);
|
||||
|
||||
if(furi_string_end_with(new_path_fstr, INFRARED_APP_EXTENSION)) {
|
||||
path_extract_dirname(old_path, new_path_fstr);
|
||||
}
|
||||
|
||||
path_append(new_path_fstr, furi_string_get_cstr(new_name_fstr));
|
||||
furi_string_cat(new_path_fstr, INFRARED_APP_EXTENSION);
|
||||
|
||||
const bool success = infrared_remote_rename(remote, furi_string_get_cstr(new_path_fstr));
|
||||
|
||||
furi_string_free(new_name_fstr);
|
||||
furi_string_free(new_path_fstr);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void infrared_tx_start(InfraredApp* infrared) {
|
||||
void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
|
||||
if(infrared->app_state.is_transmitting) {
|
||||
return;
|
||||
}
|
||||
@@ -340,12 +331,12 @@ void infrared_tx_start(InfraredApp* infrared) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(infrared_signal_is_raw(infrared->current_signal)) {
|
||||
const InfraredRawSignal* raw = infrared_signal_get_raw_signal(infrared->current_signal);
|
||||
if(infrared_signal_is_raw(signal)) {
|
||||
InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
|
||||
infrared_worker_set_raw_signal(
|
||||
infrared->worker, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle);
|
||||
} else {
|
||||
const InfraredMessage* message = infrared_signal_get_message(infrared->current_signal);
|
||||
InfraredMessage* message = infrared_signal_get_message(signal);
|
||||
infrared_worker_set_decoded_signal(infrared->worker, message);
|
||||
}
|
||||
|
||||
@@ -359,20 +350,20 @@ void infrared_tx_start(InfraredApp* infrared) {
|
||||
infrared->app_state.is_transmitting = true;
|
||||
}
|
||||
|
||||
void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index) {
|
||||
furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote));
|
||||
void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
|
||||
furi_assert(button_index < infrared_remote_get_button_count(infrared->remote));
|
||||
|
||||
if(infrared_remote_load_signal(infrared->remote, infrared->current_signal, button_index)) {
|
||||
infrared_tx_start(infrared);
|
||||
} else {
|
||||
infrared_show_error_message(
|
||||
infrared,
|
||||
"Failed to load\n\"%s\"",
|
||||
infrared_remote_get_signal_name(infrared->remote, button_index));
|
||||
}
|
||||
InfraredRemoteButton* button = infrared_remote_get_button(infrared->remote, button_index);
|
||||
InfraredSignal* signal = infrared_remote_button_get_signal(button);
|
||||
|
||||
infrared_tx_start_signal(infrared, signal);
|
||||
}
|
||||
|
||||
void infrared_tx_stop(InfraredApp* infrared) {
|
||||
void infrared_tx_start_received(Infrared* infrared) {
|
||||
infrared_tx_start_signal(infrared, infrared->received_signal);
|
||||
}
|
||||
|
||||
void infrared_tx_stop(Infrared* infrared) {
|
||||
if(!infrared->app_state.is_transmitting) {
|
||||
return;
|
||||
}
|
||||
@@ -386,65 +377,53 @@ void infrared_tx_stop(InfraredApp* infrared) {
|
||||
infrared->app_state.last_transmit_time = furi_get_tick();
|
||||
}
|
||||
|
||||
void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) {
|
||||
void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
va_start(args, text);
|
||||
|
||||
vsnprintf(infrared->text_store[bank], INFRARED_TEXT_STORE_SIZE, fmt, args);
|
||||
vsnprintf(infrared->text_store[bank], INFRARED_TEXT_STORE_SIZE, text, args);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void infrared_text_store_clear(InfraredApp* infrared, uint32_t bank) {
|
||||
void infrared_text_store_clear(Infrared* infrared, uint32_t bank) {
|
||||
memset(infrared->text_store[bank], 0, INFRARED_TEXT_STORE_SIZE + 1);
|
||||
}
|
||||
|
||||
void infrared_play_notification_message(
|
||||
const InfraredApp* infrared,
|
||||
InfraredNotificationMessage message) {
|
||||
furi_assert(message < InfraredNotificationMessageCount);
|
||||
void infrared_play_notification_message(Infrared* infrared, uint32_t message) {
|
||||
furi_assert(message < sizeof(infrared_notification_sequences) / sizeof(NotificationSequence*));
|
||||
notification_message(infrared->notifications, infrared_notification_sequences[message]);
|
||||
}
|
||||
|
||||
void infrared_show_loading_popup(const InfraredApp* infrared, bool show) {
|
||||
void infrared_show_loading_popup(Infrared* infrared, bool show) {
|
||||
TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
|
||||
ViewStack* view_stack = infrared->view_stack;
|
||||
Loading* loading = infrared->loading;
|
||||
|
||||
if(show) {
|
||||
// Raise timer priority so that animations can play
|
||||
furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
|
||||
vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
|
||||
view_stack_add_view(view_stack, loading_get_view(loading));
|
||||
} else {
|
||||
view_stack_remove_view(view_stack, loading_get_view(loading));
|
||||
// Restore default timer priority
|
||||
furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
|
||||
vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
|
||||
}
|
||||
}
|
||||
|
||||
void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
FuriString* message = furi_string_alloc_vprintf(fmt, args);
|
||||
dialog_message_show_storage_error(infrared->dialogs, furi_string_get_cstr(message));
|
||||
|
||||
furi_string_free(message);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
|
||||
furi_assert(context);
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
|
||||
if(infrared_worker_signal_is_decoded(received_signal)) {
|
||||
infrared_signal_set_message(
|
||||
infrared->current_signal, infrared_worker_get_decoded_signal(received_signal));
|
||||
infrared->received_signal, infrared_worker_get_decoded_signal(received_signal));
|
||||
} else {
|
||||
const uint32_t* timings;
|
||||
size_t timings_size;
|
||||
infrared_worker_get_raw_signal(received_signal, &timings, &timings_size);
|
||||
infrared_signal_set_raw_signal(
|
||||
infrared->current_signal,
|
||||
infrared->received_signal,
|
||||
timings,
|
||||
timings_size,
|
||||
INFRARED_COMMON_CARRIER_FREQUENCY,
|
||||
@@ -457,20 +436,20 @@ void infrared_signal_received_callback(void* context, InfraredWorkerSignal* rece
|
||||
|
||||
void infrared_text_input_callback(void* context) {
|
||||
furi_assert(context);
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeTextEditDone);
|
||||
}
|
||||
|
||||
void infrared_popup_closed_callback(void* context) {
|
||||
furi_assert(context);
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypePopupClosed);
|
||||
}
|
||||
|
||||
int32_t infrared_app(void* p) {
|
||||
InfraredApp* infrared = infrared_alloc();
|
||||
Infrared* infrared = infrared_alloc();
|
||||
|
||||
infrared_make_app_folder(infrared);
|
||||
|
||||
@@ -486,15 +465,13 @@ int32_t infrared_app(void* p) {
|
||||
rpc_system_app_send_started(infrared->rpc_ctx);
|
||||
is_rpc_mode = true;
|
||||
} else {
|
||||
const char* file_path = (const char*)p;
|
||||
is_remote_loaded = infrared_remote_load(infrared->remote, file_path);
|
||||
|
||||
furi_string_set(infrared->file_path, (const char*)p);
|
||||
is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path);
|
||||
if(!is_remote_loaded) {
|
||||
infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path);
|
||||
dialog_message_show_storage_error(
|
||||
infrared->dialogs, "Failed to load\nselected remote");
|
||||
return -1;
|
||||
}
|
||||
|
||||
furi_string_set(infrared->file_path, file_path);
|
||||
}
|
||||
}
|
||||
|
||||
3
applications/main/infrared/infrared.h
Normal file
3
applications/main/infrared/infrared.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct Infrared Infrared;
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* @file infrared_app.h
|
||||
* @brief Infrared application - start here.
|
||||
*
|
||||
* @see infrared_app_i.h for the main application data structure and functions.
|
||||
* @see infrared_signal.h for the infrared signal library - loading, storing and transmitting signals.
|
||||
* @see infrared_remote.hl for the infrared remote library - loading, storing and manipulating remotes.
|
||||
* @see infrared_brute_force.h for the infrared brute force - loading and transmitting multiple signals.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @brief InfraredApp opaque type declaration.
|
||||
*/
|
||||
typedef struct InfraredApp InfraredApp;
|
||||
@@ -1,292 +0,0 @@
|
||||
/**
|
||||
* @file infrared_app_i.h
|
||||
* @brief Main Infrared application types and functions.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_stack.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/button_menu.h>
|
||||
#include <gui/modules/button_panel.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <infrared_worker.h>
|
||||
|
||||
#include "infrared_app.h"
|
||||
#include "infrared_remote.h"
|
||||
#include "infrared_brute_force.h"
|
||||
#include "infrared_custom_event.h"
|
||||
|
||||
#include "scenes/infrared_scene.h"
|
||||
#include "views/infrared_progress_view.h"
|
||||
#include "views/infrared_debug_view.h"
|
||||
#include "views/infrared_move_view.h"
|
||||
|
||||
#include "rpc/rpc_app.h"
|
||||
|
||||
#define INFRARED_FILE_NAME_SIZE 100
|
||||
#define INFRARED_TEXT_STORE_NUM 2
|
||||
#define INFRARED_TEXT_STORE_SIZE 128
|
||||
|
||||
#define INFRARED_MAX_BUTTON_NAME_LENGTH 22
|
||||
#define INFRARED_MAX_REMOTE_NAME_LENGTH 22
|
||||
|
||||
#define INFRARED_APP_FOLDER ANY_PATH("infrared")
|
||||
#define INFRARED_APP_EXTENSION ".ir"
|
||||
|
||||
#define INFRARED_DEFAULT_REMOTE_NAME "Remote"
|
||||
#define INFRARED_LOG_TAG "InfraredApp"
|
||||
|
||||
/**
|
||||
* @brief Enumeration of invalid remote button indices.
|
||||
*/
|
||||
typedef enum {
|
||||
InfraredButtonIndexNone = -1, /**< No button is currently selected. */
|
||||
} InfraredButtonIndex;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of editing targets.
|
||||
*/
|
||||
typedef enum {
|
||||
InfraredEditTargetNone, /**< No editing target is selected. */
|
||||
InfraredEditTargetRemote, /**< Whole remote is selected as editing target. */
|
||||
InfraredEditTargetButton, /**< Single button is selected as editing target. */
|
||||
} InfraredEditTarget;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of editing modes.
|
||||
*/
|
||||
typedef enum {
|
||||
InfraredEditModeNone, /**< No editing mode is selected. */
|
||||
InfraredEditModeRename, /**< Rename mode is selected. */
|
||||
InfraredEditModeDelete, /**< Delete mode is selected. */
|
||||
} InfraredEditMode;
|
||||
|
||||
/**
|
||||
* @brief Infrared application state type.
|
||||
*/
|
||||
typedef struct {
|
||||
bool is_learning_new_remote; /**< Learning new remote or adding to an existing one. */
|
||||
bool is_debug_enabled; /**< Whether to enable or disable debugging features. */
|
||||
bool is_transmitting; /**< Whether a signal is currently being transmitted. */
|
||||
InfraredEditTarget edit_target : 8; /**< Selected editing target (a remote or a button). */
|
||||
InfraredEditMode edit_mode : 8; /**< Selected editing operation (rename or delete). */
|
||||
int32_t current_button_index; /**< Selected button index (move destination). */
|
||||
int32_t prev_button_index; /**< Previous button index (move source). */
|
||||
uint32_t last_transmit_time; /**< Lat time a signal was transmitted. */
|
||||
} InfraredAppState;
|
||||
|
||||
/**
|
||||
* @brief Infrared application type.
|
||||
*/
|
||||
struct InfraredApp {
|
||||
SceneManager* scene_manager; /**< Pointer to a SceneManager instance. */
|
||||
ViewDispatcher* view_dispatcher; /**< Pointer to a ViewDispatcher instance. */
|
||||
|
||||
Gui* gui; /**< Pointer to a Gui instance. */
|
||||
Storage* storage; /**< Pointer to a Storage instance. */
|
||||
DialogsApp* dialogs; /**< Pointer to a DialogsApp instance. */
|
||||
NotificationApp* notifications; /**< Pointer to a NotificationApp instance. */
|
||||
InfraredWorker* worker; /**< Used to send or receive signals. */
|
||||
InfraredRemote* remote; /**< Holds the currently loaded remote. */
|
||||
InfraredSignal* current_signal; /**< Holds the currently loaded signal. */
|
||||
InfraredBruteForce* brute_force; /**< Used for the Universal Remote feature. */
|
||||
|
||||
Submenu* submenu; /**< Standard view for displaying application menus. */
|
||||
TextInput* text_input; /**< Standard view for receiving user text input. */
|
||||
DialogEx* dialog_ex; /**< Standard view for displaying dialogs. */
|
||||
ButtonMenu* button_menu; /**< Custom view for interacting with IR remotes. */
|
||||
Popup* popup; /**< Standard view for displaying messages. */
|
||||
VariableItemList* variable_item_list;
|
||||
|
||||
ViewStack* view_stack; /**< Standard view for displaying stacked interfaces. */
|
||||
InfraredDebugView* debug_view; /**< Custom view for displaying debug information. */
|
||||
InfraredMoveView* move_view; /**< Custom view for rearranging buttons in a remote. */
|
||||
|
||||
ButtonPanel* button_panel; /**< Standard view for displaying control panels. */
|
||||
Loading* loading; /**< Standard view for informing about long operations. */
|
||||
InfraredProgressView* progress; /**< Custom view for showing brute force progress. */
|
||||
|
||||
FuriString* file_path; /**< Full path to the currently loaded file. */
|
||||
FuriString* button_name; /** Name of the button requested in RPC mode. */
|
||||
/** Arbitrary text storage for various inputs. */
|
||||
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
|
||||
InfraredAppState app_state; /**< Application state. */
|
||||
|
||||
void* rpc_ctx; /**< Pointer to the RPC context object. */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Enumeration of all used view types.
|
||||
*/
|
||||
typedef enum {
|
||||
InfraredViewSubmenu,
|
||||
InfraredViewTextInput,
|
||||
InfraredViewDialogEx,
|
||||
InfraredViewButtonMenu,
|
||||
InfraredViewPopup,
|
||||
InfraredViewStack,
|
||||
InfraredViewDebugView,
|
||||
InfraredViewMove,
|
||||
InfraredViewVariableItemList,
|
||||
} InfraredView;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of all notification message types.
|
||||
*/
|
||||
typedef enum {
|
||||
InfraredNotificationMessageSuccess, /**< Play a short happy tune. */
|
||||
InfraredNotificationMessageGreenOn, /**< Turn green LED on. */
|
||||
InfraredNotificationMessageGreenOff, /**< Turn green LED off. */
|
||||
InfraredNotificationMessageYellowOn, /**< Turn yellow LED on. */
|
||||
InfraredNotificationMessageYellowOff, /**< Turn yellow LED off. */
|
||||
InfraredNotificationMessageBlinkStartRead, /**< Blink the LED to indicate receiver mode. */
|
||||
InfraredNotificationMessageBlinkStartSend, /**< Blink the LED to indicate transmitter mode. */
|
||||
InfraredNotificationMessageBlinkStop, /**< Stop blinking the LED. */
|
||||
InfraredNotificationMessageCount, /**< Special value equal to the message type count. */
|
||||
} InfraredNotificationMessage;
|
||||
|
||||
/**
|
||||
* @brief Add a new remote with a single signal.
|
||||
*
|
||||
* The filename will be automatically generated depending on
|
||||
* the names and number of other files in the infrared data directory.
|
||||
*
|
||||
* @param[in] infrared pointer to the application instance.
|
||||
* @param[in] name pointer to a zero-terminated string containing the signal name.
|
||||
* @param[in] signal pointer to the signal to be added.
|
||||
* @return true if the remote was successfully created, false otherwise.
|
||||
*/
|
||||
bool infrared_add_remote_with_button(
|
||||
const InfraredApp* infrared,
|
||||
const char* name,
|
||||
const InfraredSignal* signal);
|
||||
|
||||
/**
|
||||
* @brief Rename the currently loaded remote.
|
||||
*
|
||||
* @param[in] infrared pointer to the application instance.
|
||||
* @param[in] new_name pointer to a zero-terminated string containing the new remote name.
|
||||
* @return true if the remote was successfully renamed, false otherwise.
|
||||
*/
|
||||
bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name);
|
||||
|
||||
/**
|
||||
* @brief Begin transmission of the currently loaded signal.
|
||||
*
|
||||
* The signal will be repeated indefinitely until stopped.
|
||||
*
|
||||
* @param[in,out] infrared pointer to the application instance.
|
||||
*/
|
||||
void infrared_tx_start(InfraredApp* infrared);
|
||||
|
||||
/**
|
||||
* @brief Load a signal under the given index and begin transmission.
|
||||
*
|
||||
* The signal will be repeated indefinitely until stopped.
|
||||
*
|
||||
* @param[in,out] infrared pointer to the application instance.
|
||||
* @param[in] button_index index of the signal to be loaded.
|
||||
* @returns true if the signal could be loaded, false otherwise.
|
||||
*/
|
||||
void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index);
|
||||
|
||||
/**
|
||||
* @brief Stop transmission of the currently loaded signal.
|
||||
*
|
||||
* @param[in,out] infrared pointer to the application instance.
|
||||
*/
|
||||
void infrared_tx_stop(InfraredApp* infrared);
|
||||
|
||||
/**
|
||||
* @brief Set the internal text store with formatted text.
|
||||
*
|
||||
* @param[in,out] infrared pointer to the application instance.
|
||||
* @param[in] bank index of text store bank (0 or 1).
|
||||
* @param[in] fmt pointer to a zero-terminated string containing the format text.
|
||||
* @param[in] ... additional arguments.
|
||||
*/
|
||||
void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...)
|
||||
_ATTRIBUTE((__format__(__printf__, 3, 4)));
|
||||
|
||||
/**
|
||||
* @brief Clear the internal text store.
|
||||
*
|
||||
* @param[in,out] infrared pointer to the application instance.
|
||||
* @param[in] bank index of text store bank (0 or 1).
|
||||
*/
|
||||
void infrared_text_store_clear(InfraredApp* infrared, uint32_t bank);
|
||||
|
||||
/**
|
||||
* @brief Play a sound and/or blink the LED.
|
||||
*
|
||||
* @param[in] infrared pointer to the application instance.
|
||||
* @param[in] message type of the message to play.
|
||||
*/
|
||||
void infrared_play_notification_message(
|
||||
const InfraredApp* infrared,
|
||||
InfraredNotificationMessage message);
|
||||
|
||||
/**
|
||||
* @brief Show a loading pop-up screen.
|
||||
*
|
||||
* In order for this to work, a Stack view must be currently active and
|
||||
* the main view must be added to it.
|
||||
*
|
||||
* @param[in] infrared pointer to the application instance.
|
||||
* @param[in] show whether to show or hide the pop-up.
|
||||
*/
|
||||
void infrared_show_loading_popup(const InfraredApp* infrared, bool show);
|
||||
|
||||
/**
|
||||
* @brief Show a formatted error messsage.
|
||||
*
|
||||
* @param[in] infrared pointer to the application instance.
|
||||
* @param[in] fmt pointer to a zero-terminated string containing the format text.
|
||||
* @param[in] ... additional arguments.
|
||||
*/
|
||||
void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...)
|
||||
_ATTRIBUTE((__format__(__printf__, 2, 3)));
|
||||
|
||||
/**
|
||||
* @brief Common received signal callback.
|
||||
*
|
||||
* Called when the worker has received a complete infrared signal.
|
||||
*
|
||||
* @param[in,out] context pointer to the user-specified context object.
|
||||
* @param[in] received_signal pointer to the received signal.
|
||||
*/
|
||||
void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal);
|
||||
|
||||
/**
|
||||
* @brief Common text input callback.
|
||||
*
|
||||
* Called when the input has been accepted by the user.
|
||||
*
|
||||
* @param[in,out] context pointer to the user-specified context object.
|
||||
*/
|
||||
void infrared_text_input_callback(void* context);
|
||||
|
||||
/**
|
||||
* @brief Common popup close callback.
|
||||
*
|
||||
* Called when the popup has been closed either by the user or after a timeout.
|
||||
*
|
||||
* @param[in,out] context pointer to the user-specified context object.
|
||||
*/
|
||||
void infrared_popup_closed_callback(void* context);
|
||||
@@ -111,7 +111,7 @@ bool infrared_brute_force_start(
|
||||
return success;
|
||||
}
|
||||
|
||||
bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force) {
|
||||
bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) {
|
||||
return brute_force->is_started;
|
||||
}
|
||||
|
||||
@@ -128,10 +128,8 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
|
||||
|
||||
bool infrared_brute_force_send_next(InfraredBruteForce* 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));
|
||||
const bool success = infrared_signal_search_and_read(
|
||||
brute_force->current_signal, brute_force->ff, brute_force->current_record_name);
|
||||
if(success) {
|
||||
infrared_signal_transmit(brute_force->current_signal);
|
||||
}
|
||||
|
||||
@@ -1,110 +1,23 @@
|
||||
/**
|
||||
* @file infrared_brute_force.h
|
||||
* @brief Infrared signal brute-forcing library.
|
||||
*
|
||||
* The BruteForce library is used to send large quantities of signals,
|
||||
* sorted by a category. It is used to implement the Universal Remote
|
||||
* feature.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief InfraredBruteForce opaque type declaration.
|
||||
*/
|
||||
typedef struct InfraredBruteForce InfraredBruteForce;
|
||||
|
||||
/**
|
||||
* @brief Create a new InfraredBruteForce instance.
|
||||
*
|
||||
* @returns pointer to the created instance.
|
||||
*/
|
||||
InfraredBruteForce* infrared_brute_force_alloc();
|
||||
|
||||
/**
|
||||
* @brief Delete an InfraredBruteForce instance.
|
||||
*
|
||||
* @param[in,out] brute_force pointer to the instance to be deleted.
|
||||
*/
|
||||
void infrared_brute_force_free(InfraredBruteForce* brute_force);
|
||||
|
||||
/**
|
||||
* @brief Set an InfraredBruteForce instance to use a signal database contained in a file.
|
||||
*
|
||||
* @param[in,out] brute_force pointer to the instance to be configured.
|
||||
* @param[in] db_filename pointer to a zero-terminated string containing a full path to the database file.
|
||||
*/
|
||||
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename);
|
||||
|
||||
/**
|
||||
* @brief Build a signal dictionary from a previously set database file.
|
||||
*
|
||||
* This function must be called each time after setting the database via
|
||||
* a infrared_brute_force_set_db_filename() call.
|
||||
*
|
||||
* @param[in,out] brute_force pointer to the instance to be updated.
|
||||
* @returns true on success, false otherwise.
|
||||
*/
|
||||
bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force);
|
||||
|
||||
/**
|
||||
* @brief Start transmitting signals from a category stored in an InfraredBruteForce's instance dictionary.
|
||||
*
|
||||
* @param[in,out] brute_force pointer to the instance to be started.
|
||||
* @param[in] index index of the signal category in the dictionary.
|
||||
* @returns true on success, false otherwise.
|
||||
*/
|
||||
bool infrared_brute_force_start(
|
||||
InfraredBruteForce* brute_force,
|
||||
uint32_t index,
|
||||
uint32_t* record_count);
|
||||
|
||||
/**
|
||||
* @brief Determine whether the transmission was started.
|
||||
*
|
||||
* @param[in] brute_force pointer to the instance to be tested.
|
||||
* @returns true if transmission was started, false otherwise.
|
||||
*/
|
||||
bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force);
|
||||
|
||||
/**
|
||||
* @brief Stop transmitting the signals.
|
||||
*
|
||||
* @param[in] brute_force pointer to the instance to be stopped.
|
||||
*/
|
||||
bool infrared_brute_force_is_started(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.
|
||||
*/
|
||||
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force);
|
||||
|
||||
/**
|
||||
* @brief Add a signal category to an InfraredBruteForce instance's dictionary.
|
||||
*
|
||||
* @param[in,out] brute_force pointer to the instance to be updated.
|
||||
* @param[in] index index of the category to be added.
|
||||
* @param[in] name name of the category to be added.
|
||||
*/
|
||||
void infrared_brute_force_reset(InfraredBruteForce* brute_force);
|
||||
void infrared_brute_force_add_record(
|
||||
InfraredBruteForce* brute_force,
|
||||
uint32_t index,
|
||||
const char* name);
|
||||
|
||||
/**
|
||||
* @brief Reset an InfraredBruteForce instance.
|
||||
*
|
||||
* @param[in,out] brute_force pointer to the instance to be reset.
|
||||
*/
|
||||
void infrared_brute_force_reset(InfraredBruteForce* brute_force);
|
||||
|
||||
@@ -85,7 +85,7 @@ static void infrared_cli_print_usage(void) {
|
||||
printf("\tir decode <input_file> [<output_file>]\r\n");
|
||||
printf("\tir universal <remote_name> <signal_name>\r\n");
|
||||
printf("\tir universal list <remote_name>\r\n");
|
||||
// TODO FL-3496: Do not hardcode universal remote names
|
||||
// TODO: Do not hardcode universal remote names
|
||||
printf("\tAvailable universal remotes: tv audio ac projector\r\n");
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ static bool
|
||||
}
|
||||
|
||||
static bool infrared_cli_decode_raw_signal(
|
||||
const InfraredRawSignal* raw_signal,
|
||||
InfraredRawSignal* raw_signal,
|
||||
InfraredDecoderHandler* decoder,
|
||||
FlipperFormat* output_file,
|
||||
const char* signal_name) {
|
||||
@@ -211,7 +211,7 @@ static bool infrared_cli_decode_raw_signal(
|
||||
|
||||
size_t i;
|
||||
for(i = 0; i < raw_signal->timings_size; ++i) {
|
||||
// TODO FL-3523: Any infrared_check_decoder_ready() magic?
|
||||
// TODO: Any infrared_check_decoder_ready() magic?
|
||||
const InfraredMessage* message = infrared_decode(decoder, level, raw_signal->timings[i]);
|
||||
|
||||
if(message) {
|
||||
@@ -274,7 +274,7 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal);
|
||||
InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal);
|
||||
printf(
|
||||
"Raw signal: %s, %zu samples\r\n",
|
||||
furi_string_get_cstr(tmp),
|
||||
|
||||
@@ -15,10 +15,9 @@ enum InfraredCustomEventType {
|
||||
InfraredCustomEventTypeButtonSelected,
|
||||
InfraredCustomEventTypeBackPressed,
|
||||
|
||||
InfraredCustomEventTypeRpcLoadFile,
|
||||
InfraredCustomEventTypeRpcLoad,
|
||||
InfraredCustomEventTypeRpcExit,
|
||||
InfraredCustomEventTypeRpcButtonPressName,
|
||||
InfraredCustomEventTypeRpcButtonPressIndex,
|
||||
InfraredCustomEventTypeRpcButtonPress,
|
||||
InfraredCustomEventTypeRpcButtonRelease,
|
||||
InfraredCustomEventTypeRpcSessionClose,
|
||||
};
|
||||
|
||||
149
applications/main/infrared/infrared_i.h
Normal file
149
applications/main/infrared/infrared_i.h
Normal file
@@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_stack.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/button_menu.h>
|
||||
#include <gui/modules/button_panel.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <infrared_worker.h>
|
||||
|
||||
#include "infrared.h"
|
||||
#include "infrared_remote.h"
|
||||
#include "infrared_brute_force.h"
|
||||
#include "infrared_custom_event.h"
|
||||
|
||||
#include "scenes/infrared_scene.h"
|
||||
#include "views/infrared_progress_view.h"
|
||||
#include "views/infrared_debug_view.h"
|
||||
#include "views/infrared_move_view.h"
|
||||
|
||||
#include "rpc/rpc_app.h"
|
||||
|
||||
#define INFRARED_FILE_NAME_SIZE 100
|
||||
#define INFRARED_TEXT_STORE_NUM 2
|
||||
#define INFRARED_TEXT_STORE_SIZE 128
|
||||
|
||||
#define INFRARED_MAX_BUTTON_NAME_LENGTH 22
|
||||
#define INFRARED_MAX_REMOTE_NAME_LENGTH 22
|
||||
|
||||
#define INFRARED_APP_FOLDER ANY_PATH("infrared")
|
||||
#define INFRARED_APP_EXTENSION ".ir"
|
||||
|
||||
#define INFRARED_DEFAULT_REMOTE_NAME "Remote"
|
||||
#define INFRARED_LOG_TAG "InfraredApp"
|
||||
|
||||
typedef enum {
|
||||
InfraredButtonIndexNone = -1,
|
||||
} InfraredButtonIndex;
|
||||
|
||||
typedef enum {
|
||||
InfraredEditTargetNone,
|
||||
InfraredEditTargetRemote,
|
||||
InfraredEditTargetButton,
|
||||
} InfraredEditTarget;
|
||||
|
||||
typedef enum {
|
||||
InfraredEditModeNone,
|
||||
InfraredEditModeRename,
|
||||
InfraredEditModeDelete,
|
||||
} InfraredEditMode;
|
||||
|
||||
typedef struct {
|
||||
bool is_learning_new_remote;
|
||||
bool is_debug_enabled;
|
||||
bool is_transmitting;
|
||||
InfraredEditTarget edit_target : 8;
|
||||
InfraredEditMode edit_mode : 8;
|
||||
int32_t current_button_index;
|
||||
int32_t current_button_index_move_orig;
|
||||
uint32_t last_transmit_time;
|
||||
} InfraredAppState;
|
||||
|
||||
struct Infrared {
|
||||
SceneManager* scene_manager;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
|
||||
Gui* gui;
|
||||
Storage* storage;
|
||||
DialogsApp* dialogs;
|
||||
NotificationApp* notifications;
|
||||
InfraredWorker* worker;
|
||||
InfraredRemote* remote;
|
||||
InfraredSignal* received_signal;
|
||||
InfraredBruteForce* brute_force;
|
||||
|
||||
Submenu* submenu;
|
||||
TextInput* text_input;
|
||||
VariableItemList* variable_item_list;
|
||||
DialogEx* dialog_ex;
|
||||
ButtonMenu* button_menu;
|
||||
Popup* popup;
|
||||
|
||||
ViewStack* view_stack;
|
||||
InfraredDebugView* debug_view;
|
||||
InfraredMoveView* move_view;
|
||||
|
||||
ButtonPanel* button_panel;
|
||||
Loading* loading;
|
||||
InfraredProgressView* progress;
|
||||
|
||||
FuriString* file_path;
|
||||
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
|
||||
InfraredAppState app_state;
|
||||
|
||||
void* rpc_ctx;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
InfraredViewSubmenu,
|
||||
InfraredViewTextInput,
|
||||
InfraredViewVariableItemList,
|
||||
InfraredViewDialogEx,
|
||||
InfraredViewButtonMenu,
|
||||
InfraredViewPopup,
|
||||
InfraredViewStack,
|
||||
InfraredViewDebugView,
|
||||
InfraredViewMove,
|
||||
} InfraredView;
|
||||
|
||||
typedef enum {
|
||||
InfraredNotificationMessageSuccess,
|
||||
InfraredNotificationMessageGreenOn,
|
||||
InfraredNotificationMessageGreenOff,
|
||||
InfraredNotificationMessageYellowOn,
|
||||
InfraredNotificationMessageYellowOff,
|
||||
InfraredNotificationMessageBlinkStartRead,
|
||||
InfraredNotificationMessageBlinkStartSend,
|
||||
InfraredNotificationMessageBlinkStop,
|
||||
} InfraredNotificationMessage;
|
||||
|
||||
bool infrared_add_remote_with_button(Infrared* infrared, const char* name, InfraredSignal* signal);
|
||||
bool infrared_rename_current_remote(Infrared* infrared, const char* name);
|
||||
void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal);
|
||||
void infrared_tx_start_button_index(Infrared* infrared, size_t button_index);
|
||||
void infrared_tx_start_received(Infrared* infrared);
|
||||
void infrared_tx_stop(Infrared* infrared);
|
||||
void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...);
|
||||
void infrared_text_store_clear(Infrared* infrared, uint32_t bank);
|
||||
void infrared_play_notification_message(Infrared* infrared, uint32_t message);
|
||||
void infrared_show_loading_popup(Infrared* infrared, bool show);
|
||||
|
||||
void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal);
|
||||
void infrared_text_input_callback(void* context);
|
||||
void infrared_popup_closed_callback(void* context);
|
||||
@@ -1,427 +1,197 @@
|
||||
#include "infrared_remote.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <m-array.h>
|
||||
|
||||
#include <toolbox/m_cstr_dup.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <storage/storage.h>
|
||||
#include <core/common_defines.h>
|
||||
|
||||
#define TAG "InfraredRemote"
|
||||
|
||||
#define INFRARED_FILE_HEADER "IR signals file"
|
||||
#define INFRARED_FILE_VERSION (1)
|
||||
|
||||
ARRAY_DEF(StringArray, const char*, M_CSTR_DUP_OPLIST); //-V575
|
||||
ARRAY_DEF(InfraredButtonArray, InfraredRemoteButton*, M_PTR_OPLIST);
|
||||
|
||||
struct InfraredRemote {
|
||||
StringArray_t signal_names;
|
||||
InfraredButtonArray_t buttons;
|
||||
FuriString* name;
|
||||
FuriString* path;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
InfraredRemote* remote;
|
||||
FlipperFormat* ff_in;
|
||||
FlipperFormat* ff_out;
|
||||
FuriString* signal_name;
|
||||
InfraredSignal* signal;
|
||||
size_t signal_index;
|
||||
} InfraredBatch;
|
||||
|
||||
typedef struct {
|
||||
size_t signal_index;
|
||||
const char* signal_name;
|
||||
const InfraredSignal* signal;
|
||||
} InfraredBatchTarget;
|
||||
|
||||
typedef bool (
|
||||
*InfraredBatchCallback)(const InfraredBatch* batch, const InfraredBatchTarget* target);
|
||||
static void infrared_remote_clear_buttons(InfraredRemote* remote) {
|
||||
InfraredButtonArray_it_t it;
|
||||
for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it);
|
||||
InfraredButtonArray_next(it)) {
|
||||
infrared_remote_button_free(*InfraredButtonArray_cref(it));
|
||||
}
|
||||
InfraredButtonArray_reset(remote->buttons);
|
||||
}
|
||||
|
||||
InfraredRemote* infrared_remote_alloc() {
|
||||
InfraredRemote* remote = malloc(sizeof(InfraredRemote));
|
||||
StringArray_init(remote->signal_names);
|
||||
InfraredButtonArray_init(remote->buttons);
|
||||
remote->name = furi_string_alloc();
|
||||
remote->path = furi_string_alloc();
|
||||
return remote;
|
||||
}
|
||||
|
||||
void infrared_remote_free(InfraredRemote* remote) {
|
||||
StringArray_clear(remote->signal_names);
|
||||
infrared_remote_clear_buttons(remote);
|
||||
InfraredButtonArray_clear(remote->buttons);
|
||||
furi_string_free(remote->path);
|
||||
furi_string_free(remote->name);
|
||||
free(remote);
|
||||
}
|
||||
|
||||
void infrared_remote_reset(InfraredRemote* remote) {
|
||||
StringArray_reset(remote->signal_names);
|
||||
infrared_remote_clear_buttons(remote);
|
||||
furi_string_reset(remote->name);
|
||||
furi_string_reset(remote->path);
|
||||
}
|
||||
|
||||
const char* infrared_remote_get_name(const InfraredRemote* remote) {
|
||||
void infrared_remote_set_name(InfraredRemote* remote, const char* name) {
|
||||
furi_string_set(remote->name, name);
|
||||
}
|
||||
|
||||
const char* infrared_remote_get_name(InfraredRemote* remote) {
|
||||
return furi_string_get_cstr(remote->name);
|
||||
}
|
||||
|
||||
static void infrared_remote_set_path(InfraredRemote* remote, const char* path) {
|
||||
void infrared_remote_set_path(InfraredRemote* remote, const char* path) {
|
||||
furi_string_set(remote->path, path);
|
||||
path_extract_filename(remote->path, remote->name, true);
|
||||
}
|
||||
|
||||
const char* infrared_remote_get_path(const InfraredRemote* remote) {
|
||||
const char* infrared_remote_get_path(InfraredRemote* remote) {
|
||||
return furi_string_get_cstr(remote->path);
|
||||
}
|
||||
|
||||
size_t infrared_remote_get_signal_count(const InfraredRemote* remote) {
|
||||
return StringArray_size(remote->signal_names);
|
||||
size_t infrared_remote_get_button_count(InfraredRemote* remote) {
|
||||
return InfraredButtonArray_size(remote->buttons);
|
||||
}
|
||||
|
||||
const char* infrared_remote_get_signal_name(const InfraredRemote* remote, size_t index) {
|
||||
furi_assert(index < infrared_remote_get_signal_count(remote));
|
||||
return *StringArray_cget(remote->signal_names, index);
|
||||
InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index) {
|
||||
furi_assert(index < InfraredButtonArray_size(remote->buttons));
|
||||
return *InfraredButtonArray_get(remote->buttons, index);
|
||||
}
|
||||
|
||||
bool infrared_remote_load_signal(
|
||||
const InfraredRemote* remote,
|
||||
InfraredSignal* signal,
|
||||
size_t index) {
|
||||
furi_assert(index < infrared_remote_get_signal_count(remote));
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
const char* path = furi_string_get_cstr(remote->path);
|
||||
if(!flipper_format_buffered_file_open_existing(ff, path)) break;
|
||||
|
||||
if(!infrared_signal_search_by_index_and_read(signal, ff, index)) {
|
||||
const char* signal_name = infrared_remote_get_signal_name(remote, index);
|
||||
FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", signal_name, path);
|
||||
break;
|
||||
}
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(ff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool infrared_remote_get_signal_index(
|
||||
const InfraredRemote* remote,
|
||||
const char* name,
|
||||
size_t* index) {
|
||||
uint32_t i = 0;
|
||||
StringArray_it_t it;
|
||||
|
||||
for(StringArray_it(it, remote->signal_names); !StringArray_end_p(it);
|
||||
StringArray_next(it), ++i) {
|
||||
if(strcmp(*StringArray_cref(it), name) == 0) {
|
||||
bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index) {
|
||||
for(size_t i = 0; i < InfraredButtonArray_size(remote->buttons); i++) {
|
||||
InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, i);
|
||||
if(!strcmp(infrared_remote_button_get_name(button), name)) {
|
||||
*index = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool infrared_remote_append_signal(
|
||||
InfraredRemote* remote,
|
||||
const InfraredSignal* signal,
|
||||
const char* name) {
|
||||
bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) {
|
||||
InfraredRemoteButton* button = infrared_remote_button_alloc();
|
||||
infrared_remote_button_set_name(button, name);
|
||||
infrared_remote_button_set_signal(button, signal);
|
||||
InfraredButtonArray_push_back(remote->buttons, button);
|
||||
return infrared_remote_store(remote);
|
||||
}
|
||||
|
||||
bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index) {
|
||||
furi_assert(index < InfraredButtonArray_size(remote->buttons));
|
||||
InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, index);
|
||||
infrared_remote_button_set_name(button, new_name);
|
||||
return infrared_remote_store(remote);
|
||||
}
|
||||
|
||||
bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) {
|
||||
furi_assert(index < InfraredButtonArray_size(remote->buttons));
|
||||
InfraredRemoteButton* button;
|
||||
InfraredButtonArray_pop_at(&button, remote->buttons, index);
|
||||
infrared_remote_button_free(button);
|
||||
return infrared_remote_store(remote);
|
||||
}
|
||||
|
||||
void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest) {
|
||||
furi_assert(index_orig < InfraredButtonArray_size(remote->buttons));
|
||||
furi_assert(index_dest < InfraredButtonArray_size(remote->buttons));
|
||||
|
||||
InfraredRemoteButton* button;
|
||||
InfraredButtonArray_pop_at(&button, remote->buttons, index_orig);
|
||||
InfraredButtonArray_push_at(remote->buttons, index_dest, button);
|
||||
}
|
||||
|
||||
bool infrared_remote_store(InfraredRemote* remote) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* ff = flipper_format_file_alloc(storage);
|
||||
|
||||
bool success = false;
|
||||
const char* path = furi_string_get_cstr(remote->path);
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_append(ff, path)) break;
|
||||
if(!infrared_signal_save(signal, ff, name)) break;
|
||||
FURI_LOG_I(TAG, "store file: \'%s\'", path);
|
||||
|
||||
StringArray_push_back(remote->signal_names, name);
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(ff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool infrared_remote_batch_start(
|
||||
InfraredRemote* remote,
|
||||
InfraredBatchCallback batch_callback,
|
||||
const InfraredBatchTarget* target) {
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
InfraredBatch batch_context = {
|
||||
.remote = remote,
|
||||
.ff_in = flipper_format_buffered_file_alloc(storage),
|
||||
.ff_out = flipper_format_buffered_file_alloc(storage),
|
||||
.signal_name = furi_string_alloc(),
|
||||
.signal = infrared_signal_alloc(),
|
||||
.signal_index = 0,
|
||||
};
|
||||
|
||||
const char* path_in = furi_string_get_cstr(remote->path);
|
||||
const char* path_out;
|
||||
|
||||
FS_Error status;
|
||||
|
||||
do {
|
||||
furi_string_printf(tmp, "%s.temp%08x.swp", path_in, rand());
|
||||
path_out = furi_string_get_cstr(tmp);
|
||||
status = storage_common_stat(storage, path_out, NULL);
|
||||
} while(status == FSE_OK || status == FSE_EXIST);
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!flipper_format_buffered_file_open_existing(batch_context.ff_in, path_in)) break;
|
||||
if(!flipper_format_buffered_file_open_always(batch_context.ff_out, path_out)) break;
|
||||
if(!flipper_format_write_header_cstr(
|
||||
batch_context.ff_out, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION))
|
||||
break;
|
||||
|
||||
const size_t signal_count = infrared_remote_get_signal_count(remote);
|
||||
|
||||
for(; batch_context.signal_index < signal_count; ++batch_context.signal_index) {
|
||||
if(!infrared_signal_read(
|
||||
batch_context.signal, batch_context.ff_in, batch_context.signal_name))
|
||||
bool success = flipper_format_file_open_always(ff, path) &&
|
||||
flipper_format_write_header_cstr(ff, "IR signals file", 1);
|
||||
if(success) {
|
||||
InfraredButtonArray_it_t it;
|
||||
for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it);
|
||||
InfraredButtonArray_next(it)) {
|
||||
InfraredRemoteButton* button = *InfraredButtonArray_cref(it);
|
||||
success = infrared_signal_save(
|
||||
infrared_remote_button_get_signal(button),
|
||||
ff,
|
||||
infrared_remote_button_get_name(button));
|
||||
if(!success) {
|
||||
break;
|
||||
if(!batch_callback(&batch_context, target)) break;
|
||||
}
|
||||
}
|
||||
|
||||
if(batch_context.signal_index != signal_count) break;
|
||||
|
||||
if(!flipper_format_buffered_file_close(batch_context.ff_out)) break;
|
||||
if(!flipper_format_buffered_file_close(batch_context.ff_in)) break;
|
||||
|
||||
const FS_Error status = storage_common_rename(storage, path_out, path_in);
|
||||
success = (status == FSE_OK || status == FSE_EXIST);
|
||||
} while(false);
|
||||
|
||||
infrared_signal_free(batch_context.signal);
|
||||
furi_string_free(batch_context.signal_name);
|
||||
flipper_format_free(batch_context.ff_out);
|
||||
flipper_format_free(batch_context.ff_in);
|
||||
furi_string_free(tmp);
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool infrared_remote_insert_signal_callback(
|
||||
const InfraredBatch* batch,
|
||||
const InfraredBatchTarget* target) {
|
||||
// Insert a signal under the specified index
|
||||
if(batch->signal_index == target->signal_index) {
|
||||
if(!infrared_signal_save(target->signal, batch->ff_out, target->signal_name)) return false;
|
||||
StringArray_push_at(
|
||||
batch->remote->signal_names, target->signal_index, target->signal_name);
|
||||
}
|
||||
|
||||
// Write the rest normally
|
||||
return infrared_signal_save(
|
||||
batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name));
|
||||
}
|
||||
|
||||
bool infrared_remote_insert_signal(
|
||||
InfraredRemote* remote,
|
||||
const InfraredSignal* signal,
|
||||
const char* name,
|
||||
size_t index) {
|
||||
if(index >= infrared_remote_get_signal_count(remote)) {
|
||||
return infrared_remote_append_signal(remote, signal, name);
|
||||
}
|
||||
|
||||
const InfraredBatchTarget insert_target = {
|
||||
.signal_index = index,
|
||||
.signal_name = name,
|
||||
.signal = signal,
|
||||
};
|
||||
|
||||
return infrared_remote_batch_start(
|
||||
remote, infrared_remote_insert_signal_callback, &insert_target);
|
||||
}
|
||||
|
||||
static bool infrared_remote_rename_signal_callback(
|
||||
const InfraredBatch* batch,
|
||||
const InfraredBatchTarget* target) {
|
||||
const char* signal_name;
|
||||
|
||||
if(batch->signal_index == target->signal_index) {
|
||||
// Rename the signal at requested index
|
||||
signal_name = target->signal_name;
|
||||
StringArray_set_at(batch->remote->signal_names, batch->signal_index, signal_name);
|
||||
} else {
|
||||
// Use the original name otherwise
|
||||
signal_name = furi_string_get_cstr(batch->signal_name);
|
||||
}
|
||||
|
||||
return infrared_signal_save(batch->signal, batch->ff_out, signal_name);
|
||||
}
|
||||
|
||||
bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name) {
|
||||
furi_assert(index < infrared_remote_get_signal_count(remote));
|
||||
|
||||
const InfraredBatchTarget rename_target = {
|
||||
.signal_index = index,
|
||||
.signal_name = new_name,
|
||||
.signal = NULL,
|
||||
};
|
||||
|
||||
return infrared_remote_batch_start(
|
||||
remote, infrared_remote_rename_signal_callback, &rename_target);
|
||||
}
|
||||
|
||||
static bool infrared_remote_delete_signal_callback(
|
||||
const InfraredBatch* batch,
|
||||
const InfraredBatchTarget* target) {
|
||||
if(batch->signal_index == target->signal_index) {
|
||||
// Do not save the signal to be deleted, remove it from the signal name list instead
|
||||
StringArray_remove_v(
|
||||
batch->remote->signal_names, batch->signal_index, batch->signal_index + 1);
|
||||
} else {
|
||||
// Pass other signals through
|
||||
return infrared_signal_save(
|
||||
batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index) {
|
||||
furi_assert(index < infrared_remote_get_signal_count(remote));
|
||||
|
||||
const InfraredBatchTarget delete_target = {
|
||||
.signal_index = index,
|
||||
.signal_name = NULL,
|
||||
.signal = NULL,
|
||||
};
|
||||
|
||||
return infrared_remote_batch_start(
|
||||
remote, infrared_remote_delete_signal_callback, &delete_target);
|
||||
}
|
||||
|
||||
bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index) {
|
||||
const size_t signal_count = infrared_remote_get_signal_count(remote);
|
||||
furi_assert(index < signal_count);
|
||||
furi_assert(new_index < signal_count);
|
||||
|
||||
if(index == new_index) return true;
|
||||
|
||||
InfraredSignal* signal = infrared_signal_alloc();
|
||||
char* signal_name = strdup(infrared_remote_get_signal_name(remote, index));
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!infrared_remote_load_signal(remote, signal, index)) break;
|
||||
if(!infrared_remote_delete_signal(remote, index)) break;
|
||||
if(!infrared_remote_insert_signal(remote, signal, signal_name, new_index)) break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
free(signal_name);
|
||||
infrared_signal_free(signal);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool infrared_remote_create(InfraredRemote* remote, const char* path) {
|
||||
FURI_LOG_I(TAG, "Creating new file: '%s'", path);
|
||||
|
||||
infrared_remote_reset(remote);
|
||||
infrared_remote_set_path(remote, path);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* ff = flipper_format_file_alloc(storage);
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_always(ff, path)) break;
|
||||
if(!flipper_format_write_header_cstr(ff, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION))
|
||||
break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(ff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool infrared_remote_load(InfraredRemote* remote, const char* path) {
|
||||
FURI_LOG_I(TAG, "Loading file: '%s'", path);
|
||||
|
||||
bool infrared_remote_load(InfraredRemote* remote, FuriString* path) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
||||
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
FuriString* buf;
|
||||
buf = furi_string_alloc();
|
||||
|
||||
FURI_LOG_I(TAG, "load file: \'%s\'", furi_string_get_cstr(path));
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!flipper_format_buffered_file_open_existing(ff, path)) break;
|
||||
|
||||
if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break;
|
||||
uint32_t version;
|
||||
if(!flipper_format_read_header(ff, tmp, &version)) break;
|
||||
if(!flipper_format_read_header(ff, buf, &version)) break;
|
||||
if(!furi_string_equal(buf, "IR signals file") || (version != 1)) break;
|
||||
|
||||
if(!furi_string_equal(tmp, INFRARED_FILE_HEADER) || (version != INFRARED_FILE_VERSION))
|
||||
break;
|
||||
path_extract_filename(path, buf, true);
|
||||
infrared_remote_clear_buttons(remote);
|
||||
infrared_remote_set_name(remote, furi_string_get_cstr(buf));
|
||||
infrared_remote_set_path(remote, furi_string_get_cstr(path));
|
||||
|
||||
infrared_remote_set_path(remote, path);
|
||||
StringArray_reset(remote->signal_names);
|
||||
|
||||
while(infrared_signal_read_name(ff, tmp)) {
|
||||
StringArray_push_back(remote->signal_names, furi_string_get_cstr(tmp));
|
||||
for(bool can_read = true; can_read;) {
|
||||
InfraredRemoteButton* button = infrared_remote_button_alloc();
|
||||
can_read = infrared_signal_read(infrared_remote_button_get_signal(button), ff, buf);
|
||||
if(can_read) {
|
||||
infrared_remote_button_set_name(button, furi_string_get_cstr(buf));
|
||||
InfraredButtonArray_push_back(remote->buttons, button);
|
||||
} else {
|
||||
infrared_remote_button_free(button);
|
||||
}
|
||||
}
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(tmp);
|
||||
furi_string_free(buf);
|
||||
flipper_format_free(ff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool infrared_remote_rename(InfraredRemote* remote, const char* new_path) {
|
||||
const char* old_path = infrared_remote_get_path(remote);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
const FS_Error status = storage_common_rename(storage, old_path, new_path);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
const bool success = (status == FSE_OK || status == FSE_EXIST);
|
||||
|
||||
if(success) {
|
||||
infrared_remote_set_path(remote, new_path);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool infrared_remote_remove(InfraredRemote* remote) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
const FS_Error status = storage_common_remove(storage, infrared_remote_get_path(remote));
|
||||
|
||||
FS_Error status = storage_common_remove(storage, furi_string_get_cstr(remote->path));
|
||||
infrared_remote_reset(remote);
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
const bool success = (status == FSE_OK || status == FSE_NOT_EXIST);
|
||||
|
||||
if(success) {
|
||||
infrared_remote_reset(remote);
|
||||
}
|
||||
|
||||
return success;
|
||||
return (status == FSE_OK || status == FSE_NOT_EXIST);
|
||||
}
|
||||
|
||||
@@ -1,229 +1,30 @@
|
||||
/**
|
||||
* @file infrared_remote.h
|
||||
* @brief Infrared remote library.
|
||||
*
|
||||
* An infrared remote contains zero or more infrared signals which
|
||||
* have a (possibly non-unique) name each.
|
||||
*
|
||||
* The current implementation does load only the names into the memory,
|
||||
* while the signals themselves are loaded on-demand one by one. In theory,
|
||||
* this should allow for quite large remotes with relatively bulky signals.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "infrared_signal.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "infrared_remote_button.h"
|
||||
|
||||
/**
|
||||
* @brief InfraredRemote opaque type declaration.
|
||||
*/
|
||||
typedef struct InfraredRemote InfraredRemote;
|
||||
|
||||
/**
|
||||
* @brief Create a new InfraredRemote instance.
|
||||
*
|
||||
* @returns pointer to the created instance.
|
||||
*/
|
||||
InfraredRemote* infrared_remote_alloc();
|
||||
|
||||
/**
|
||||
* @brief Delete an InfraredRemote instance.
|
||||
*
|
||||
* @param[in,out] remote pointer to the instance to be deleted.
|
||||
*/
|
||||
void infrared_remote_free(InfraredRemote* remote);
|
||||
|
||||
/**
|
||||
* @brief Reset an InfraredRemote instance.
|
||||
*
|
||||
* Resetting a remote clears its signal name list and
|
||||
* the associated file path.
|
||||
*
|
||||
* @param[in,out] remote pointer to the instance to be deleted.
|
||||
*/
|
||||
void infrared_remote_reset(InfraredRemote* remote);
|
||||
|
||||
/**
|
||||
* @brief Get an InfraredRemote instance's name.
|
||||
*
|
||||
* The name is deduced from the file path.
|
||||
*
|
||||
* The return value remains valid unless one of the following functions is called:
|
||||
* - infrared_remote_reset()
|
||||
* - infrared_remote_load()
|
||||
* - infrared_remote_create()
|
||||
*
|
||||
* @param[in] remote pointer to the instance to be queried.
|
||||
* @returns pointer to a zero-terminated string containing the name.
|
||||
*/
|
||||
const char* infrared_remote_get_name(const InfraredRemote* remote);
|
||||
void infrared_remote_set_name(InfraredRemote* remote, const char* name);
|
||||
const char* infrared_remote_get_name(InfraredRemote* remote);
|
||||
|
||||
/**
|
||||
* @brief Get an InfraredRemote instance's file path.
|
||||
*
|
||||
* Same return value validity considerations as infrared_remote_get_name().
|
||||
*
|
||||
* @param[in] remote pointer to the instance to be queried.
|
||||
* @returns pointer to a zero-terminated string containing the path.
|
||||
*/
|
||||
const char* infrared_remote_get_path(const InfraredRemote* remote);
|
||||
void infrared_remote_set_path(InfraredRemote* remote, const char* path);
|
||||
const char* infrared_remote_get_path(InfraredRemote* remote);
|
||||
|
||||
/**
|
||||
* @brief Get the number of signals listed in an InfraredRemote instance.
|
||||
*
|
||||
* @param[in] remote pointer to the instance to be queried.
|
||||
* @returns number of signals, zero or more
|
||||
*/
|
||||
size_t infrared_remote_get_signal_count(const InfraredRemote* remote);
|
||||
size_t infrared_remote_get_button_count(InfraredRemote* remote);
|
||||
InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index);
|
||||
bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index);
|
||||
|
||||
/**
|
||||
* @brief Get the name of a signal listed in an InfraredRemote instance.
|
||||
*
|
||||
* @param[in] remote pointer to the instance to be queried.
|
||||
* @param[in] index index of the signal in question. Must be less than the total signal count.
|
||||
*/
|
||||
const char* infrared_remote_get_signal_name(const InfraredRemote* remote, size_t index);
|
||||
bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal);
|
||||
bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index);
|
||||
bool infrared_remote_delete_button(InfraredRemote* remote, size_t index);
|
||||
void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest);
|
||||
|
||||
/**
|
||||
* @brief Get the index of a signal listed in an InfraredRemote instance by its name.
|
||||
*
|
||||
* @param[in] remote pointer to the instance to be queried.
|
||||
* @param[in] name pointer to a zero-terminated string containig the name of the signal in question.
|
||||
* @param[out] index pointer to the variable to hold the signal index.
|
||||
* @returns true if a signal with the given name was found, false otherwise.
|
||||
*/
|
||||
bool infrared_remote_get_signal_index(
|
||||
const InfraredRemote* remote,
|
||||
const char* name,
|
||||
size_t* index);
|
||||
|
||||
/**
|
||||
* @brief Load a signal listed in an InfraredRemote instance.
|
||||
*
|
||||
* As mentioned above, the signals are loaded on-demand. The user code must call this function
|
||||
* each time it wants to interact with a new signal.
|
||||
*
|
||||
* @param[in] remote pointer to the instance to load from.
|
||||
* @param[out] signal pointer to the signal to load into. Must be allocated.
|
||||
* @param[in] index index of the signal to be loaded. Must be less than the total signal count.
|
||||
* @return true if the signal was successfully loaded, false otherwise.
|
||||
*/
|
||||
bool infrared_remote_load_signal(
|
||||
const InfraredRemote* remote,
|
||||
InfraredSignal* signal,
|
||||
size_t index);
|
||||
|
||||
/**
|
||||
* @brief Append a signal to the file associated with an InfraredRemote instance.
|
||||
*
|
||||
* The file path must be somehow initialised first by calling either infrared_remote_load() or
|
||||
* infrared_remote_create(). As the name suggests, the signal will be put in the end of the file.
|
||||
*
|
||||
* @param[in,out] remote pointer to the instance to append to.
|
||||
* @param[in] signal pointer to the signal to be appended.
|
||||
* @param[in] name pointer to a zero-terminated string containing the name of the signal.
|
||||
* @returns true if the signal was successfully appended, false otherwise.
|
||||
*/
|
||||
bool infrared_remote_append_signal(
|
||||
InfraredRemote* remote,
|
||||
const InfraredSignal* signal,
|
||||
const char* name);
|
||||
|
||||
/**
|
||||
* @brief Insert a signal to the file associated with an InfraredRemote instance.
|
||||
*
|
||||
* Same behaviour as infrared_remote_append_signal(), but the user code can decide where to
|
||||
* put the signal in the file.
|
||||
*
|
||||
* Index values equal to or greater than the total signal count will result in behaviour
|
||||
* identical to infrared_remote_append_signal().
|
||||
*
|
||||
* @param[in,out] remote pointer to the instance to insert to.
|
||||
* @param[in] signal pointer to the signal to be inserted.
|
||||
* @param[in] name pointer to a zero-terminated string containing the name of the signal.
|
||||
* @param[in] index the index under which the signal shall be inserted.
|
||||
* @returns true if the signal was successfully inserted, false otherwise.
|
||||
*/
|
||||
bool infrared_remote_insert_signal(
|
||||
InfraredRemote* remote,
|
||||
const InfraredSignal* signal,
|
||||
const char* name,
|
||||
size_t index);
|
||||
|
||||
/**
|
||||
* @brief Rename a signal in the file associated with an InfraredRemote instance.
|
||||
*
|
||||
* Only changes the signal's name, but neither its position nor contents.
|
||||
*
|
||||
* @param[in,out] remote pointer to the instance to be modified.
|
||||
* @param[in] index index of the signal to be renamed. Must be less than the total signal count.
|
||||
* @param[in] new_name pointer to a zero-terminated string containig the signal's new name.
|
||||
* @returns true if the signal was successfully renamed, false otherwise.
|
||||
*/
|
||||
bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name);
|
||||
|
||||
/**
|
||||
* @brief Change a signal's position in the file associated with an InfraredRemote instance.
|
||||
*
|
||||
* Only changes the signal's position (index), but neither its name nor contents.
|
||||
*
|
||||
* @param[in,out] remote pointer to the instance to be modified.
|
||||
* @param[in] index index of the signal to be moved. Must be less than the total signal count.
|
||||
* @param[in] new_index index of the signal to be moved. Must be less than the total signal count.
|
||||
*/
|
||||
bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index);
|
||||
|
||||
/**
|
||||
* @brief Delete a signal in the file associated with an InfraredRemote instance.
|
||||
*
|
||||
* @param[in,out] remote pointer to the instance to be modified.
|
||||
* @param[in] index index of the signal to be deleted. Must be less than the total signal count.
|
||||
* @returns true if the signal was successfully deleted, false otherwise.
|
||||
*/
|
||||
bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index);
|
||||
|
||||
/**
|
||||
* @brief Create a new file and associate it with an InfraredRemote instance.
|
||||
*
|
||||
* The instance will be reset and given a new empty file with just the header.
|
||||
*
|
||||
* @param[in,out] remote pointer to the instance to be assigned with a new file.
|
||||
* @param[in] path pointer to a zero-terminated string containing the full file path.
|
||||
* @returns true if the file was successfully created, false otherwise.
|
||||
*/
|
||||
bool infrared_remote_create(InfraredRemote* remote, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Associate an InfraredRemote instance with a file and load the signal names from it.
|
||||
*
|
||||
* The instance will be reset and fill its signal name list from the given file.
|
||||
* The file must already exist and be valid.
|
||||
*
|
||||
* @param[in,out] remote pointer to the instance to be assigned with an existing file.
|
||||
* @param[in] path pointer to a zero-terminated string containing the full file path.
|
||||
* @returns true if the file was successfully loaded, false otherwise.
|
||||
*/
|
||||
bool infrared_remote_load(InfraredRemote* remote, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Rename the file associated with an InfraredRemote instance.
|
||||
*
|
||||
* Only renames the file, no signals are added, moved or deleted.
|
||||
*
|
||||
* @param[in,out] remote pointer to the instance to be modified.
|
||||
* @param[in] new_path pointer to a zero-terminated string containing the new full file path.
|
||||
* @returns true if the file was successfully renamed, false otherwise.
|
||||
*/
|
||||
bool infrared_remote_rename(InfraredRemote* remote, const char* new_path);
|
||||
|
||||
/**
|
||||
* @brief Remove the file associated with an InfraredRemote instance.
|
||||
*
|
||||
* This operation is irreversible and fully deletes the remote file
|
||||
* from the underlying filesystem.
|
||||
* After calling this function, the instance becomes invalid until
|
||||
* infrared_remote_create() or infrared_remote_load() are successfully executed.
|
||||
*
|
||||
* @param[in,out] remote pointer to the instance to be modified.
|
||||
* @returns true if the file was successfully removed, false otherwise.
|
||||
*/
|
||||
bool infrared_remote_store(InfraredRemote* remote);
|
||||
bool infrared_remote_load(InfraredRemote* remote, FuriString* path);
|
||||
bool infrared_remote_remove(InfraredRemote* remote);
|
||||
|
||||
37
applications/main/infrared/infrared_remote_button.c
Normal file
37
applications/main/infrared/infrared_remote_button.c
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "infrared_remote_button.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
struct InfraredRemoteButton {
|
||||
FuriString* name;
|
||||
InfraredSignal* signal;
|
||||
};
|
||||
|
||||
InfraredRemoteButton* infrared_remote_button_alloc() {
|
||||
InfraredRemoteButton* button = malloc(sizeof(InfraredRemoteButton));
|
||||
button->name = furi_string_alloc();
|
||||
button->signal = infrared_signal_alloc();
|
||||
return button;
|
||||
}
|
||||
|
||||
void infrared_remote_button_free(InfraredRemoteButton* button) {
|
||||
furi_string_free(button->name);
|
||||
infrared_signal_free(button->signal);
|
||||
free(button);
|
||||
}
|
||||
|
||||
void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name) {
|
||||
furi_string_set(button->name, name);
|
||||
}
|
||||
|
||||
const char* infrared_remote_button_get_name(InfraredRemoteButton* button) {
|
||||
return furi_string_get_cstr(button->name);
|
||||
}
|
||||
|
||||
void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal) {
|
||||
infrared_signal_set_signal(button->signal, signal);
|
||||
}
|
||||
|
||||
InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button) {
|
||||
return button->signal;
|
||||
}
|
||||
14
applications/main/infrared/infrared_remote_button.h
Normal file
14
applications/main/infrared/infrared_remote_button.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "infrared_signal.h"
|
||||
|
||||
typedef struct InfraredRemoteButton InfraredRemoteButton;
|
||||
|
||||
InfraredRemoteButton* infrared_remote_button_alloc();
|
||||
void infrared_remote_button_free(InfraredRemoteButton* button);
|
||||
|
||||
void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name);
|
||||
const char* infrared_remote_button_get_name(InfraredRemoteButton* button);
|
||||
|
||||
void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal);
|
||||
InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button);
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
#define TAG "InfraredSignal"
|
||||
|
||||
#define INFRARED_SIGNAL_NAME_KEY "name"
|
||||
|
||||
struct InfraredSignal {
|
||||
bool is_raw;
|
||||
union {
|
||||
@@ -26,7 +24,7 @@ static void infrared_signal_clear_timings(InfraredSignal* signal) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool infrared_signal_is_message_valid(const InfraredMessage* message) {
|
||||
static bool infrared_signal_is_message_valid(InfraredMessage* message) {
|
||||
if(!infrared_is_protocol_valid(message->protocol)) {
|
||||
FURI_LOG_E(TAG, "Unknown protocol");
|
||||
return false;
|
||||
@@ -59,7 +57,7 @@ static bool infrared_signal_is_message_valid(const InfraredMessage* message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) {
|
||||
static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) {
|
||||
if((raw->frequency > INFRARED_MAX_FREQUENCY) || (raw->frequency < INFRARED_MIN_FREQUENCY)) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
@@ -85,8 +83,7 @@ static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) {
|
||||
static inline bool infrared_signal_save_message(InfraredMessage* message, FlipperFormat* ff) {
|
||||
const char* protocol_name = infrared_get_protocol_name(message->protocol);
|
||||
return flipper_format_write_string_cstr(ff, "type", "parsed") &&
|
||||
flipper_format_write_string_cstr(ff, "protocol", protocol_name) &&
|
||||
@@ -94,7 +91,7 @@ static inline bool
|
||||
flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4);
|
||||
}
|
||||
|
||||
static inline bool infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) {
|
||||
static inline bool infrared_signal_save_raw(InfraredRawSignal* raw, FlipperFormat* ff) {
|
||||
furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT);
|
||||
return flipper_format_write_string_cstr(ff, "type", "raw") &&
|
||||
flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) &&
|
||||
@@ -183,11 +180,11 @@ void infrared_signal_free(InfraredSignal* signal) {
|
||||
free(signal);
|
||||
}
|
||||
|
||||
bool infrared_signal_is_raw(const InfraredSignal* signal) {
|
||||
bool infrared_signal_is_raw(InfraredSignal* signal) {
|
||||
return signal->is_raw;
|
||||
}
|
||||
|
||||
bool infrared_signal_is_valid(const InfraredSignal* signal) {
|
||||
bool infrared_signal_is_valid(InfraredSignal* signal) {
|
||||
return signal->is_raw ? infrared_signal_is_raw_valid(&signal->payload.raw) :
|
||||
infrared_signal_is_message_valid(&signal->payload.message);
|
||||
}
|
||||
@@ -236,7 +233,7 @@ void infrared_signal_set_raw_signal(
|
||||
memcpy(signal->payload.raw.timings, timings, timings_size * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal) {
|
||||
InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal) {
|
||||
furi_assert(signal->is_raw);
|
||||
return &signal->payload.raw;
|
||||
}
|
||||
@@ -248,14 +245,14 @@ void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage*
|
||||
signal->payload.message = *message;
|
||||
}
|
||||
|
||||
const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal) {
|
||||
InfraredMessage* infrared_signal_get_message(InfraredSignal* signal) {
|
||||
furi_assert(!signal->is_raw);
|
||||
return &signal->payload.message;
|
||||
}
|
||||
|
||||
bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name) {
|
||||
bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name) {
|
||||
if(!flipper_format_write_comment_cstr(ff, "") ||
|
||||
!flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_NAME_KEY, name)) {
|
||||
!flipper_format_write_string_cstr(ff, "name", name)) {
|
||||
return false;
|
||||
} else if(signal->is_raw) {
|
||||
return infrared_signal_save_raw(&signal->payload.raw, ff);
|
||||
@@ -265,61 +262,46 @@ bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const
|
||||
}
|
||||
|
||||
bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) {
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!infrared_signal_read_name(ff, name)) break;
|
||||
if(!flipper_format_read_string(ff, "name", tmp)) break;
|
||||
furi_string_set(name, tmp);
|
||||
if(!infrared_signal_read_body(signal, ff)) break;
|
||||
success = true;
|
||||
} while(0);
|
||||
|
||||
success = true; //-V779
|
||||
furi_string_free(tmp);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool infrared_signal_search_and_read(
|
||||
InfraredSignal* signal,
|
||||
FlipperFormat* ff,
|
||||
const FuriString* name) {
|
||||
bool success = false;
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
do {
|
||||
bool is_name_found = false;
|
||||
while(flipper_format_read_string(ff, "name", tmp)) {
|
||||
is_name_found = furi_string_equal(name, tmp);
|
||||
if(is_name_found) break;
|
||||
}
|
||||
if(!is_name_found) break; //-V547
|
||||
if(!infrared_signal_read_body(signal, ff)) break; //-V779
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name) {
|
||||
return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name);
|
||||
}
|
||||
|
||||
bool infrared_signal_search_by_name_and_read(
|
||||
InfraredSignal* signal,
|
||||
FlipperFormat* ff,
|
||||
const char* name) {
|
||||
bool success = false;
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
while(infrared_signal_read_name(ff, tmp)) {
|
||||
if(furi_string_equal(tmp, name)) {
|
||||
success = infrared_signal_read_body(signal, ff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(tmp);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool infrared_signal_search_by_index_and_read(
|
||||
InfraredSignal* signal,
|
||||
FlipperFormat* ff,
|
||||
size_t index) {
|
||||
bool success = false;
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
for(uint32_t i = 0; infrared_signal_read_name(ff, tmp); ++i) {
|
||||
if(i == index) {
|
||||
success = infrared_signal_read_body(signal, ff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(tmp);
|
||||
return success;
|
||||
}
|
||||
|
||||
void infrared_signal_transmit(const InfraredSignal* signal) {
|
||||
void infrared_signal_transmit(InfraredSignal* signal) {
|
||||
if(signal->is_raw) {
|
||||
const InfraredRawSignal* raw_signal = &signal->payload.raw;
|
||||
InfraredRawSignal* raw_signal = &signal->payload.raw;
|
||||
infrared_send_raw_ext(
|
||||
raw_signal->timings,
|
||||
raw_signal->timings_size,
|
||||
@@ -327,7 +309,7 @@ void infrared_signal_transmit(const InfraredSignal* signal) {
|
||||
raw_signal->frequency,
|
||||
raw_signal->duty_cycle);
|
||||
} else {
|
||||
const InfraredMessage* message = &signal->payload.message;
|
||||
InfraredMessage* message = &signal->payload.message;
|
||||
infrared_send(message, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,205 +1,45 @@
|
||||
/**
|
||||
* @file infrared_signal.h
|
||||
* @brief Infrared signal library.
|
||||
*
|
||||
* Infrared signals may be of two types:
|
||||
* - known to the infrared signal decoder, or *parsed* signals
|
||||
* - the rest, or *raw* signals, which are treated merely as a set of timings.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <infrared/encoder_decoder/infrared.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <infrared.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
/**
|
||||
* @brief InfraredSignal opaque type declaration.
|
||||
*/
|
||||
typedef struct InfraredSignal InfraredSignal;
|
||||
|
||||
/**
|
||||
* @brief Raw signal type definition.
|
||||
*
|
||||
* Measurement units used:
|
||||
* - time: microseconds (uS)
|
||||
* - frequency: Hertz (Hz)
|
||||
* - duty_cycle: no units, fraction between 0 and 1.
|
||||
*/
|
||||
typedef struct {
|
||||
size_t timings_size; /**< Number of elements in the timings array. */
|
||||
uint32_t* timings; /**< Pointer to an array of timings describing the signal. */
|
||||
uint32_t frequency; /**< Carrier frequency of the signal. */
|
||||
float duty_cycle; /**< Duty cycle of the signal. */
|
||||
size_t timings_size;
|
||||
uint32_t* timings;
|
||||
uint32_t frequency;
|
||||
float duty_cycle;
|
||||
} InfraredRawSignal;
|
||||
|
||||
/**
|
||||
* @brief Create a new InfraredSignal instance.
|
||||
*
|
||||
* @returns pointer to the instance created.
|
||||
*/
|
||||
InfraredSignal* infrared_signal_alloc();
|
||||
|
||||
/**
|
||||
* @brief Delete an InfraredSignal instance.
|
||||
*
|
||||
* @param[in,out] signal pointer to the instance to be deleted.
|
||||
*/
|
||||
void infrared_signal_free(InfraredSignal* signal);
|
||||
|
||||
/**
|
||||
* @brief Test whether an InfraredSignal instance holds a raw signal.
|
||||
*
|
||||
* @param[in] signal pointer to the instance to be tested.
|
||||
* @returns true if the instance holds a raw signal, false otherwise.
|
||||
*/
|
||||
bool infrared_signal_is_raw(const InfraredSignal* signal);
|
||||
bool infrared_signal_is_raw(InfraredSignal* signal);
|
||||
bool infrared_signal_is_valid(InfraredSignal* signal);
|
||||
|
||||
/**
|
||||
* @brief Test whether an InfraredSignal instance holds any signal.
|
||||
*
|
||||
* @param[in] signal pointer to the instance to be tested.
|
||||
* @returns true if the instance holds raw signal, false otherwise.
|
||||
*/
|
||||
bool infrared_signal_is_valid(const InfraredSignal* signal);
|
||||
|
||||
/**
|
||||
* @brief Set an InfraredInstance to hold the signal from another one.
|
||||
*
|
||||
* Any instance's previous contents will be automatically deleted before
|
||||
* copying the source instance's contents.
|
||||
*
|
||||
* @param[in,out] signal pointer to the destination instance.
|
||||
* @param[in] other pointer to the source instance.
|
||||
*/
|
||||
void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other);
|
||||
|
||||
/**
|
||||
* @brief Set an InfraredInstance to hold a raw signal.
|
||||
*
|
||||
* Any instance's previous contents will be automatically deleted before
|
||||
* copying the raw signal.
|
||||
*
|
||||
* After this call, infrared_signal_is_raw() will return true.
|
||||
*
|
||||
* @param[in,out] signal pointer to the destination instance.
|
||||
* @param[in] timings pointer to an array containing the raw signal timings.
|
||||
* @param[in] timings_size number of elements in the timings array.
|
||||
* @param[in] frequency signal carrier frequency, in Hertz.
|
||||
* @param[in] duty_cycle signal duty cycle, fraction between 0 and 1.
|
||||
*/
|
||||
void infrared_signal_set_raw_signal(
|
||||
InfraredSignal* signal,
|
||||
const uint32_t* timings,
|
||||
size_t timings_size,
|
||||
uint32_t frequency,
|
||||
float duty_cycle);
|
||||
InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal);
|
||||
|
||||
/**
|
||||
* @brief Get the raw signal held by an InfraredSignal instance.
|
||||
*
|
||||
* @warning the instance MUST hold a *raw* signal, otherwise undefined behaviour will occur.
|
||||
*
|
||||
* @param[in] signal pointer to the instance to be queried.
|
||||
* @returns pointer to the raw signal structure held by the instance.
|
||||
*/
|
||||
const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal);
|
||||
|
||||
/**
|
||||
* @brief Set an InfraredInstance to hold a parsed signal.
|
||||
*
|
||||
* Any instance's previous contents will be automatically deleted before
|
||||
* copying the raw signal.
|
||||
*
|
||||
* After this call, infrared_signal_is_raw() will return false.
|
||||
*
|
||||
* @param[in,out] signal pointer to the destination instance.
|
||||
* @param[in] message pointer to the message containing the parsed signal.
|
||||
*/
|
||||
void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message);
|
||||
InfraredMessage* infrared_signal_get_message(InfraredSignal* signal);
|
||||
|
||||
/**
|
||||
* @brief Get the parsed signal held by an InfraredSignal instance.
|
||||
*
|
||||
* @warning the instance MUST hold a *parsed* signal, otherwise undefined behaviour will occur.
|
||||
*
|
||||
* @param[in] signal pointer to the instance to be queried.
|
||||
* @returns pointer to the parsed signal structure held by the instance.
|
||||
*/
|
||||
const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal);
|
||||
|
||||
/**
|
||||
* @brief Read a signal from a FlipperFormat file into an InfraredSignal instance.
|
||||
*
|
||||
* The file must be allocated and open prior to this call. The seek position determines
|
||||
* which signal will be read (if there is more than one in the file). Calling this function
|
||||
* repeatedly will result in all signals in the file to be read until no more are left.
|
||||
*
|
||||
* @param[in,out] signal pointer to the instance to be read into.
|
||||
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
|
||||
* @param[out] name pointer to the string to hold the signal name. Must be properly allocated.
|
||||
* @returns true if a signal was successfully read, false otherwise (e.g. no more signals to read).
|
||||
*/
|
||||
bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name);
|
||||
bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name);
|
||||
|
||||
/**
|
||||
* @brief Read a signal name from a FlipperFormat file.
|
||||
*
|
||||
* Same behaviour as infrared_signal_read(), but only the name is read.
|
||||
*
|
||||
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
|
||||
* @param[out] name pointer to the string to hold the signal name. Must be properly allocated.
|
||||
* @returns true if a signal name was successfully read, false otherwise (e.g. no more signals to read).
|
||||
*/
|
||||
bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name);
|
||||
|
||||
/**
|
||||
* @brief Read a signal with a particular name from a FlipperFormat file into an InfraredSignal instance.
|
||||
*
|
||||
* This function will look for a signal with the given name and if found, attempt to read it.
|
||||
* Same considerations apply as to infrared_signal_read().
|
||||
*
|
||||
* @param[in,out] signal pointer to the instance to be read into.
|
||||
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
|
||||
* @param[in] name pointer to a zero-terminated string containing the requested signal name.
|
||||
* @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found).
|
||||
*/
|
||||
bool infrared_signal_search_by_name_and_read(
|
||||
bool infrared_signal_search_and_read(
|
||||
InfraredSignal* signal,
|
||||
FlipperFormat* ff,
|
||||
const char* name);
|
||||
const FuriString* name);
|
||||
|
||||
/**
|
||||
* @brief Read a signal with a particular index from a FlipperFormat file into an InfraredSignal instance.
|
||||
*
|
||||
* This function will look for a signal with the given index and if found, attempt to read it.
|
||||
* Same considerations apply as to infrared_signal_read().
|
||||
*
|
||||
* @param[in,out] signal pointer to the instance to be read into.
|
||||
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
|
||||
* @param[in] index the requested signal index.
|
||||
* @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found).
|
||||
*/
|
||||
bool infrared_signal_search_by_index_and_read(
|
||||
InfraredSignal* signal,
|
||||
FlipperFormat* ff,
|
||||
size_t index);
|
||||
|
||||
/**
|
||||
* @brief Save a signal contained in an InfraredSignal instance to a FlipperFormat file.
|
||||
*
|
||||
* The file must be allocated and open prior to this call. Additionally, an appropriate header
|
||||
* must be already written into the file.
|
||||
*
|
||||
* @param[in] signal pointer to the instance holding the signal to be saved.
|
||||
* @param[in,out] ff pointer to the FlipperFormat file instance to write to.
|
||||
* @param[in] name pointer to a zero-terminated string contating the name of the signal.
|
||||
*/
|
||||
bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name);
|
||||
|
||||
/**
|
||||
* @brief Transmit a signal contained in an InfraredSignal instance.
|
||||
*
|
||||
* The transmission happens once per call using the built-in hardware (via HAL calls).
|
||||
*
|
||||
* @param[in] signal pointer to the instance holding the signal to be transmitted.
|
||||
*/
|
||||
void infrared_signal_transmit(const InfraredSignal* signal);
|
||||
void infrared_signal_transmit(InfraredSignal* signal);
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
#include "../../infrared_app_i.h"
|
||||
#include "../../infrared_i.h"
|
||||
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void infrared_scene_universal_common_item_callback(void* context, uint32_t index) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
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) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1);
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
|
||||
}
|
||||
|
||||
static void
|
||||
infrared_scene_universal_common_show_popup(InfraredApp* infrared, uint32_t record_count) {
|
||||
static void infrared_scene_universal_common_show_popup(Infrared* infrared, uint32_t record_count) {
|
||||
ViewStack* view_stack = infrared->view_stack;
|
||||
InfraredProgressView* progress = infrared->progress;
|
||||
infrared_progress_view_set_progress_total(progress, record_count);
|
||||
@@ -25,7 +24,7 @@ static void
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
|
||||
}
|
||||
|
||||
static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) {
|
||||
static void infrared_scene_universal_common_hide_popup(Infrared* infrared) {
|
||||
ViewStack* view_stack = infrared->view_stack;
|
||||
InfraredProgressView* progress = infrared->progress;
|
||||
view_stack_remove_view(view_stack, infrared_progress_view_get_view(progress));
|
||||
@@ -33,12 +32,12 @@ static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) {
|
||||
}
|
||||
|
||||
void infrared_scene_universal_common_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel));
|
||||
}
|
||||
|
||||
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
InfraredBruteForce* brute_force = infrared->brute_force;
|
||||
bool consumed = false;
|
||||
@@ -85,7 +84,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e
|
||||
}
|
||||
|
||||
void infrared_scene_universal_common_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
ButtonPanel* button_panel = infrared->button_panel;
|
||||
view_stack_remove_view(infrared->view_stack, button_panel_get_view(button_panel));
|
||||
infrared_brute_force_reset(infrared->brute_force);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include "../infrared_i.h"
|
||||
|
||||
static void infrared_scene_dialog_result_callback(DialogExResult result, void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void infrared_scene_ask_back_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
DialogEx* dialog_ex = infrared->dialog_ex;
|
||||
|
||||
if(infrared->app_state.is_learning_new_remote) {
|
||||
@@ -28,7 +28,7 @@ void infrared_scene_ask_back_on_enter(void* context) {
|
||||
}
|
||||
|
||||
bool infrared_scene_ask_back_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
@@ -54,6 +54,6 @@ bool infrared_scene_ask_back_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
|
||||
void infrared_scene_ask_back_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
dialog_ex_reset(infrared->dialog_ex);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include "../infrared_i.h"
|
||||
|
||||
static void infrared_scene_dialog_result_callback(DialogExResult result, void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void infrared_scene_ask_retry_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
DialogEx* dialog_ex = infrared->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop);
|
||||
@@ -23,7 +23,7 @@ void infrared_scene_ask_retry_on_enter(void* context) {
|
||||
}
|
||||
|
||||
bool infrared_scene_ask_retry_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
@@ -43,6 +43,6 @@ bool infrared_scene_ask_retry_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
|
||||
void infrared_scene_ask_retry_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
dialog_ex_reset(infrared->dialog_ex);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include "../infrared_i.h"
|
||||
|
||||
void infrared_scene_debug_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
InfraredWorker* worker = infrared->worker;
|
||||
|
||||
infrared_worker_rx_set_received_signal_callback(
|
||||
@@ -14,16 +14,16 @@ void infrared_scene_debug_on_enter(void* context) {
|
||||
}
|
||||
|
||||
bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == InfraredCustomEventTypeSignalReceived) {
|
||||
InfraredDebugView* debug_view = infrared->debug_view;
|
||||
InfraredSignal* signal = infrared->current_signal;
|
||||
InfraredSignal* signal = infrared->received_signal;
|
||||
|
||||
if(infrared_signal_is_raw(signal)) {
|
||||
const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
|
||||
InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
|
||||
infrared_debug_view_set_text(debug_view, "RAW\n%d samples\n", raw->timings_size);
|
||||
|
||||
printf("RAW, %zu samples:\r\n", raw->timings_size);
|
||||
@@ -33,7 +33,7 @@ bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) {
|
||||
printf("\r\n");
|
||||
|
||||
} else {
|
||||
const InfraredMessage* message = infrared_signal_get_message(signal);
|
||||
InfraredMessage* message = infrared_signal_get_message(signal);
|
||||
infrared_debug_view_set_text(
|
||||
debug_view,
|
||||
"%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n",
|
||||
@@ -61,7 +61,7 @@ bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
|
||||
void infrared_scene_debug_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
InfraredWorker* worker = infrared->worker;
|
||||
infrared_worker_rx_stop(worker);
|
||||
infrared_worker_rx_enable_blink_on_receiving(worker, false);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include "../infrared_i.h"
|
||||
#include <furi_hal_infrared.h>
|
||||
|
||||
uint8_t value_index_ir;
|
||||
@@ -10,7 +10,7 @@ const char* const infrared_debug_cfg_variables_text[] = {
|
||||
};
|
||||
|
||||
static void infrared_scene_debug_settings_changed(VariableItem* item) {
|
||||
InfraredApp* infrared = variable_item_get_context(item);
|
||||
Infrared* infrared = variable_item_get_context(item);
|
||||
value_index_ir = variable_item_get_current_value_index(item);
|
||||
UNUSED(infrared);
|
||||
|
||||
@@ -35,12 +35,12 @@ static void infrared_scene_debug_settings_power_changed(VariableItem* item) {
|
||||
}
|
||||
|
||||
static void infrared_debug_settings_start_var_list_enter_callback(void* context, uint32_t index) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void infrared_scene_debug_settings_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
|
||||
VariableItemList* variable_item_list = infrared->variable_item_list;
|
||||
|
||||
@@ -72,7 +72,7 @@ void infrared_scene_debug_settings_on_enter(void* context) {
|
||||
}
|
||||
|
||||
bool infrared_scene_debug_settings_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
UNUSED(infrared);
|
||||
UNUSED(event);
|
||||
|
||||
@@ -80,6 +80,6 @@ bool infrared_scene_debug_settings_on_event(void* context, SceneManagerEvent eve
|
||||
}
|
||||
|
||||
void infrared_scene_debug_settings_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
variable_item_list_reset(infrared->variable_item_list);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include "../infrared_i.h"
|
||||
|
||||
typedef enum {
|
||||
SubmenuIndexAddButton,
|
||||
@@ -10,12 +10,12 @@ typedef enum {
|
||||
} SubmenuIndex;
|
||||
|
||||
static void infrared_scene_edit_submenu_callback(void* context, uint32_t index) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void infrared_scene_edit_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
Submenu* submenu = infrared->submenu;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
|
||||
@@ -64,7 +64,7 @@ void infrared_scene_edit_on_enter(void* context) {
|
||||
}
|
||||
|
||||
bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
@@ -106,6 +106,6 @@ bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
|
||||
void infrared_scene_edit_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
submenu_reset(infrared->submenu);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include "../infrared_i.h"
|
||||
|
||||
static void infrared_scene_edit_button_select_submenu_callback(void* context, uint32_t index) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void infrared_scene_edit_button_select_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
Submenu* submenu = infrared->submenu;
|
||||
InfraredRemote* remote = infrared->remote;
|
||||
InfraredAppState* app_state = &infrared->app_state;
|
||||
@@ -16,16 +16,16 @@ void infrared_scene_edit_button_select_on_enter(void* context) {
|
||||
"Delete Button:";
|
||||
submenu_set_header(submenu, header);
|
||||
|
||||
const size_t button_count = infrared_remote_get_signal_count(remote);
|
||||
const size_t button_count = infrared_remote_get_button_count(remote);
|
||||
for(size_t i = 0; i < button_count; ++i) {
|
||||
InfraredRemoteButton* button = infrared_remote_get_button(remote, i);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
infrared_remote_get_signal_name(remote, i),
|
||||
infrared_remote_button_get_name(button),
|
||||
i,
|
||||
infrared_scene_edit_button_select_submenu_callback,
|
||||
context);
|
||||
}
|
||||
|
||||
if(button_count && app_state->current_button_index != InfraredButtonIndexNone) {
|
||||
submenu_set_selected_item(submenu, app_state->current_button_index);
|
||||
app_state->current_button_index = InfraredButtonIndexNone;
|
||||
@@ -35,7 +35,7 @@ void infrared_scene_edit_button_select_on_enter(void* context) {
|
||||
}
|
||||
|
||||
bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
InfraredAppState* app_state = &infrared->app_state;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
bool consumed = false;
|
||||
@@ -48,7 +48,7 @@ bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent
|
||||
} else if(edit_mode == InfraredEditModeDelete) {
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneEditDelete);
|
||||
} else {
|
||||
furi_crash();
|
||||
furi_assert(0);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
@@ -57,6 +57,6 @@ bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent
|
||||
}
|
||||
|
||||
void infrared_scene_edit_button_select_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
submenu_reset(infrared->submenu);
|
||||
}
|
||||
|
||||
@@ -1,49 +1,42 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include "../infrared_i.h"
|
||||
|
||||
static void
|
||||
infrared_scene_edit_delete_dialog_result_callback(DialogExResult result, void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void infrared_scene_edit_delete_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
DialogEx* dialog_ex = infrared->dialog_ex;
|
||||
InfraredRemote* remote = infrared->remote;
|
||||
|
||||
const InfraredEditTarget edit_target = infrared->app_state.edit_target;
|
||||
if(edit_target == InfraredEditTargetButton) {
|
||||
int32_t current_button_index = infrared->app_state.current_button_index;
|
||||
furi_assert(current_button_index != InfraredButtonIndexNone);
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "Delete Button?", 64, 0, AlignCenter, AlignTop);
|
||||
InfraredRemoteButton* current_button =
|
||||
infrared_remote_get_button(remote, current_button_index);
|
||||
InfraredSignal* signal = infrared_remote_button_get_signal(current_button);
|
||||
|
||||
const int32_t current_button_index = infrared->app_state.current_button_index;
|
||||
furi_check(current_button_index != InfraredButtonIndexNone);
|
||||
|
||||
if(!infrared_remote_load_signal(remote, infrared->current_signal, current_button_index)) {
|
||||
infrared_show_error_message(
|
||||
infrared,
|
||||
"Failed to load\n\"%s\"",
|
||||
infrared_remote_get_signal_name(remote, current_button_index));
|
||||
scene_manager_previous_scene(infrared->scene_manager);
|
||||
return;
|
||||
}
|
||||
|
||||
if(infrared_signal_is_raw(infrared->current_signal)) {
|
||||
const InfraredRawSignal* raw =
|
||||
infrared_signal_get_raw_signal(infrared->current_signal);
|
||||
if(infrared_signal_is_raw(signal)) {
|
||||
const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
|
||||
infrared_text_store_set(
|
||||
infrared,
|
||||
0,
|
||||
"%s\nRAW\n%zu samples",
|
||||
infrared_remote_get_signal_name(remote, current_button_index),
|
||||
"%s\nRAW\n%ld samples",
|
||||
infrared_remote_button_get_name(current_button),
|
||||
raw->timings_size);
|
||||
|
||||
} else {
|
||||
const InfraredMessage* message = infrared_signal_get_message(infrared->current_signal);
|
||||
const InfraredMessage* message = infrared_signal_get_message(signal);
|
||||
infrared_text_store_set(
|
||||
infrared,
|
||||
0,
|
||||
"%s\n%s\nA=0x%0*lX C=0x%0*lX",
|
||||
infrared_remote_get_signal_name(remote, current_button_index),
|
||||
infrared_remote_button_get_name(current_button),
|
||||
infrared_get_protocol_name(message->protocol),
|
||||
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
|
||||
message->address,
|
||||
@@ -56,11 +49,11 @@ void infrared_scene_edit_delete_on_enter(void* context) {
|
||||
infrared_text_store_set(
|
||||
infrared,
|
||||
0,
|
||||
"%s\n with %zu buttons",
|
||||
"%s\n with %lu buttons",
|
||||
infrared_remote_get_name(remote),
|
||||
infrared_remote_get_signal_count(remote));
|
||||
infrared_remote_get_button_count(remote));
|
||||
} else {
|
||||
furi_crash();
|
||||
furi_assert(0);
|
||||
}
|
||||
|
||||
dialog_ex_set_text(dialog_ex, infrared->text_store[0], 64, 31, AlignCenter, AlignCenter);
|
||||
@@ -70,14 +63,11 @@ void infrared_scene_edit_delete_on_enter(void* context) {
|
||||
dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_delete_dialog_result_callback);
|
||||
dialog_ex_set_context(dialog_ex, context);
|
||||
|
||||
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal);
|
||||
view_stack_add_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex));
|
||||
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
|
||||
}
|
||||
|
||||
bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
@@ -93,24 +83,18 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event)
|
||||
|
||||
if(edit_target == InfraredEditTargetButton) {
|
||||
furi_assert(app_state->current_button_index != InfraredButtonIndexNone);
|
||||
infrared_show_loading_popup(infrared, true);
|
||||
success = infrared_remote_delete_signal(remote, app_state->current_button_index);
|
||||
infrared_show_loading_popup(infrared, false);
|
||||
success = infrared_remote_delete_button(remote, app_state->current_button_index);
|
||||
app_state->current_button_index = InfraredButtonIndexNone;
|
||||
} else if(edit_target == InfraredEditTargetRemote) {
|
||||
success = infrared_remote_remove(remote);
|
||||
app_state->current_button_index = InfraredButtonIndexNone;
|
||||
} else {
|
||||
furi_crash();
|
||||
furi_assert(0);
|
||||
}
|
||||
|
||||
if(success) {
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone);
|
||||
} else {
|
||||
infrared_show_error_message(
|
||||
infrared,
|
||||
"Failed to\ndelete %s",
|
||||
edit_target == InfraredEditTargetButton ? "button" : "file");
|
||||
const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
|
||||
scene_manager_search_and_switch_to_previous_scene_one_of(
|
||||
scene_manager, possible_scenes, COUNT_OF(possible_scenes));
|
||||
@@ -123,6 +107,6 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event)
|
||||
}
|
||||
|
||||
void infrared_scene_edit_delete_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
view_stack_remove_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex));
|
||||
Infrared* infrared = context;
|
||||
UNUSED(infrared);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include "../infrared_i.h"
|
||||
|
||||
void infrared_scene_edit_delete_done_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
Popup* popup = infrared->popup;
|
||||
|
||||
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
|
||||
@@ -16,7 +16,7 @@ void infrared_scene_edit_delete_done_on_enter(void* context) {
|
||||
}
|
||||
|
||||
bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
@@ -33,7 +33,7 @@ bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent e
|
||||
view_dispatcher_stop(infrared->view_dispatcher);
|
||||
}
|
||||
} else {
|
||||
furi_crash();
|
||||
furi_assert(0);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
@@ -43,6 +43,6 @@ bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent e
|
||||
}
|
||||
|
||||
void infrared_scene_edit_delete_done_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
UNUSED(infrared);
|
||||
}
|
||||
|
||||
@@ -1,69 +1,44 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include "../infrared_i.h"
|
||||
|
||||
static void infrared_scene_edit_move_button_callback(
|
||||
uint32_t index_old,
|
||||
uint32_t index_new,
|
||||
void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
furi_assert(infrared);
|
||||
static void infrared_scene_move_button(uint32_t index_old, uint32_t index_new, void* context) {
|
||||
InfraredRemote* remote = context;
|
||||
furi_assert(remote);
|
||||
infrared_remote_move_button(remote, index_old, index_new);
|
||||
}
|
||||
|
||||
infrared->app_state.prev_button_index = index_old;
|
||||
infrared->app_state.current_button_index = index_new;
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeButtonSelected);
|
||||
static const char* infrared_scene_get_btn_name(uint32_t index, void* context) {
|
||||
InfraredRemote* remote = context;
|
||||
furi_assert(remote);
|
||||
InfraredRemoteButton* button = infrared_remote_get_button(remote, index);
|
||||
return (infrared_remote_button_get_name(button));
|
||||
}
|
||||
|
||||
void infrared_scene_edit_move_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
InfraredRemote* remote = infrared->remote;
|
||||
|
||||
for(size_t i = 0; i < infrared_remote_get_signal_count(remote); ++i) {
|
||||
infrared_move_view_add_item(
|
||||
infrared->move_view, infrared_remote_get_signal_name(remote, i));
|
||||
}
|
||||
infrared_move_view_set_callback(infrared->move_view, infrared_scene_move_button);
|
||||
|
||||
infrared_move_view_set_callback(
|
||||
infrared->move_view, infrared_scene_edit_move_button_callback, infrared);
|
||||
uint32_t btn_count = infrared_remote_get_button_count(remote);
|
||||
infrared_move_view_list_init(
|
||||
infrared->move_view, btn_count, infrared_scene_get_btn_name, remote);
|
||||
infrared_move_view_list_update(infrared->move_view);
|
||||
|
||||
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal);
|
||||
view_stack_add_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view));
|
||||
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove);
|
||||
}
|
||||
|
||||
bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == InfraredCustomEventTypeButtonSelected) {
|
||||
infrared_show_loading_popup(infrared, true);
|
||||
const bool button_moved = infrared_remote_move_signal(
|
||||
infrared->remote,
|
||||
infrared->app_state.prev_button_index,
|
||||
infrared->app_state.current_button_index);
|
||||
infrared_show_loading_popup(infrared, false);
|
||||
|
||||
if(!button_moved) {
|
||||
infrared_show_error_message(
|
||||
infrared,
|
||||
"Failed to move\n\"%s\"",
|
||||
infrared_remote_get_signal_name(
|
||||
infrared->remote, infrared->app_state.current_button_index));
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
infrared->scene_manager, InfraredSceneRemoteList);
|
||||
}
|
||||
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
UNUSED(event);
|
||||
UNUSED(infrared);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void infrared_scene_edit_move_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
view_stack_remove_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view));
|
||||
infrared_move_view_reset(infrared->move_view);
|
||||
Infrared* infrared = context;
|
||||
InfraredRemote* remote = infrared->remote;
|
||||
infrared_remote_store(remote);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include "../infrared_i.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <toolbox/path.h>
|
||||
|
||||
void infrared_scene_edit_rename_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
InfraredRemote* remote = infrared->remote;
|
||||
TextInput* text_input = infrared->text_input;
|
||||
size_t enter_name_length = 0;
|
||||
@@ -14,12 +14,14 @@ void infrared_scene_edit_rename_on_enter(void* context) {
|
||||
text_input_set_header_text(text_input, "Name the button");
|
||||
|
||||
const int32_t current_button_index = infrared->app_state.current_button_index;
|
||||
furi_check(current_button_index != InfraredButtonIndexNone);
|
||||
furi_assert(current_button_index != InfraredButtonIndexNone);
|
||||
|
||||
InfraredRemoteButton* current_button =
|
||||
infrared_remote_get_button(remote, current_button_index);
|
||||
enter_name_length = INFRARED_MAX_BUTTON_NAME_LENGTH;
|
||||
strncpy(
|
||||
infrared->text_store[0],
|
||||
infrared_remote_get_signal_name(remote, current_button_index),
|
||||
infrared_remote_button_get_name(current_button),
|
||||
enter_name_length);
|
||||
|
||||
} else if(edit_target == InfraredEditTargetRemote) {
|
||||
@@ -42,7 +44,7 @@ void infrared_scene_edit_rename_on_enter(void* context) {
|
||||
|
||||
furi_string_free(folder_path);
|
||||
} else {
|
||||
furi_crash();
|
||||
furi_assert(0);
|
||||
}
|
||||
|
||||
text_input_set_result_callback(
|
||||
@@ -53,14 +55,11 @@ void infrared_scene_edit_rename_on_enter(void* context) {
|
||||
enter_name_length,
|
||||
false);
|
||||
|
||||
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal);
|
||||
view_stack_add_view(infrared->view_stack, text_input_get_view(infrared->text_input));
|
||||
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput);
|
||||
}
|
||||
|
||||
bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
InfraredRemote* remote = infrared->remote;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
InfraredAppState* app_state = &infrared->app_state;
|
||||
@@ -73,24 +72,18 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event)
|
||||
if(edit_target == InfraredEditTargetButton) {
|
||||
const int32_t current_button_index = app_state->current_button_index;
|
||||
furi_assert(current_button_index != InfraredButtonIndexNone);
|
||||
infrared_show_loading_popup(infrared, true);
|
||||
success = infrared_remote_rename_signal(
|
||||
remote, current_button_index, infrared->text_store[0]);
|
||||
infrared_show_loading_popup(infrared, false);
|
||||
success = infrared_remote_rename_button(
|
||||
remote, infrared->text_store[0], current_button_index);
|
||||
app_state->current_button_index = InfraredButtonIndexNone;
|
||||
} else if(edit_target == InfraredEditTargetRemote) {
|
||||
success = infrared_rename_current_remote(infrared, infrared->text_store[0]);
|
||||
} else {
|
||||
furi_crash();
|
||||
furi_assert(0);
|
||||
}
|
||||
|
||||
if(success) {
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone);
|
||||
} else {
|
||||
infrared_show_error_message(
|
||||
infrared,
|
||||
"Failed to\nrename %s",
|
||||
edit_target == InfraredEditTargetButton ? "button" : "file");
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
scene_manager, InfraredSceneRemoteList);
|
||||
}
|
||||
@@ -102,11 +95,9 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event)
|
||||
}
|
||||
|
||||
void infrared_scene_edit_rename_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
TextInput* text_input = infrared->text_input;
|
||||
|
||||
view_stack_remove_view(infrared->view_stack, text_input_get_view(text_input));
|
||||
|
||||
void* validator_context = text_input_get_validator_callback_context(text_input);
|
||||
text_input_set_validator(text_input, NULL, NULL);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include "../infrared_i.h"
|
||||
|
||||
void infrared_scene_edit_rename_done_on_enter(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
Popup* popup = infrared->popup;
|
||||
|
||||
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
|
||||
@@ -16,7 +16,7 @@ void infrared_scene_edit_rename_done_on_enter(void* context) {
|
||||
}
|
||||
|
||||
bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
@@ -33,6 +33,6 @@ bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent e
|
||||
}
|
||||
|
||||
void infrared_scene_edit_rename_done_on_exit(void* context) {
|
||||
InfraredApp* infrared = context;
|
||||
Infrared* infrared = context;
|
||||
UNUSED(infrared);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user