From 2a105e42668e9c065127a61d1e22bc95e5c80f46 Mon Sep 17 00:00:00 2001 From: MatthewKuKanich Date: Fri, 8 Mar 2024 13:37:55 -0500 Subject: [PATCH 01/40] fix placeholders and beacon stop on exit --- applications/system/findmy/findmy.c | 34 +++++++++++++++---- .../system/findmy/scenes/findmy_scene_main.c | 1 + 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/applications/system/findmy/findmy.c b/applications/system/findmy/findmy.c index 2722f32ab7..47b9f29364 100644 --- a/applications/system/findmy/findmy.c +++ b/applications/system/findmy/findmy.c @@ -83,7 +83,7 @@ static void findmy_start(FindMy* app) { app->config.adv_power_level = GapAdvPowerLevel_0dBm + app->transmit_power; app->config.address_type = GapAddressTypePublic; - uint8_t mac[EXTRA_BEACON_MAC_ADDR_SIZE] = {0x4D, 0x61, 0x74, 0x4B, 0x75, 0x4B}; + uint8_t mac[EXTRA_BEACON_MAC_ADDR_SIZE] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; furi_hal_bt_reverse_mac_addr(mac); memcpy(&app->config.address, mac, sizeof(app->config.address)); furi_check(furi_hal_bt_extra_beacon_set_config(&app->config)); @@ -92,15 +92,37 @@ static void findmy_start(FindMy* app) { uint8_t* it = data; // For Apple AirTags - *it++ = 0x1E; // Length + *it++ = 0x1E, // Length *it++ = 0xFF; // Manufacturer Specific Data *it++ = 0x4C; // Company ID (Apple, Inc.) *it++ = 0x00; // State *it++ = 0x12; // Data - Public Key without the MAC address - *it++ = 0x81; // ... - *it++ = 0xB9; // ... - *it++ = 0x02; // First 2 bits are the version, the rest is the battery level - *it++ = 0x7E; // Hint (0x00) + *it++ = 0x19; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; + *it++ = 0x00; // First 2 bits are the version, the rest is the battery level + *it++ = 0x00; // Hint (0x00) furi_check(furi_hal_bt_extra_beacon_set_data(data, it - data)); } diff --git a/applications/system/findmy/scenes/findmy_scene_main.c b/applications/system/findmy/scenes/findmy_scene_main.c index 5738370cf8..923dfafd6b 100644 --- a/applications/system/findmy/scenes/findmy_scene_main.c +++ b/applications/system/findmy/scenes/findmy_scene_main.c @@ -51,4 +51,5 @@ bool findmy_scene_main_on_event(void* context, SceneManagerEvent event) { void findmy_scene_main_on_exit(void* context) { FindMy* app = context; UNUSED(app); + furi_hal_bt_extra_beacon_stop(); } \ No newline at end of file From aa15ee6fbc737d4fdab5f5482bda5dd8e3ca78dc Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 8 Mar 2024 18:55:29 +0000 Subject: [PATCH 02/40] FindMy: Disable beacon correctly at exit, not in config --- applications/system/findmy/scenes/findmy_scene_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/findmy/scenes/findmy_scene_main.c b/applications/system/findmy/scenes/findmy_scene_main.c index 923dfafd6b..40e22d9420 100644 --- a/applications/system/findmy/scenes/findmy_scene_main.c +++ b/applications/system/findmy/scenes/findmy_scene_main.c @@ -38,6 +38,7 @@ bool findmy_scene_main_on_event(void* context, SceneManagerEvent event) { findmy_change_broadcast_interval(app, app->broadcast_interval - 1); break; case FindMyMainEventQuit: + furi_hal_bt_extra_beacon_stop(); break; default: consumed = false; @@ -51,5 +52,4 @@ bool findmy_scene_main_on_event(void* context, SceneManagerEvent event) { void findmy_scene_main_on_exit(void* context) { FindMy* app = context; UNUSED(app); - furi_hal_bt_extra_beacon_stop(); } \ No newline at end of file From 1941da7fd62b7e57bfef1b057f44e0a64b8ca6cc Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 8 Mar 2024 18:59:13 +0000 Subject: [PATCH 03/40] Format + cleanup --- applications/system/findmy/findmy.c | 2 +- .../findmy/scenes/findmy_scene_config.c | 7 +++++- .../findmy/scenes/findmy_scene_config_mac.c | 1 - .../scenes/findmy_scene_config_packet.c | 5 +++-- .../system/findmy/views/findmy_main.c | 22 ++++++++++--------- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/applications/system/findmy/findmy.c b/applications/system/findmy/findmy.c index 47b9f29364..6ccade2bf3 100644 --- a/applications/system/findmy/findmy.c +++ b/applications/system/findmy/findmy.c @@ -92,7 +92,7 @@ static void findmy_start(FindMy* app) { uint8_t* it = data; // For Apple AirTags - *it++ = 0x1E, // Length + *it++ = 0x1E; // Length *it++ = 0xFF; // Manufacturer Specific Data *it++ = 0x4C; // Company ID (Apple, Inc.) *it++ = 0x00; // State diff --git a/applications/system/findmy/scenes/findmy_scene_config.c b/applications/system/findmy/scenes/findmy_scene_config.c index 8f16d7b190..bf961a5f72 100644 --- a/applications/system/findmy/scenes/findmy_scene_config.c +++ b/applications/system/findmy/scenes/findmy_scene_config.c @@ -58,7 +58,12 @@ void findmy_scene_config_on_enter(void* context) { variable_item_set_current_value_text(item, transmit_power_s); item = variable_item_list_add(var_item_list, "Register Tag", 0, NULL, NULL); - item = variable_item_list_add(var_item_list, "Matthew KuKanich, Thanks to Chapoly1305, WillyJL, OpenHaystack, Testers", 1, NULL, NULL); + item = variable_item_list_add( + var_item_list, + "Matthew KuKanich, Thanks to Chapoly1305, WillyJL, OpenHaystack, Testers", + 1, + NULL, + NULL); variable_item_set_current_value_text(item, "Credits"); variable_item_list_set_enter_callback(var_item_list, findmy_scene_config_callback, app); diff --git a/applications/system/findmy/scenes/findmy_scene_config_mac.c b/applications/system/findmy/scenes/findmy_scene_config_mac.c index c86e3dc1e7..0a5ed1392e 100644 --- a/applications/system/findmy/scenes/findmy_scene_config_mac.c +++ b/applications/system/findmy/scenes/findmy_scene_config_mac.c @@ -56,5 +56,4 @@ void findmy_scene_config_mac_on_exit(void* context) { byte_input_set_result_callback(app->byte_input, NULL, NULL, NULL, NULL, 0); byte_input_set_header_text(app->byte_input, ""); - } \ No newline at end of file diff --git a/applications/system/findmy/scenes/findmy_scene_config_packet.c b/applications/system/findmy/scenes/findmy_scene_config_packet.c index 4dddf6c02b..f431445d58 100644 --- a/applications/system/findmy/scenes/findmy_scene_config_packet.c +++ b/applications/system/findmy/scenes/findmy_scene_config_packet.c @@ -40,8 +40,9 @@ bool findmy_scene_config_packet_on_event(void* context, SceneManagerEvent event) case ByteInputResultOk: scene_manager_search_and_switch_to_previous_scene( app->scene_manager, FindMySceneConfig); - furi_check(furi_hal_bt_extra_beacon_set_data(app->packet_buf, sizeof(app->packet_buf))); - if (app->packet_buf[0] == 0x1E && app->packet_buf[3] == 0x00) { + furi_check( + furi_hal_bt_extra_beacon_set_data(app->packet_buf, sizeof(app->packet_buf))); + if(app->packet_buf[0] == 0x1E && app->packet_buf[3] == 0x00) { app->apple = true; // Checks payload data for Apple identifier } else { app->apple = false; diff --git a/applications/system/findmy/views/findmy_main.c b/applications/system/findmy/views/findmy_main.c index f7b2485462..553a8d87e9 100644 --- a/applications/system/findmy/views/findmy_main.c +++ b/applications/system/findmy/views/findmy_main.c @@ -34,7 +34,7 @@ static void findmy_main_draw_callback(Canvas* canvas, void* _model) { snprintf(interval_str, sizeof(interval_str), "Ping Interval: %ds", model->interval); canvas_draw_str(canvas, 4, 62, interval_str); canvas_set_font(canvas, FontPrimary); - if(model->apple){ + if(model->apple) { canvas_draw_str(canvas, 4, 32, "Apple Network"); canvas_draw_icon(canvas, 80, 24, &I_Lock_7x8); } else { @@ -56,30 +56,32 @@ static bool findmy_main_input_callback(InputEvent* event, void* context) { if(event->type == InputTypePress) { consumed = true; - // FIXME: finish implementing handlers in scene side + FindMyMainEvent event; + switch(event->key) { case InputKeyBack: - findmy_main->callback(FindMyMainEventQuit, findmy_main->context); - // furi_hal_bt_extra_beacon_stop(); + event = FindMyMainEventQuit; break; case InputKeyOk: - findmy_main->callback(FindMyMainEventToggle, findmy_main->context); + event = FindMyMainEventToggle; break; case InputKeyLeft: - findmy_main->callback(FindMyMainEventBackground, findmy_main->context); + event = FindMyMainEventBackground; break; case InputKeyRight: - findmy_main->callback(FindMyMainEventConfig, findmy_main->context); + event = FindMyMainEventConfig; break; case InputKeyUp: - findmy_main->callback(FindMyMainEventIntervalUp, findmy_main->context); + event = FindMyMainEventIntervalUp; break; case InputKeyDown: - findmy_main->callback(FindMyMainEventIntervalDown, findmy_main->context); + event = FindMyMainEventIntervalDown; break; default: - break; + return consumed; } + + findmy_main->callback(event, findmy_main->context); } return consumed; From a8d18fa02aa1f188589fde8221bc6ef4a1c94f0b Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:04:47 +0000 Subject: [PATCH 04/40] Cleanup default packet template and fix comments --- applications/system/findmy/findmy.c | 34 +++++++---------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/applications/system/findmy/findmy.c b/applications/system/findmy/findmy.c index 6ccade2bf3..ed7d472f08 100644 --- a/applications/system/findmy/findmy.c +++ b/applications/system/findmy/findmy.c @@ -95,32 +95,14 @@ static void findmy_start(FindMy* app) { *it++ = 0x1E; // Length *it++ = 0xFF; // Manufacturer Specific Data *it++ = 0x4C; // Company ID (Apple, Inc.) - *it++ = 0x00; // State - *it++ = 0x12; // Data - Public Key without the MAC address - *it++ = 0x19; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; - *it++ = 0x00; + *it++ = 0x00; // ... + *it++ = 0x12; // Type (FindMy) + *it++ = 0x19; // Length + *it++ = 0x00; // Status + // Placeholder Empty Public Key without the MAC address + for(size_t i = 0; i < 22; ++i) { + *it++ = 0x00; + } *it++ = 0x00; // First 2 bits are the version, the rest is the battery level *it++ = 0x00; // Hint (0x00) From 09af215e75a47b998e4704f02260974f5d082bec Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:10:08 +0000 Subject: [PATCH 05/40] Oops --- applications/system/findmy/views/findmy_main.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/applications/system/findmy/views/findmy_main.c b/applications/system/findmy/views/findmy_main.c index 553a8d87e9..6ac55d2618 100644 --- a/applications/system/findmy/views/findmy_main.c +++ b/applications/system/findmy/views/findmy_main.c @@ -56,32 +56,32 @@ static bool findmy_main_input_callback(InputEvent* event, void* context) { if(event->type == InputTypePress) { consumed = true; - FindMyMainEvent event; + FindMyMainEvent cb_event; switch(event->key) { case InputKeyBack: - event = FindMyMainEventQuit; + cb_event = FindMyMainEventQuit; break; case InputKeyOk: - event = FindMyMainEventToggle; + cb_event = FindMyMainEventToggle; break; case InputKeyLeft: - event = FindMyMainEventBackground; + cb_event = FindMyMainEventBackground; break; case InputKeyRight: - event = FindMyMainEventConfig; + cb_event = FindMyMainEventConfig; break; case InputKeyUp: - event = FindMyMainEventIntervalUp; + cb_event = FindMyMainEventIntervalUp; break; case InputKeyDown: - event = FindMyMainEventIntervalDown; + cb_event = FindMyMainEventIntervalDown; break; default: return consumed; } - findmy_main->callback(event, findmy_main->context); + findmy_main->callback(cb_event, findmy_main->context); } return consumed; From 7c48c6164aeb242145cdfff385305e57bd3ec17f Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:53:00 +0000 Subject: [PATCH 06/40] FindMy: Refactors and improvements - state handled reparately, decoupled from app - better apple/samsung/unknown parsing and handling - improve error handling - add url to manifest + up stack size just in case --- applications/system/findmy/application.fam | 4 +- applications/system/findmy/findmy.c | 100 +++++++----------- applications/system/findmy/findmy.h | 2 + applications/system/findmy/findmy_i.h | 13 ++- applications/system/findmy/findmy_state.c | 61 +++++++++++ applications/system/findmy/findmy_state.h | 17 +++ .../findmy/scenes/findmy_scene_config.c | 24 ++--- .../findmy/scenes/findmy_scene_config_mac.c | 13 ++- .../scenes/findmy_scene_config_packet.c | 13 +-- .../system/findmy/scenes/findmy_scene_main.c | 12 ++- .../system/findmy/views/findmy_main.c | 21 ++-- .../system/findmy/views/findmy_main.h | 2 +- 12 files changed, 176 insertions(+), 106 deletions(-) create mode 100644 applications/system/findmy/findmy_state.c create mode 100644 applications/system/findmy/findmy_state.h diff --git a/applications/system/findmy/application.fam b/applications/system/findmy/application.fam index 0b6006923c..9a7b2df949 100644 --- a/applications/system/findmy/application.fam +++ b/applications/system/findmy/application.fam @@ -4,11 +4,11 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="findmy_main", requires=["gui"], - stack_size=1 * 1024, - order=35, + stack_size=2 * 1024, fap_icon="location_icon.png", fap_category="Bluetooth", fap_author="@MatthewKuKanich", + fap_weburl="https://github.com/MatthewKuKanich/FindMyFlipper", fap_version="1.0", fap_description="BLE FindMy Location Beacon", ) diff --git a/applications/system/findmy/findmy.c b/applications/system/findmy/findmy.c index ed7d472f08..10019347fd 100644 --- a/applications/system/findmy/findmy.c +++ b/applications/system/findmy/findmy.c @@ -43,13 +43,12 @@ static FindMy* findmy_app_alloc() { view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - app->beacon_active = false; - findmy_main_update_active(app->findmy_main, app->beacon_active); - app->broadcast_interval = 5; - findmy_main_update_interval(app->findmy_main, app->broadcast_interval); - app->transmit_power = 6; - app->apple = true; - findmy_main_update_apple(app->findmy_main, app->apple); + findmy_state_load(&app->state); + findmy_state_apply(&app->state); + + findmy_main_update_active(app->findmy_main, furi_hal_bt_extra_beacon_is_active()); + findmy_main_update_interval(app->findmy_main, app->state.broadcast_interval); + findmy_main_update_type(app->findmy_main, findmy_data_get_type(app->state.data)); return app; } @@ -74,47 +73,10 @@ static void findmy_app_free(FindMy* app) { free(app); } -static void findmy_start(FindMy* app) { - furi_hal_bt_extra_beacon_stop(); // Stop any running beacon - - app->config.min_adv_interval_ms = app->broadcast_interval * 1000; // Converting s to ms - app->config.max_adv_interval_ms = (app->broadcast_interval * 1000) + 150; - app->config.adv_channel_map = GapAdvChannelMapAll; - app->config.adv_power_level = GapAdvPowerLevel_0dBm + app->transmit_power; - app->config.address_type = GapAddressTypePublic; - - uint8_t mac[EXTRA_BEACON_MAC_ADDR_SIZE] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; - furi_hal_bt_reverse_mac_addr(mac); - memcpy(&app->config.address, mac, sizeof(app->config.address)); - furi_check(furi_hal_bt_extra_beacon_set_config(&app->config)); - - uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]; - uint8_t* it = data; - - // For Apple AirTags - *it++ = 0x1E; // Length - *it++ = 0xFF; // Manufacturer Specific Data - *it++ = 0x4C; // Company ID (Apple, Inc.) - *it++ = 0x00; // ... - *it++ = 0x12; // Type (FindMy) - *it++ = 0x19; // Length - *it++ = 0x00; // Status - // Placeholder Empty Public Key without the MAC address - for(size_t i = 0; i < 22; ++i) { - *it++ = 0x00; - } - *it++ = 0x00; // First 2 bits are the version, the rest is the battery level - *it++ = 0x00; // Hint (0x00) - - furi_check(furi_hal_bt_extra_beacon_set_data(data, it - data)); -} - int32_t findmy_main(void* p) { UNUSED(p); FindMy* app = findmy_app_alloc(); - findmy_start(app); - scene_manager_next_scene(app->scene_manager, FindMySceneMain); view_dispatcher_run(app->view_dispatcher); @@ -127,16 +89,16 @@ void findmy_change_broadcast_interval(FindMy* app, uint8_t value) { if(value > 10 || value < 1) { return; } - app->broadcast_interval = value; - findmy_main_update_interval(app->findmy_main, app->broadcast_interval); - if(app->beacon_active) { + app->state.broadcast_interval = value; + findmy_main_update_interval(app->findmy_main, app->state.broadcast_interval); + if(furi_hal_bt_extra_beacon_is_active()) { // Always check if beacon is active before changing config furi_check(furi_hal_bt_extra_beacon_stop()); } - app->config.min_adv_interval_ms = app->broadcast_interval * 1000; - app->config.max_adv_interval_ms = app->config.min_adv_interval_ms + 150; - furi_check(furi_hal_bt_extra_beacon_set_config(&app->config)); - if(app->beacon_active) { + app->state.config.min_adv_interval_ms = app->state.broadcast_interval * 1000; + app->state.config.max_adv_interval_ms = app->state.config.min_adv_interval_ms + 150; + furi_check(furi_hal_bt_extra_beacon_set_config(&app->state.config)); + if(app->state.beacon_active) { furi_check(furi_hal_bt_extra_beacon_start()); } } @@ -145,24 +107,38 @@ void findmy_change_transmit_power(FindMy* app, uint8_t value) { if(value > 6) { return; } - app->transmit_power = value; - if(app->beacon_active) { + app->state.transmit_power = value; + if(furi_hal_bt_extra_beacon_is_active()) { furi_check(furi_hal_bt_extra_beacon_stop()); } - app->config.adv_power_level = GapAdvPowerLevel_0dBm + app->transmit_power; - furi_check(furi_hal_bt_extra_beacon_set_config(&app->config)); - if(app->beacon_active) { + app->state.config.adv_power_level = GapAdvPowerLevel_0dBm + app->state.transmit_power; + furi_check(furi_hal_bt_extra_beacon_set_config(&app->state.config)); + if(app->state.beacon_active) { furi_check(furi_hal_bt_extra_beacon_start()); } } void findmy_toggle_beacon(FindMy* app) { - app->beacon_active = !app->beacon_active; - findmy_main_update_active(app->findmy_main, app->beacon_active); - findmy_main_update_apple(app->findmy_main, app->apple); - if(app->beacon_active) { - furi_hal_bt_extra_beacon_start(); + app->state.beacon_active = !app->state.beacon_active; + if(furi_hal_bt_extra_beacon_is_active()) { + furi_check(furi_hal_bt_extra_beacon_stop()); + } + if(app->state.beacon_active) { + furi_check(furi_hal_bt_extra_beacon_start()); + } + findmy_main_update_active(app->findmy_main, furi_hal_bt_extra_beacon_is_active()); +} + +FindMyType findmy_data_get_type(uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]) { + if(data[0] == 0x1E && // Length + data[1] == 0xFF && // Manufacturer Specific Data + data[2] == 0x4C && // Company ID (Apple, Inc.) + data[3] == 0x00 && // ... + data[4] == 0x12 && // Type (FindMy) + data[5] == 0x19 // Length + ) { + return FindMyTypeApple; } else { - furi_hal_bt_extra_beacon_stop(); + return FindMyTypeSamsung; } } \ No newline at end of file diff --git a/applications/system/findmy/findmy.h b/applications/system/findmy/findmy.h index 344882ef10..a5135f2d70 100644 --- a/applications/system/findmy/findmy.h +++ b/applications/system/findmy/findmy.h @@ -1,3 +1,5 @@ #pragma once typedef struct FindMy FindMy; + +typedef enum FindMyType FindMyType; \ No newline at end of file diff --git a/applications/system/findmy/findmy_i.h b/applications/system/findmy/findmy_i.h index fd09e5324a..c5991b5f47 100644 --- a/applications/system/findmy/findmy_i.h +++ b/applications/system/findmy/findmy_i.h @@ -1,6 +1,7 @@ #pragma once #include "findmy.h" +#include "findmy_state.h" #include #include #include @@ -24,11 +25,7 @@ struct FindMy { uint8_t mac_buf[EXTRA_BEACON_MAC_ADDR_SIZE]; uint8_t packet_buf[EXTRA_BEACON_MAX_DATA_SIZE]; - GapExtraBeaconConfig config; - bool apple; - bool beacon_active; - uint8_t broadcast_interval; - uint8_t transmit_power; + FindMyState state; }; typedef enum { @@ -37,6 +34,12 @@ typedef enum { FindMyViewVarItemList, } FindMyView; +enum FindMyType { + FindMyTypeApple, + FindMyTypeSamsung, +}; + void findmy_change_broadcast_interval(FindMy* app, uint8_t value); void findmy_change_transmit_power(FindMy* app, uint8_t value); void findmy_toggle_beacon(FindMy* app); +FindMyType findmy_data_get_type(uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]); diff --git a/applications/system/findmy/findmy_state.c b/applications/system/findmy/findmy_state.c new file mode 100644 index 0000000000..34e9c80b22 --- /dev/null +++ b/applications/system/findmy/findmy_state.c @@ -0,0 +1,61 @@ +#include "findmy_state.h" + +#include +#include +#include + +bool findmy_state_load(FindMyState* out_state) { + FindMyState state; + + // Set default values + state.beacon_active = false; + state.broadcast_interval = 5; + state.transmit_power = 6; + state.config.min_adv_interval_ms = state.broadcast_interval * 1000; // Converting s to ms + state.config.max_adv_interval_ms = (state.broadcast_interval * 1000) + 150; + state.config.adv_channel_map = GapAdvChannelMapAll; + state.config.adv_power_level = GapAdvPowerLevel_0dBm + state.transmit_power; + state.config.address_type = GapAddressTypePublic; + + // Set default mac + uint8_t default_mac[EXTRA_BEACON_MAC_ADDR_SIZE] = {0x66, 0x55, 0x44, 0x33, 0x22, 0x11}; + memcpy(state.mac, default_mac, sizeof(state.mac)); + memcpy(state.config.address, default_mac, sizeof(state.config.address)); + + // Set default empty AirTag data + uint8_t* data = state.data; + *data++ = 0x1E; // Length + *data++ = 0xFF; // Manufacturer Specific Data + *data++ = 0x4C; // Company ID (Apple, Inc.) + *data++ = 0x00; // ... + *data++ = 0x12; // Type (FindMy) + *data++ = 0x19; // Length + *data++ = 0x00; // Status + // Placeholder Empty Public Key without the MAC address + for(size_t i = 0; i < 22; ++i) { + *data++ = 0x00; + } + *data++ = 0x00; // First 2 bits are the version, the rest is the battery level + *data++ = 0x00; // Hint (0x00) + + // Copy to caller state before popping stack + memcpy(out_state, &state, sizeof(state)); + + // Return if active, can be used to start after loading in an if statement + return state.beacon_active; +} + +void findmy_state_apply(FindMyState* state) { + // Stop any running beacon + if(furi_hal_bt_extra_beacon_is_active()) { + furi_check(furi_hal_bt_extra_beacon_stop()); + } + + furi_check(furi_hal_bt_extra_beacon_set_config(&state->config)); + + furi_check(furi_hal_bt_extra_beacon_set_data(state->data, sizeof(state->data))); + + if(state->beacon_active) { + furi_check(furi_hal_bt_extra_beacon_start()); + } +} diff --git a/applications/system/findmy/findmy_state.h b/applications/system/findmy/findmy_state.h new file mode 100644 index 0000000000..48a82fd2e4 --- /dev/null +++ b/applications/system/findmy/findmy_state.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +typedef struct { + uint8_t mac[EXTRA_BEACON_MAC_ADDR_SIZE]; + uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]; + GapExtraBeaconConfig config; + + bool beacon_active; + uint8_t broadcast_interval; + uint8_t transmit_power; +} FindMyState; + +bool findmy_state_load(FindMyState* out_state); + +void findmy_state_apply(FindMyState* state); diff --git a/applications/system/findmy/scenes/findmy_scene_config.c b/applications/system/findmy/scenes/findmy_scene_config.c index bf961a5f72..415bda9453 100644 --- a/applications/system/findmy/scenes/findmy_scene_config.c +++ b/applications/system/findmy/scenes/findmy_scene_config.c @@ -12,9 +12,9 @@ void findmy_scene_config_broadcast_interval_changed(VariableItem* item) { uint8_t index = variable_item_get_current_value_index(item); findmy_change_broadcast_interval(app, index + 1); char str[5]; - snprintf(str, sizeof(str), "%ds", app->broadcast_interval); + snprintf(str, sizeof(str), "%ds", app->state.broadcast_interval); variable_item_set_current_value_text(item, str); - variable_item_set_current_value_index(item, app->broadcast_interval - 1); + variable_item_set_current_value_index(item, app->state.broadcast_interval - 1); } void findmy_scene_config_transmit_power_changed(VariableItem* item) { @@ -22,9 +22,9 @@ void findmy_scene_config_transmit_power_changed(VariableItem* item) { uint8_t index = variable_item_get_current_value_index(item); findmy_change_transmit_power(app, index); char str[7]; - snprintf(str, sizeof(str), "%ddBm", app->transmit_power); + snprintf(str, sizeof(str), "%ddBm", app->state.transmit_power); variable_item_set_current_value_text(item, str); - variable_item_set_current_value_index(item, app->transmit_power); + variable_item_set_current_value_index(item, app->state.transmit_power); } void findmy_scene_config_callback(void* context, uint32_t index) { @@ -45,17 +45,17 @@ void findmy_scene_config_on_enter(void* context) { findmy_scene_config_broadcast_interval_changed, app); // Broadcast Interval is 1-10, so use 0-9 and offset indexes by 1 - variable_item_set_current_value_index(item, app->broadcast_interval - 1); - char broadcast_interval_s[5]; - snprintf(broadcast_interval_s, sizeof(broadcast_interval_s), "%ds", app->broadcast_interval); - variable_item_set_current_value_text(item, broadcast_interval_s); + variable_item_set_current_value_index(item, app->state.broadcast_interval - 1); + char interval_str[5]; + snprintf(interval_str, sizeof(interval_str), "%ds", app->state.broadcast_interval); + variable_item_set_current_value_text(item, interval_str); item = variable_item_list_add( var_item_list, "Transmit Power", 7, findmy_scene_config_transmit_power_changed, app); - variable_item_set_current_value_index(item, app->transmit_power); - char transmit_power_s[7]; - snprintf(transmit_power_s, sizeof(transmit_power_s), "%ddBm", app->transmit_power); - variable_item_set_current_value_text(item, transmit_power_s); + variable_item_set_current_value_index(item, app->state.transmit_power); + char power_str[7]; + snprintf(power_str, sizeof(power_str), "%ddBm", app->state.transmit_power); + variable_item_set_current_value_text(item, power_str); item = variable_item_list_add(var_item_list, "Register Tag", 0, NULL, NULL); item = variable_item_list_add( diff --git a/applications/system/findmy/scenes/findmy_scene_config_mac.c b/applications/system/findmy/scenes/findmy_scene_config_mac.c index 0a5ed1392e..b42291a7f4 100644 --- a/applications/system/findmy/scenes/findmy_scene_config_mac.c +++ b/applications/system/findmy/scenes/findmy_scene_config_mac.c @@ -16,7 +16,7 @@ void findmy_scene_config_mac_on_enter(void* context) { byte_input_set_header_text(byte_input, "Enter Bluetooth MAC:"); - memcpy(app->mac_buf, &app->config.address, sizeof(app->mac_buf)); + memcpy(app->mac_buf, app->state.mac, sizeof(app->mac_buf)); furi_hal_bt_reverse_mac_addr(app->mac_buf); byte_input_set_result_callback( @@ -39,8 +39,15 @@ bool findmy_scene_config_mac_on_event(void* context, SceneManagerEvent event) { switch(event.event) { case ByteInputResultOk: furi_hal_bt_reverse_mac_addr(app->mac_buf); - memcpy(&app->config.address, app->mac_buf, sizeof(app->config.address)); - furi_hal_bt_extra_beacon_set_config(&app->config); + memcpy(&app->state.mac, app->mac_buf, sizeof(app->state.mac)); + memcpy(&app->state.config.address, app->mac_buf, sizeof(app->state.config.address)); + if(furi_hal_bt_extra_beacon_is_active()) { + furi_check(furi_hal_bt_extra_beacon_stop()); + } + furi_check(furi_hal_bt_extra_beacon_set_config(&app->state.config)); + if(app->state.beacon_active) { + furi_check(furi_hal_bt_extra_beacon_start()); + } scene_manager_next_scene(app->scene_manager, FindMySceneConfigPacket); break; default: diff --git a/applications/system/findmy/scenes/findmy_scene_config_packet.c b/applications/system/findmy/scenes/findmy_scene_config_packet.c index f431445d58..121bf58b58 100644 --- a/applications/system/findmy/scenes/findmy_scene_config_packet.c +++ b/applications/system/findmy/scenes/findmy_scene_config_packet.c @@ -16,8 +16,7 @@ void findmy_scene_config_packet_on_enter(void* context) { byte_input_set_header_text(byte_input, "Enter Bluetooth Payload:"); - memset(app->packet_buf, 0, sizeof(app->packet_buf)); - furi_hal_bt_extra_beacon_get_data(app->packet_buf); + memcpy(app->packet_buf, app->state.data, sizeof(app->packet_buf)); byte_input_set_result_callback( byte_input, @@ -40,14 +39,10 @@ bool findmy_scene_config_packet_on_event(void* context, SceneManagerEvent event) case ByteInputResultOk: scene_manager_search_and_switch_to_previous_scene( app->scene_manager, FindMySceneConfig); + memcpy(app->state.data, app->packet_buf, sizeof(app->state.data)); furi_check( - furi_hal_bt_extra_beacon_set_data(app->packet_buf, sizeof(app->packet_buf))); - if(app->packet_buf[0] == 0x1E && app->packet_buf[3] == 0x00) { - app->apple = true; // Checks payload data for Apple identifier - } else { - app->apple = false; - } - findmy_main_update_apple(app->findmy_main, app->apple); + furi_hal_bt_extra_beacon_set_data(app->state.data, sizeof(app->state.data))); + findmy_main_update_type(app->findmy_main, findmy_data_get_type(app->state.data)); break; default: break; diff --git a/applications/system/findmy/scenes/findmy_scene_main.c b/applications/system/findmy/scenes/findmy_scene_main.c index 40e22d9420..63932fb5c8 100644 --- a/applications/system/findmy/scenes/findmy_scene_main.c +++ b/applications/system/findmy/scenes/findmy_scene_main.c @@ -25,20 +25,24 @@ bool findmy_scene_main_on_event(void* context, SceneManagerEvent event) { findmy_toggle_beacon(app); break; case FindMyMainEventBackground: - furi_hal_bt_extra_beacon_start(); + if(!furi_hal_bt_extra_beacon_is_active()) { + furi_check(furi_hal_bt_extra_beacon_start()); + } view_dispatcher_stop(app->view_dispatcher); break; case FindMyMainEventConfig: scene_manager_next_scene(app->scene_manager, FindMySceneConfig); break; case FindMyMainEventIntervalUp: - findmy_change_broadcast_interval(app, app->broadcast_interval + 1); + findmy_change_broadcast_interval(app, app->state.broadcast_interval + 1); break; case FindMyMainEventIntervalDown: - findmy_change_broadcast_interval(app, app->broadcast_interval - 1); + findmy_change_broadcast_interval(app, app->state.broadcast_interval - 1); break; case FindMyMainEventQuit: - furi_hal_bt_extra_beacon_stop(); + if(furi_hal_bt_extra_beacon_is_active()) { + furi_check(furi_hal_bt_extra_beacon_stop()); + } break; default: consumed = false; diff --git a/applications/system/findmy/views/findmy_main.c b/applications/system/findmy/views/findmy_main.c index 6ac55d2618..b39adc2bf8 100644 --- a/applications/system/findmy/views/findmy_main.c +++ b/applications/system/findmy/views/findmy_main.c @@ -9,8 +9,8 @@ struct FindMyMain { typedef struct { bool active; - bool apple; uint8_t interval; + FindMyType type; } FindMyMainModel; static void findmy_main_draw_callback(Canvas* canvas, void* _model) { @@ -34,12 +34,17 @@ static void findmy_main_draw_callback(Canvas* canvas, void* _model) { snprintf(interval_str, sizeof(interval_str), "Ping Interval: %ds", model->interval); canvas_draw_str(canvas, 4, 62, interval_str); canvas_set_font(canvas, FontPrimary); - if(model->apple) { + switch(model->type) { + case FindMyTypeApple: canvas_draw_str(canvas, 4, 32, "Apple Network"); canvas_draw_icon(canvas, 80, 24, &I_Lock_7x8); - } else { + break; + case FindMyTypeSamsung: canvas_draw_str(canvas, 4, 32, "Samsung Network"); canvas_draw_icon(canvas, 97, 24, &I_Lock_7x8); + break; + default: + break; } canvas_set_font(canvas, FontSecondary); canvas_draw_str(canvas, 100, 61, "Config"); @@ -96,9 +101,9 @@ FindMyMain* findmy_main_alloc(FindMy* app) { findmy_main->view, FindMyMainModel * model, { - model->active = app->beacon_active; - model->apple = app->apple; - model->interval = app->broadcast_interval; + model->active = app->state.beacon_active; + model->interval = app->state.broadcast_interval; + model->type = findmy_data_get_type(app->state.data); }, false); view_set_context(findmy_main->view, findmy_main); @@ -138,8 +143,8 @@ void findmy_main_update_interval(FindMyMain* findmy_main, uint8_t interval) { findmy_main->view, FindMyMainModel * model, { model->interval = interval; }, true); } -void findmy_main_update_apple(FindMyMain* findmy_main, bool apple) { +void findmy_main_update_type(FindMyMain* findmy_main, FindMyType type) { furi_assert(findmy_main); with_view_model( - findmy_main->view, FindMyMainModel * model, { model->apple = apple; }, true); + findmy_main->view, FindMyMainModel * model, { model->type = type; }, true); } \ No newline at end of file diff --git a/applications/system/findmy/views/findmy_main.h b/applications/system/findmy/views/findmy_main.h index 977f574d71..92a9170ec2 100644 --- a/applications/system/findmy/views/findmy_main.h +++ b/applications/system/findmy/views/findmy_main.h @@ -26,4 +26,4 @@ void findmy_main_set_callback(FindMyMain* findmy_main, FindMyMainCallback callba // To redraw when info changes void findmy_main_update_active(FindMyMain* findmy_main, bool active); void findmy_main_update_interval(FindMyMain* findmy_main, uint8_t interval); -void findmy_main_update_apple(FindMyMain* findmy_main, bool apple); \ No newline at end of file +void findmy_main_update_type(FindMyMain* findmy_main, FindMyType type); \ No newline at end of file From 5d45d6abb8d8b598d226636605309df6d38c4c87 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 8 Mar 2024 21:35:59 +0000 Subject: [PATCH 07/40] FindMy: Save and load state from file --- applications/system/findmy/findmy.c | 3 + applications/system/findmy/findmy_state.c | 113 ++++++++++++++---- applications/system/findmy/findmy_state.h | 17 ++- .../findmy/scenes/findmy_scene_config_mac.c | 1 + .../scenes/findmy_scene_config_packet.c | 1 + .../system/findmy/scenes/findmy_scene_main.c | 4 + 6 files changed, 110 insertions(+), 29 deletions(-) diff --git a/applications/system/findmy/findmy.c b/applications/system/findmy/findmy.c index 10019347fd..f9dc292cac 100644 --- a/applications/system/findmy/findmy.c +++ b/applications/system/findmy/findmy.c @@ -90,6 +90,7 @@ void findmy_change_broadcast_interval(FindMy* app, uint8_t value) { return; } app->state.broadcast_interval = value; + findmy_state_save(&app->state); findmy_main_update_interval(app->findmy_main, app->state.broadcast_interval); if(furi_hal_bt_extra_beacon_is_active()) { // Always check if beacon is active before changing config @@ -108,6 +109,7 @@ void findmy_change_transmit_power(FindMy* app, uint8_t value) { return; } app->state.transmit_power = value; + findmy_state_save(&app->state); if(furi_hal_bt_extra_beacon_is_active()) { furi_check(furi_hal_bt_extra_beacon_stop()); } @@ -120,6 +122,7 @@ void findmy_change_transmit_power(FindMy* app, uint8_t value) { void findmy_toggle_beacon(FindMy* app) { app->state.beacon_active = !app->state.beacon_active; + findmy_state_save(&app->state); if(furi_hal_bt_extra_beacon_is_active()) { furi_check(furi_hal_bt_extra_beacon_stop()); } diff --git a/applications/system/findmy/findmy_state.c b/applications/system/findmy/findmy_state.c index 34e9c80b22..a8d15d75eb 100644 --- a/applications/system/findmy/findmy_state.c +++ b/applications/system/findmy/findmy_state.c @@ -3,40 +3,76 @@ #include #include #include +#include bool findmy_state_load(FindMyState* out_state) { FindMyState state; - // Set default values - state.beacon_active = false; - state.broadcast_interval = 5; - state.transmit_power = 6; + // Try to load from file + bool loaded_from_file = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_file_exists(storage, FINDMY_STATE_PATH)) { + FlipperFormat* file = flipper_format_file_alloc(storage); + do { + uint32_t tmp; + FuriString* str = furi_string_alloc(); + if(!flipper_format_file_open_existing(file, FINDMY_STATE_PATH)) break; + if(!flipper_format_read_header(file, str, &tmp)) break; + if(furi_string_cmp_str(str, FINDMY_STATE_HEADER)) break; + if(tmp != FINDMY_STATE_VER) break; + + if(!flipper_format_read_bool(file, "beacon_active", &state.beacon_active, 1)) break; + + if(!flipper_format_read_uint32(file, "broadcast_interval", &tmp, 1)) break; + state.broadcast_interval = tmp; + + if(!flipper_format_read_uint32(file, "transmit_power", &tmp, 1)) break; + state.transmit_power = tmp; + + if(!flipper_format_read_hex(file, "mac", state.mac, sizeof(state.mac))) break; + + if(!flipper_format_read_hex(file, "data", state.data, sizeof(state.data))) break; + + loaded_from_file = true; + } while(0); + flipper_format_free(file); + } + furi_record_close(RECORD_STORAGE); + + // Otherwise set default values + if(!loaded_from_file) { + state.beacon_active = false; + state.broadcast_interval = 5; + state.transmit_power = 6; + + // Set default mac + uint8_t default_mac[EXTRA_BEACON_MAC_ADDR_SIZE] = {0x66, 0x55, 0x44, 0x33, 0x22, 0x11}; + memcpy(state.mac, default_mac, sizeof(state.mac)); + + // Set default empty AirTag data + uint8_t* data = state.data; + *data++ = 0x1E; // Length + *data++ = 0xFF; // Manufacturer Specific Data + *data++ = 0x4C; // Company ID (Apple, Inc.) + *data++ = 0x00; // ... + *data++ = 0x12; // Type (FindMy) + *data++ = 0x19; // Length + *data++ = 0x00; // Status + // Placeholder Empty Public Key without the MAC address + for(size_t i = 0; i < 22; ++i) { + *data++ = 0x00; + } + *data++ = 0x00; // First 2 bits are the version, the rest is the battery level + *data++ = 0x00; // Hint (0x00) + } + + // Sync values to config state.config.min_adv_interval_ms = state.broadcast_interval * 1000; // Converting s to ms state.config.max_adv_interval_ms = (state.broadcast_interval * 1000) + 150; state.config.adv_channel_map = GapAdvChannelMapAll; state.config.adv_power_level = GapAdvPowerLevel_0dBm + state.transmit_power; state.config.address_type = GapAddressTypePublic; - - // Set default mac - uint8_t default_mac[EXTRA_BEACON_MAC_ADDR_SIZE] = {0x66, 0x55, 0x44, 0x33, 0x22, 0x11}; - memcpy(state.mac, default_mac, sizeof(state.mac)); - memcpy(state.config.address, default_mac, sizeof(state.config.address)); - - // Set default empty AirTag data - uint8_t* data = state.data; - *data++ = 0x1E; // Length - *data++ = 0xFF; // Manufacturer Specific Data - *data++ = 0x4C; // Company ID (Apple, Inc.) - *data++ = 0x00; // ... - *data++ = 0x12; // Type (FindMy) - *data++ = 0x19; // Length - *data++ = 0x00; // Status - // Placeholder Empty Public Key without the MAC address - for(size_t i = 0; i < 22; ++i) { - *data++ = 0x00; - } - *data++ = 0x00; // First 2 bits are the version, the rest is the battery level - *data++ = 0x00; // Hint (0x00) + memcpy(state.config.address, state.mac, sizeof(state.config.address)); // Copy to caller state before popping stack memcpy(out_state, &state, sizeof(state)); @@ -59,3 +95,30 @@ void findmy_state_apply(FindMyState* state) { furi_check(furi_hal_bt_extra_beacon_start()); } } + +void findmy_state_save(FindMyState* state) { + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_mkdir(storage, FINDMY_STATE_DIR); + FlipperFormat* file = flipper_format_file_alloc(storage); + + do { + uint32_t tmp; + if(!flipper_format_file_open_always(file, FINDMY_STATE_PATH)) break; + if(!flipper_format_write_header_cstr(file, FINDMY_STATE_HEADER, FINDMY_STATE_VER)) break; + + if(!flipper_format_write_bool(file, "beacon_active", &state->beacon_active, 1)) break; + + tmp = state->broadcast_interval; + if(!flipper_format_write_uint32(file, "broadcast_interval", &tmp, 1)) break; + + tmp = state->transmit_power; + if(!flipper_format_write_uint32(file, "transmit_power", &tmp, 1)) break; + + if(!flipper_format_write_hex(file, "mac", state->mac, sizeof(state->mac))) break; + + if(!flipper_format_write_hex(file, "data", state->data, sizeof(state->data))) break; + } while(0); + + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); +} diff --git a/applications/system/findmy/findmy_state.h b/applications/system/findmy/findmy_state.h index 48a82fd2e4..284f2dfbc1 100644 --- a/applications/system/findmy/findmy_state.h +++ b/applications/system/findmy/findmy_state.h @@ -2,16 +2,25 @@ #include -typedef struct { - uint8_t mac[EXTRA_BEACON_MAC_ADDR_SIZE]; - uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]; - GapExtraBeaconConfig config; +#define FINDMY_STATE_HEADER "FindMy Flipper State" +#define FINDMY_STATE_VER 1 +#define FINDMY_STATE_DIR EXT_PATH("apps_data/findmy") +#define FINDMY_STATE_PATH FINDMY_STATE_DIR "/findmy_state.txt" +typedef struct { bool beacon_active; uint8_t broadcast_interval; uint8_t transmit_power; + + uint8_t mac[EXTRA_BEACON_MAC_ADDR_SIZE]; + uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]; + + // Generated from the other state values + GapExtraBeaconConfig config; } FindMyState; bool findmy_state_load(FindMyState* out_state); void findmy_state_apply(FindMyState* state); + +void findmy_state_save(FindMyState* state); diff --git a/applications/system/findmy/scenes/findmy_scene_config_mac.c b/applications/system/findmy/scenes/findmy_scene_config_mac.c index b42291a7f4..2dc67f79da 100644 --- a/applications/system/findmy/scenes/findmy_scene_config_mac.c +++ b/applications/system/findmy/scenes/findmy_scene_config_mac.c @@ -40,6 +40,7 @@ bool findmy_scene_config_mac_on_event(void* context, SceneManagerEvent event) { case ByteInputResultOk: furi_hal_bt_reverse_mac_addr(app->mac_buf); memcpy(&app->state.mac, app->mac_buf, sizeof(app->state.mac)); + findmy_state_save(&app->state); memcpy(&app->state.config.address, app->mac_buf, sizeof(app->state.config.address)); if(furi_hal_bt_extra_beacon_is_active()) { furi_check(furi_hal_bt_extra_beacon_stop()); diff --git a/applications/system/findmy/scenes/findmy_scene_config_packet.c b/applications/system/findmy/scenes/findmy_scene_config_packet.c index 121bf58b58..9d77fb1d79 100644 --- a/applications/system/findmy/scenes/findmy_scene_config_packet.c +++ b/applications/system/findmy/scenes/findmy_scene_config_packet.c @@ -40,6 +40,7 @@ bool findmy_scene_config_packet_on_event(void* context, SceneManagerEvent event) scene_manager_search_and_switch_to_previous_scene( app->scene_manager, FindMySceneConfig); memcpy(app->state.data, app->packet_buf, sizeof(app->state.data)); + findmy_state_save(&app->state); furi_check( furi_hal_bt_extra_beacon_set_data(app->state.data, sizeof(app->state.data))); findmy_main_update_type(app->findmy_main, findmy_data_get_type(app->state.data)); diff --git a/applications/system/findmy/scenes/findmy_scene_main.c b/applications/system/findmy/scenes/findmy_scene_main.c index 63932fb5c8..e70b59fc54 100644 --- a/applications/system/findmy/scenes/findmy_scene_main.c +++ b/applications/system/findmy/scenes/findmy_scene_main.c @@ -25,6 +25,8 @@ bool findmy_scene_main_on_event(void* context, SceneManagerEvent event) { findmy_toggle_beacon(app); break; case FindMyMainEventBackground: + app->state.beacon_active = true; + findmy_state_save(&app->state); if(!furi_hal_bt_extra_beacon_is_active()) { furi_check(furi_hal_bt_extra_beacon_start()); } @@ -40,6 +42,8 @@ bool findmy_scene_main_on_event(void* context, SceneManagerEvent event) { findmy_change_broadcast_interval(app, app->state.broadcast_interval - 1); break; case FindMyMainEventQuit: + app->state.beacon_active = false; + findmy_state_save(&app->state); if(furi_hal_bt_extra_beacon_is_active()) { furi_check(furi_hal_bt_extra_beacon_stop()); } From 61b35e07ff9d112423c98e72cf1ad41a6d8f5f95 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 8 Mar 2024 21:41:57 +0000 Subject: [PATCH 08/40] FindMy: Simpler beacon state sync --- applications/system/findmy/findmy.c | 5 ++--- applications/system/findmy/findmy_state.c | 14 ++++++++++---- .../system/findmy/scenes/findmy_scene_config_mac.c | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/applications/system/findmy/findmy.c b/applications/system/findmy/findmy.c index f9dc292cac..921818103f 100644 --- a/applications/system/findmy/findmy.c +++ b/applications/system/findmy/findmy.c @@ -90,14 +90,13 @@ void findmy_change_broadcast_interval(FindMy* app, uint8_t value) { return; } app->state.broadcast_interval = value; + findmy_state_sync_config(&app->state); findmy_state_save(&app->state); findmy_main_update_interval(app->findmy_main, app->state.broadcast_interval); if(furi_hal_bt_extra_beacon_is_active()) { // Always check if beacon is active before changing config furi_check(furi_hal_bt_extra_beacon_stop()); } - app->state.config.min_adv_interval_ms = app->state.broadcast_interval * 1000; - app->state.config.max_adv_interval_ms = app->state.config.min_adv_interval_ms + 150; furi_check(furi_hal_bt_extra_beacon_set_config(&app->state.config)); if(app->state.beacon_active) { furi_check(furi_hal_bt_extra_beacon_start()); @@ -109,11 +108,11 @@ void findmy_change_transmit_power(FindMy* app, uint8_t value) { return; } app->state.transmit_power = value; + findmy_state_sync_config(&app->state); findmy_state_save(&app->state); if(furi_hal_bt_extra_beacon_is_active()) { furi_check(furi_hal_bt_extra_beacon_stop()); } - app->state.config.adv_power_level = GapAdvPowerLevel_0dBm + app->state.transmit_power; furi_check(furi_hal_bt_extra_beacon_set_config(&app->state.config)); if(app->state.beacon_active) { furi_check(furi_hal_bt_extra_beacon_start()); diff --git a/applications/system/findmy/findmy_state.c b/applications/system/findmy/findmy_state.c index a8d15d75eb..69c90389e2 100644 --- a/applications/system/findmy/findmy_state.c +++ b/applications/system/findmy/findmy_state.c @@ -67,12 +67,11 @@ bool findmy_state_load(FindMyState* out_state) { } // Sync values to config - state.config.min_adv_interval_ms = state.broadcast_interval * 1000; // Converting s to ms - state.config.max_adv_interval_ms = (state.broadcast_interval * 1000) + 150; + findmy_state_sync_config(&state); + + // Set constants state.config.adv_channel_map = GapAdvChannelMapAll; - state.config.adv_power_level = GapAdvPowerLevel_0dBm + state.transmit_power; state.config.address_type = GapAddressTypePublic; - memcpy(state.config.address, state.mac, sizeof(state.config.address)); // Copy to caller state before popping stack memcpy(out_state, &state, sizeof(state)); @@ -96,6 +95,13 @@ void findmy_state_apply(FindMyState* state) { } } +void findmy_state_sync_config(FindMyState* state) { + state.config.min_adv_interval_ms = state.broadcast_interval * 1000; // Converting s to ms + state.config.max_adv_interval_ms = (state.broadcast_interval * 1000) + 150; + state.config.adv_power_level = GapAdvPowerLevel_0dBm + state.transmit_power; + memcpy(state.config.address, state.mac, sizeof(state.config.address)); +} + void findmy_state_save(FindMyState* state) { Storage* storage = furi_record_open(RECORD_STORAGE); storage_simply_mkdir(storage, FINDMY_STATE_DIR); diff --git a/applications/system/findmy/scenes/findmy_scene_config_mac.c b/applications/system/findmy/scenes/findmy_scene_config_mac.c index 2dc67f79da..1b72d19278 100644 --- a/applications/system/findmy/scenes/findmy_scene_config_mac.c +++ b/applications/system/findmy/scenes/findmy_scene_config_mac.c @@ -40,8 +40,8 @@ bool findmy_scene_config_mac_on_event(void* context, SceneManagerEvent event) { case ByteInputResultOk: furi_hal_bt_reverse_mac_addr(app->mac_buf); memcpy(&app->state.mac, app->mac_buf, sizeof(app->state.mac)); + findmy_state_sync_config(&app->state); findmy_state_save(&app->state); - memcpy(&app->state.config.address, app->mac_buf, sizeof(app->state.config.address)); if(furi_hal_bt_extra_beacon_is_active()) { furi_check(furi_hal_bt_extra_beacon_stop()); } From f4ef9e50fbb81db602d539bc88720894b87e9b86 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 8 Mar 2024 22:35:24 +0000 Subject: [PATCH 09/40] FindMy: Fix build --- applications/system/findmy/findmy_state.c | 8 ++++---- applications/system/findmy/findmy_state.h | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/applications/system/findmy/findmy_state.c b/applications/system/findmy/findmy_state.c index 69c90389e2..12e8cbed21 100644 --- a/applications/system/findmy/findmy_state.c +++ b/applications/system/findmy/findmy_state.c @@ -96,10 +96,10 @@ void findmy_state_apply(FindMyState* state) { } void findmy_state_sync_config(FindMyState* state) { - state.config.min_adv_interval_ms = state.broadcast_interval * 1000; // Converting s to ms - state.config.max_adv_interval_ms = (state.broadcast_interval * 1000) + 150; - state.config.adv_power_level = GapAdvPowerLevel_0dBm + state.transmit_power; - memcpy(state.config.address, state.mac, sizeof(state.config.address)); + state->config.min_adv_interval_ms = state->broadcast_interval * 1000; // Converting s to ms + state->config.max_adv_interval_ms = (state->broadcast_interval * 1000) + 150; + state->config.adv_power_level = GapAdvPowerLevel_0dBm + state->transmit_power; + memcpy(state->config.address, state->mac, sizeof(state->config.address)); } void findmy_state_save(FindMyState* state) { diff --git a/applications/system/findmy/findmy_state.h b/applications/system/findmy/findmy_state.h index 284f2dfbc1..d11313e588 100644 --- a/applications/system/findmy/findmy_state.h +++ b/applications/system/findmy/findmy_state.h @@ -23,4 +23,6 @@ bool findmy_state_load(FindMyState* out_state); void findmy_state_apply(FindMyState* state); +void findmy_state_sync_config(FindMyState* state); + void findmy_state_save(FindMyState* state); From edf013eacda870b7004120b08c48b327cf507144 Mon Sep 17 00:00:00 2001 From: Matthew <113921492+MatthewKuKanich@users.noreply.github.com> Date: Fri, 8 Mar 2024 21:39:22 -0500 Subject: [PATCH 10/40] Update findmy.c - ignore changable byte --- applications/system/findmy/findmy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/system/findmy/findmy.c b/applications/system/findmy/findmy.c index 921818103f..78303257aa 100644 --- a/applications/system/findmy/findmy.c +++ b/applications/system/findmy/findmy.c @@ -135,7 +135,7 @@ FindMyType findmy_data_get_type(uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]) { if(data[0] == 0x1E && // Length data[1] == 0xFF && // Manufacturer Specific Data data[2] == 0x4C && // Company ID (Apple, Inc.) - data[3] == 0x00 && // ... + //data[3] == 0x00 && // The state of the tag can vary, if we can find all known states this could work. I think its safe to ignore for now data[4] == 0x12 && // Type (FindMy) data[5] == 0x19 // Length ) { @@ -143,4 +143,4 @@ FindMyType findmy_data_get_type(uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]) { } else { return FindMyTypeSamsung; } -} \ No newline at end of file +} From d23f64d693ea22d50379d89689353cf998aff980 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 04:19:50 +0000 Subject: [PATCH 11/40] FindMy: Add generate keys script --- applications/system/findmy/generate_keys.py | 84 +++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 applications/system/findmy/generate_keys.py diff --git a/applications/system/findmy/generate_keys.py b/applications/system/findmy/generate_keys.py new file mode 100644 index 0000000000..59740f7662 --- /dev/null +++ b/applications/system/findmy/generate_keys.py @@ -0,0 +1,84 @@ +import base64 +import os +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes + +def advertisement_template(): + adv = "" + adv += "1e" # length (30) + adv += "ff" # manufacturer specific data + adv += "4c00" # company ID (Apple) + adv += "1219" # offline finding type and length + adv += "00" # state + for _ in range(22): + adv += "00" + adv += "00" # first two bits of key[0] + adv += "00" # hint + return bytearray.fromhex(adv) + +def convert_key_to_hex(private_key, public_key): + private_key_hex = private_key.private_numbers().private_value.to_bytes(28, byteorder='big').hex() + public_key_hex = public_key.public_numbers().x.to_bytes(28, byteorder='big').hex() + return private_key_hex, public_key_hex + +def generate_mac_and_payload(public_key): + public_key_bytes = public_key.public_numbers().x.to_bytes(28, byteorder='big') + mac = public_key_bytes[:6].hex() + payload = advertisement_template() + payload[7:29] = public_key_bytes[6:22] + return mac, payload.hex() + +def main(): + nkeys = int(input('Enter the number of keys to generate: ')) + prefix = input('Enter a name for the keyfiles (optional, press enter to skip): ') + print() + + if not os.path.exists('keys'): + os.makedirs('keys') + + for i in range(nkeys): + while True: + private_key = ec.generate_private_key(ec.SECP224R1(), default_backend()) + public_key = private_key.public_key() + + private_key_bytes = private_key.private_numbers().private_value.to_bytes(28, byteorder='big') + public_key_bytes = public_key.public_numbers().x.to_bytes(28, byteorder='big') + + private_key_b64 = base64.b64encode(private_key_bytes).decode("ascii") + public_key_b64 = base64.b64encode(public_key_bytes).decode("ascii") + + private_key_hex, public_key_hex = convert_key_to_hex(private_key, public_key) + mac, payload = generate_mac_and_payload(public_key) + + public_key_hash = hashes.Hash(hashes.SHA256()) + public_key_hash.update(public_key_bytes) + s256_b64 = base64.b64encode(public_key_hash.finalize()).decode("ascii") + + if '/' not in s256_b64[:7]: + fname = f"{prefix}_{s256_b64[:7]}.keys" if prefix else f"{s256_b64[:7]}.keys" + + print(f'{i + 1})') + print('Private key (Base64):', private_key_b64) + print('Public key (Base64):', public_key_b64) + print('Hashed adv key (Base64):', s256_b64) + print('---------------------------------------------------------------------------------') + print('Private key (Hex):', private_key_hex) + print('Public key (Hex):', public_key_hex) + print('---------------------------------------------------------------------------------') + print('MAC:', mac) + print('Payload:', payload) + print() + print('Place the .keys file onto your Flipper or input the MAC and Payload manually.') + + with open(f"keys/{fname}", 'w') as f: + f.write(f'Private key: {private_key_b64}\n') + f.write(f'Public key: {public_key_b64}\n') + f.write(f'Hashed adv key: {s256_b64}\n') + f.write(f'Private key (Hex): {private_key_hex}\n') + f.write(f'Public key (Hex): {public_key_hex}\n') + f.write(f'MAC: {mac}\n') + f.write(f'Payload: {payload}\n') + break + +main() \ No newline at end of file From 448033c8022dee407c3564650dbf0ed08bf7a4ce Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 04:20:04 +0000 Subject: [PATCH 12/40] FindMy: Fix key generation script --- applications/system/findmy/generate_keys.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/applications/system/findmy/generate_keys.py b/applications/system/findmy/generate_keys.py index 59740f7662..5059051048 100644 --- a/applications/system/findmy/generate_keys.py +++ b/applications/system/findmy/generate_keys.py @@ -23,11 +23,16 @@ def convert_key_to_hex(private_key, public_key): return private_key_hex, public_key_hex def generate_mac_and_payload(public_key): - public_key_bytes = public_key.public_numbers().x.to_bytes(28, byteorder='big') - mac = public_key_bytes[:6].hex() - payload = advertisement_template() - payload[7:29] = public_key_bytes[6:22] - return mac, payload.hex() + key = public_key.public_numbers().x.to_bytes(28, byteorder='big') + + addr = bytearray(key[:6]) + addr[0] |= 0b11000000 + + adv = advertisement_template() + adv[7:29] = key[6:28] + adv[29] = key[0] >> 6 + + return addr.hex(), adv.hex() def main(): nkeys = int(input('Enter the number of keys to generate: ')) @@ -57,14 +62,14 @@ def main(): if '/' not in s256_b64[:7]: fname = f"{prefix}_{s256_b64[:7]}.keys" if prefix else f"{s256_b64[:7]}.keys" - + print(f'{i + 1})') print('Private key (Base64):', private_key_b64) print('Public key (Base64):', public_key_b64) print('Hashed adv key (Base64):', s256_b64) print('---------------------------------------------------------------------------------') print('Private key (Hex):', private_key_hex) - print('Public key (Hex):', public_key_hex) + print('Public key (Hex):', public_key_hex) print('---------------------------------------------------------------------------------') print('MAC:', mac) print('Payload:', payload) From cd837576ea0e66954b9f3bf0953ffdc304332f35 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 05:04:09 +0000 Subject: [PATCH 13/40] FindMy: Add OpenHaystack import from .keys file --- applications/system/findmy/application.fam | 1 + applications/system/findmy/findmy.c | 10 ++ applications/system/findmy/findmy_i.h | 11 ++ applications/system/findmy/helpers/base64.c | 141 +++++++++++++++++ applications/system/findmy/helpers/base64.h | 21 +++ .../system/findmy/icons/text_10px.png | Bin 0 -> 158 bytes .../findmy/scenes/findmy_scene_config.c | 13 +- .../scenes/findmy_scene_config_import.c | 145 ++++++++++++++++++ .../findmy_scene_config_import_result.c | 58 +++++++ .../system/findmy/scenes/findmy_scenes.h | 2 + 10 files changed, 399 insertions(+), 3 deletions(-) create mode 100644 applications/system/findmy/helpers/base64.c create mode 100644 applications/system/findmy/helpers/base64.h create mode 100644 applications/system/findmy/icons/text_10px.png create mode 100644 applications/system/findmy/scenes/findmy_scene_config_import.c create mode 100644 applications/system/findmy/scenes/findmy_scene_config_import_result.c diff --git a/applications/system/findmy/application.fam b/applications/system/findmy/application.fam index 9a7b2df949..2ef49310f0 100644 --- a/applications/system/findmy/application.fam +++ b/applications/system/findmy/application.fam @@ -6,6 +6,7 @@ App( requires=["gui"], stack_size=2 * 1024, fap_icon="location_icon.png", + fap_icon_assets="icons", fap_category="Bluetooth", fap_author="@MatthewKuKanich", fap_weburl="https://github.com/MatthewKuKanich/FindMyFlipper", diff --git a/applications/system/findmy/findmy.c b/applications/system/findmy/findmy.c index 921818103f..b51343ce10 100644 --- a/applications/system/findmy/findmy.c +++ b/applications/system/findmy/findmy.c @@ -16,6 +16,8 @@ static FindMy* findmy_app_alloc() { FindMy* app = malloc(sizeof(FindMy)); app->gui = furi_record_open(RECORD_GUI); + app->storage = furi_record_open(RECORD_STORAGE); + app->dialogs = furi_record_open(RECORD_DIALOGS); app->view_dispatcher = view_dispatcher_alloc(); view_dispatcher_enable_queue(app->view_dispatcher); @@ -41,6 +43,9 @@ static FindMy* findmy_app_alloc() { FindMyViewVarItemList, variable_item_list_get_view(app->var_item_list)); + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, FindMyViewPopup, popup_get_view(app->popup)); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); findmy_state_load(&app->state); @@ -56,6 +61,9 @@ static FindMy* findmy_app_alloc() { static void findmy_app_free(FindMy* app) { furi_assert(app); + view_dispatcher_remove_view(app->view_dispatcher, FindMyViewPopup); + popup_free(app->popup); + view_dispatcher_remove_view(app->view_dispatcher, FindMyViewVarItemList); variable_item_list_free(app->var_item_list); @@ -68,6 +76,8 @@ static void findmy_app_free(FindMy* app) { view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_GUI); free(app); diff --git a/applications/system/findmy/findmy_i.h b/applications/system/findmy/findmy_i.h index c5991b5f47..c7ff965ef3 100644 --- a/applications/system/findmy/findmy_i.h +++ b/applications/system/findmy/findmy_i.h @@ -5,22 +5,32 @@ #include #include #include +#include "findmy_icons.h" +#include +#include #include +#include +#include #include #include #include "views/findmy_main.h" #include #include +#include #include "scenes/findmy_scene.h" +#include "helpers/base64.h" struct FindMy { Gui* gui; + Storage* storage; + DialogsApp* dialogs; SceneManager* scene_manager; ViewDispatcher* view_dispatcher; FindMyMain* findmy_main; ByteInput* byte_input; VariableItemList* var_item_list; + Popup* popup; uint8_t mac_buf[EXTRA_BEACON_MAC_ADDR_SIZE]; uint8_t packet_buf[EXTRA_BEACON_MAX_DATA_SIZE]; @@ -32,6 +42,7 @@ typedef enum { FindMyViewMain, FindMyViewByteInput, FindMyViewVarItemList, + FindMyViewPopup, } FindMyView; enum FindMyType { diff --git a/applications/system/findmy/helpers/base64.c b/applications/system/findmy/helpers/base64.c new file mode 100644 index 0000000000..f1fb718706 --- /dev/null +++ b/applications/system/findmy/helpers/base64.c @@ -0,0 +1,141 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005-2011, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ +// https://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c + +#include "base64.h" + +#define os_malloc malloc +#define os_free free +#define os_memset memset + +static const unsigned char base64_table[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * base64_encode - Base64 encode + * @src: Data to be encoded + * @len: Length of the data to be encoded + * @out_len: Pointer to output length variable, or %NULL if not used + * Returns: Allocated buffer of out_len bytes of encoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. Returned buffer is + * nul terminated to make it easier to use as a C string. The nul terminator is + * not included in out_len. + */ +unsigned char* base64_encode(const unsigned char* src, size_t len, size_t* out_len) { + unsigned char *out, *pos; + const unsigned char *end, *in; + size_t olen; + int line_len; + + olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */ + olen += olen / 72; /* line feeds */ + olen++; /* nul termination */ + if(olen < len) return NULL; /* integer overflow */ + out = os_malloc(olen); + if(out == NULL) return NULL; + + end = src + len; + in = src; + pos = out; + line_len = 0; + while(end - in >= 3) { + *pos++ = base64_table[in[0] >> 2]; + *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = base64_table[in[2] & 0x3f]; + in += 3; + line_len += 4; + if(line_len >= 72) { + *pos++ = '\n'; + line_len = 0; + } + } + + if(end - in) { + *pos++ = base64_table[in[0] >> 2]; + if(end - in == 1) { + *pos++ = base64_table[(in[0] & 0x03) << 4]; + *pos++ = '='; + } else { + *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64_table[(in[1] & 0x0f) << 2]; + } + *pos++ = '='; + line_len += 4; + } + + if(line_len) *pos++ = '\n'; + + *pos = '\0'; + if(out_len) *out_len = pos - out; + return out; +} + +/** + * base64_decode - Base64 decode + * @src: Data to be decoded + * @len: Length of the data to be decoded + * @out_len: Pointer to output length variable + * Returns: Allocated buffer of out_len bytes of decoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. + */ +unsigned char* base64_decode(const unsigned char* src, size_t len, size_t* out_len) { + unsigned char dtable[256], *out, *pos, block[4], tmp; + size_t i, count, olen; + int pad = 0; + + os_memset(dtable, 0x80, 256); + for(i = 0; i < sizeof(base64_table) - 1; i++) dtable[base64_table[i]] = (unsigned char)i; + dtable['='] = 0; + + count = 0; + for(i = 0; i < len; i++) { + if(dtable[src[i]] != 0x80) count++; + } + + if(count == 0 || count % 4) return NULL; + + olen = count / 4 * 3; + pos = out = os_malloc(olen); + if(out == NULL) return NULL; + + count = 0; + for(i = 0; i < len; i++) { + tmp = dtable[src[i]]; + if(tmp == 0x80) continue; + + if(src[i] == '=') pad++; + block[count] = tmp; + count++; + if(count == 4) { + *pos++ = (block[0] << 2) | (block[1] >> 4); + *pos++ = (block[1] << 4) | (block[2] >> 2); + *pos++ = (block[2] << 6) | block[3]; + count = 0; + if(pad) { + if(pad == 1) + pos--; + else if(pad == 2) + pos -= 2; + else { + /* Invalid padding */ + os_free(out); + return NULL; + } + break; + } + } + } + + *out_len = pos - out; + return out; +} diff --git a/applications/system/findmy/helpers/base64.h b/applications/system/findmy/helpers/base64.h new file mode 100644 index 0000000000..333f82dc1c --- /dev/null +++ b/applications/system/findmy/helpers/base64.h @@ -0,0 +1,21 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ +// https://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.h + +#ifndef BASE64_H +#define BASE64_H + +#include +#include +#include +#include + +unsigned char* base64_encode(const unsigned char* src, size_t len, size_t* out_len); +unsigned char* base64_decode(const unsigned char* src, size_t len, size_t* out_len); + +#endif /* BASE64_H */ \ No newline at end of file diff --git a/applications/system/findmy/icons/text_10px.png b/applications/system/findmy/icons/text_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..8e8a6183dd50535729dc9c9b4f220a12dd4c600f GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&u?6^qxB}__|Nk$&IsYz@rRC}3 z7*a7OIiZ2U&CSi=;0cBn1vTatM&Z;3u7g(^G9`qQn09G2aWeNXaKC0S=Q~tg57Z@F z;u=vBoS#-wo>-L1;E+?AmspUPnOCA;ke9BToS%}K{MA`f4ycg9)78&qol`;+00Iau A9smFU literal 0 HcmV?d00001 diff --git a/applications/system/findmy/scenes/findmy_scene_config.c b/applications/system/findmy/scenes/findmy_scene_config.c index 415bda9453..eba1a85a2b 100644 --- a/applications/system/findmy/scenes/findmy_scene_config.c +++ b/applications/system/findmy/scenes/findmy_scene_config.c @@ -3,7 +3,8 @@ enum VarItemListIndex { VarItemListIndexBroadcastInterval, VarItemListIndexTransmitPower, - VarItemListIndexRegisterTag, + VarItemListIndexImportTagFromFile, + VarItemListIndexRegisterTagManually, VarItemListIndexAbout, }; @@ -57,7 +58,10 @@ void findmy_scene_config_on_enter(void* context) { snprintf(power_str, sizeof(power_str), "%ddBm", app->state.transmit_power); variable_item_set_current_value_text(item, power_str); - item = variable_item_list_add(var_item_list, "Register Tag", 0, NULL, NULL); + item = variable_item_list_add(var_item_list, "Import Tag From File", 0, NULL, NULL); + + item = variable_item_list_add(var_item_list, "Register Tag Manually", 0, NULL, NULL); + item = variable_item_list_add( var_item_list, "Matthew KuKanich, Thanks to Chapoly1305, WillyJL, OpenHaystack, Testers", @@ -82,7 +86,10 @@ bool findmy_scene_config_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(app->scene_manager, FindMySceneConfig, event.event); consumed = true; switch(event.event) { - case VarItemListIndexRegisterTag: + case VarItemListIndexImportTagFromFile: + scene_manager_next_scene(app->scene_manager, FindMySceneConfigImport); + break; + case VarItemListIndexRegisterTagManually: scene_manager_next_scene(app->scene_manager, FindMySceneConfigMac); break; case VarItemListIndexAbout: diff --git a/applications/system/findmy/scenes/findmy_scene_config_import.c b/applications/system/findmy/scenes/findmy_scene_config_import.c new file mode 100644 index 0000000000..8dc9a07e79 --- /dev/null +++ b/applications/system/findmy/scenes/findmy_scene_config_import.c @@ -0,0 +1,145 @@ +#include "../findmy_i.h" + +enum VarItemListIndex { + VarItemListIndexOpenHaystack, +}; + +static const char* parse_open_haystack(FindMy* app, const char* path) { + const char* error = NULL; + + Stream* stream = file_stream_alloc(app->storage); + FuriString* line = furi_string_alloc(); + do { + error = "Can't open file"; + if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) break; + + error = "Wrong file format"; + while(stream_read_line(stream, line)) { + if(furi_string_start_with(line, "Public key: ") || + furi_string_start_with(line, "Advertisement key: ")) { + error = NULL; + break; + } + } + if(error) break; + + furi_string_right(line, furi_string_search_char(line, ':') + 2); + furi_string_trim(line); + + error = "Base64 failed"; + size_t decoded_len; + uint8_t* public_key = base64_decode( + (uint8_t*)furi_string_get_cstr(line), furi_string_size(line), &decoded_len); + if(decoded_len != 28) { + free(public_key); + break; + } + + memcpy(app->state.mac, public_key, sizeof(app->state.mac)); + app->state.mac[0] |= 0b11000000; + furi_hal_bt_reverse_mac_addr(app->state.mac); + + uint8_t advertisement_template[EXTRA_BEACON_MAX_DATA_SIZE] = { + 0x1e, // length (30) + 0xff, // manufacturer specific data + 0x4c, 0x00, // company ID (Apple) + 0x12, 0x19, // offline finding type and length + 0x00, //state + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, // first two bits of key[0] + 0x00, // hint + }; + memcpy(app->state.data, advertisement_template, sizeof(app->state.data)); + memcpy(&app->state.data[7], &public_key[6], decoded_len - 6); + app->state.data[29] = public_key[0] >> 6; + findmy_state_sync_config(&app->state); + findmy_state_save(&app->state); + + free(public_key); + error = NULL; + + } while(false); + furi_string_free(line); + file_stream_close(stream); + stream_free(stream); + + return error; +} + +void findmy_scene_config_import_callback(void* context, uint32_t index) { + furi_assert(context); + FindMy* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void findmy_scene_config_import_on_enter(void* context) { + FindMy* app = context; + VariableItemList* var_item_list = app->var_item_list; + VariableItem* item; + + item = variable_item_list_add(var_item_list, "OpenHaystack .keys", 0, NULL, NULL); + + // This scene acts more like a submenu than a var item list tbh + UNUSED(item); + + variable_item_list_set_enter_callback(var_item_list, findmy_scene_config_import_callback, app); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, FindMySceneConfigImport)); + + view_dispatcher_switch_to_view(app->view_dispatcher, FindMyViewVarItemList); +} + +bool findmy_scene_config_import_on_event(void* context, SceneManagerEvent event) { + FindMy* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, FindMySceneConfigImport, event.event); + consumed = true; + + const char* extension = NULL; + switch(event.event) { + case VarItemListIndexOpenHaystack: + extension = ".keys"; + break; + default: + break; + } + if(!extension) { + return consumed; + } + + const DialogsFileBrowserOptions browser_options = { + .extension = extension, + .icon = &I_text_10px, + .base_path = FINDMY_STATE_DIR, + }; + storage_simply_mkdir(app->storage, browser_options.base_path); + FuriString* path = furi_string_alloc_set_str(browser_options.base_path); + if(dialog_file_browser_show(app->dialogs, path, path, &browser_options)) { + // The parse functions return the error text, or NULL for success + // Used in result to show success or error message + const char* error = NULL; + switch(event.event) { + case VarItemListIndexOpenHaystack: + error = parse_open_haystack(app, furi_string_get_cstr(path)); + break; + } + scene_manager_set_scene_state( + app->scene_manager, FindMySceneConfigImportResult, (uint32_t)error); + scene_manager_next_scene(app->scene_manager, FindMySceneConfigImportResult); + } + furi_string_free(path); + } + + return consumed; +} + +void findmy_scene_config_import_on_exit(void* context) { + FindMy* app = context; + VariableItemList* var_item_list = app->var_item_list; + + variable_item_list_reset(var_item_list); +} \ No newline at end of file diff --git a/applications/system/findmy/scenes/findmy_scene_config_import_result.c b/applications/system/findmy/scenes/findmy_scene_config_import_result.c new file mode 100644 index 0000000000..62056e78ab --- /dev/null +++ b/applications/system/findmy/scenes/findmy_scene_config_import_result.c @@ -0,0 +1,58 @@ +#include "../findmy_i.h" + +enum PopupEvent { + PopupEventExit, +}; + +static void findmy_scene_config_import_result_callback(void* context) { + FindMy* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, PopupEventExit); +} + +void findmy_scene_config_import_result_on_enter(void* context) { + FindMy* app = context; + Popup* popup = app->popup; + + const char* error = (const char*)scene_manager_get_scene_state( + app->scene_manager, FindMySceneConfigImportResult); + if(error) { + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); + popup_set_header(popup, "Error!", 13, 22, AlignLeft, AlignBottom); + popup_set_text(popup, error, 6, 26, AlignLeft, AlignTop); + popup_disable_timeout(popup); + } else { + popup_set_icon(popup, 36, 5, &I_DolphinDone_80x58); + popup_set_header(popup, "Imported!", 13, 22, AlignLeft, AlignBottom); + popup_enable_timeout(popup); + } + popup_set_timeout(popup, 1500); + popup_set_context(popup, app); + popup_set_callback(popup, findmy_scene_config_import_result_callback); + + view_dispatcher_switch_to_view(app->view_dispatcher, FindMyViewPopup); +} + +bool findmy_scene_config_import_result_on_event(void* context, SceneManagerEvent event) { + FindMy* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case PopupEventExit: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, FindMySceneConfig); + break; + default: + break; + } + } + + return consumed; +} + +void findmy_scene_config_import_result_on_exit(void* context) { + FindMy* app = context; + popup_reset(app->popup); +} diff --git a/applications/system/findmy/scenes/findmy_scenes.h b/applications/system/findmy/scenes/findmy_scenes.h index 683c52c5b7..9a35c519d9 100644 --- a/applications/system/findmy/scenes/findmy_scenes.h +++ b/applications/system/findmy/scenes/findmy_scenes.h @@ -1,4 +1,6 @@ ADD_SCENE(findmy, main, Main) ADD_SCENE(findmy, config, Config) +ADD_SCENE(findmy, config_import, ConfigImport) +ADD_SCENE(findmy, config_import_result, ConfigImportResult) ADD_SCENE(findmy, config_mac, ConfigMac) ADD_SCENE(findmy, config_packet, ConfigPacket) From fca5da1c424958d727ee223f866e5150719e18bf Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 05:04:37 +0000 Subject: [PATCH 14/40] Revert "Update findmy.c - ignore changable byte" This reverts commit edf013eacda870b7004120b08c48b327cf507144. --- applications/system/findmy/findmy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/system/findmy/findmy.c b/applications/system/findmy/findmy.c index 80244e6426..b51343ce10 100644 --- a/applications/system/findmy/findmy.c +++ b/applications/system/findmy/findmy.c @@ -145,7 +145,7 @@ FindMyType findmy_data_get_type(uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]) { if(data[0] == 0x1E && // Length data[1] == 0xFF && // Manufacturer Specific Data data[2] == 0x4C && // Company ID (Apple, Inc.) - //data[3] == 0x00 && // The state of the tag can vary, if we can find all known states this could work. I think its safe to ignore for now + data[3] == 0x00 && // ... data[4] == 0x12 && // Type (FindMy) data[5] == 0x19 // Length ) { @@ -153,4 +153,4 @@ FindMyType findmy_data_get_type(uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]) { } else { return FindMyTypeSamsung; } -} +} \ No newline at end of file From 74c6440a27dbf877bdb1280e936c61dee0fa0c83 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 05:09:48 +0000 Subject: [PATCH 15/40] Format --- applications/system/findmy/generate_keys.py | 91 +++++++++++++-------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/applications/system/findmy/generate_keys.py b/applications/system/findmy/generate_keys.py index 5059051048..83ca274fd2 100644 --- a/applications/system/findmy/generate_keys.py +++ b/applications/system/findmy/generate_keys.py @@ -4,6 +4,7 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes + def advertisement_template(): adv = "" adv += "1e" # length (30) @@ -17,13 +18,17 @@ def advertisement_template(): adv += "00" # hint return bytearray.fromhex(adv) + def convert_key_to_hex(private_key, public_key): - private_key_hex = private_key.private_numbers().private_value.to_bytes(28, byteorder='big').hex() - public_key_hex = public_key.public_numbers().x.to_bytes(28, byteorder='big').hex() + private_key_hex = ( + private_key.private_numbers().private_value.to_bytes(28, byteorder="big").hex() + ) + public_key_hex = public_key.public_numbers().x.to_bytes(28, byteorder="big").hex() return private_key_hex, public_key_hex + def generate_mac_and_payload(public_key): - key = public_key.public_numbers().x.to_bytes(28, byteorder='big') + key = public_key.public_numbers().x.to_bytes(28, byteorder="big") addr = bytearray(key[:6]) addr[0] |= 0b11000000 @@ -34,56 +39,74 @@ def generate_mac_and_payload(public_key): return addr.hex(), adv.hex() + def main(): - nkeys = int(input('Enter the number of keys to generate: ')) - prefix = input('Enter a name for the keyfiles (optional, press enter to skip): ') + nkeys = int(input("Enter the number of keys to generate: ")) + prefix = input("Enter a name for the keyfiles (optional, press enter to skip): ") print() - if not os.path.exists('keys'): - os.makedirs('keys') + if not os.path.exists("keys"): + os.makedirs("keys") for i in range(nkeys): while True: private_key = ec.generate_private_key(ec.SECP224R1(), default_backend()) public_key = private_key.public_key() - private_key_bytes = private_key.private_numbers().private_value.to_bytes(28, byteorder='big') - public_key_bytes = public_key.public_numbers().x.to_bytes(28, byteorder='big') + private_key_bytes = private_key.private_numbers().private_value.to_bytes( + 28, byteorder="big" + ) + public_key_bytes = public_key.public_numbers().x.to_bytes( + 28, byteorder="big" + ) private_key_b64 = base64.b64encode(private_key_bytes).decode("ascii") public_key_b64 = base64.b64encode(public_key_bytes).decode("ascii") - private_key_hex, public_key_hex = convert_key_to_hex(private_key, public_key) + private_key_hex, public_key_hex = convert_key_to_hex( + private_key, public_key + ) mac, payload = generate_mac_and_payload(public_key) public_key_hash = hashes.Hash(hashes.SHA256()) public_key_hash.update(public_key_bytes) s256_b64 = base64.b64encode(public_key_hash.finalize()).decode("ascii") - if '/' not in s256_b64[:7]: - fname = f"{prefix}_{s256_b64[:7]}.keys" if prefix else f"{s256_b64[:7]}.keys" - - print(f'{i + 1})') - print('Private key (Base64):', private_key_b64) - print('Public key (Base64):', public_key_b64) - print('Hashed adv key (Base64):', s256_b64) - print('---------------------------------------------------------------------------------') - print('Private key (Hex):', private_key_hex) - print('Public key (Hex):', public_key_hex) - print('---------------------------------------------------------------------------------') - print('MAC:', mac) - print('Payload:', payload) + if "/" not in s256_b64[:7]: + fname = ( + f"{prefix}_{s256_b64[:7]}.keys" + if prefix + else f"{s256_b64[:7]}.keys" + ) + + print(f"{i + 1})") + print("Private key (Base64):", private_key_b64) + print("Public key (Base64):", public_key_b64) + print("Hashed adv key (Base64):", s256_b64) + print( + "---------------------------------------------------------------------------------" + ) + print("Private key (Hex):", private_key_hex) + print("Public key (Hex):", public_key_hex) + print( + "---------------------------------------------------------------------------------" + ) + print("MAC:", mac) + print("Payload:", payload) print() - print('Place the .keys file onto your Flipper or input the MAC and Payload manually.') - - with open(f"keys/{fname}", 'w') as f: - f.write(f'Private key: {private_key_b64}\n') - f.write(f'Public key: {public_key_b64}\n') - f.write(f'Hashed adv key: {s256_b64}\n') - f.write(f'Private key (Hex): {private_key_hex}\n') - f.write(f'Public key (Hex): {public_key_hex}\n') - f.write(f'MAC: {mac}\n') - f.write(f'Payload: {payload}\n') + print( + "Place the .keys file onto your Flipper or input the MAC and Payload manually." + ) + + with open(f"keys/{fname}", "w") as f: + f.write(f"Private key: {private_key_b64}\n") + f.write(f"Public key: {public_key_b64}\n") + f.write(f"Hashed adv key: {s256_b64}\n") + f.write(f"Private key (Hex): {private_key_hex}\n") + f.write(f"Public key (Hex): {public_key_hex}\n") + f.write(f"MAC: {mac}\n") + f.write(f"Payload: {payload}\n") break -main() \ No newline at end of file + +main() From 0b814717403c3e1920cd98173cb9cb511142fb0b Mon Sep 17 00:00:00 2001 From: MatthewKuKanich Date: Sat, 9 Mar 2024 00:23:17 -0500 Subject: [PATCH 16/40] Replace Terminal menu with MNTM menu --- applications/services/gui/modules/menu.c | 105 +++++++++++++---------- lib/momentum/momentum.h | 2 +- 2 files changed, 61 insertions(+), 46 deletions(-) diff --git a/applications/services/gui/modules/menu.c b/applications/services/gui/modules/menu.c index 4fac392d84..7025b85a29 100644 --- a/applications/services/gui/modules/menu.c +++ b/applications/services/gui/modules/menu.c @@ -348,47 +348,68 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { break; } - case MenuStyleTerminal: { - // Draw a border around the screen - canvas_draw_frame(canvas, 0, 0, 128, 64); - - // current dir on the title bar - canvas_set_font(canvas, FontSecondary); + case MenuStyleMNTM: { + // Reset canvas and set background + canvas_reset(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_clear(canvas); + canvas_set_bitmap_mode(canvas, true); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 5, 13, "Momentum"); + canvas_draw_icon(canvas, 62, 4, &I_Release_arrow_18x15); + canvas_draw_line(canvas, 5, 15, 59, 15); + canvas_draw_line(canvas, 7, 17, 61, 17); + canvas_draw_line(canvas, 10, 19, 63, 19); char title[20]; - snprintf(title, sizeof(title), "%s@fz: ~/Home", furi_hal_version_get_name_ptr()); - canvas_draw_str(canvas, 20, 10, title); - - canvas_draw_str(canvas, 118, 9, "x"); // "X" button on the top-right corner - canvas_draw_frame(canvas, 116, 2, 8, 9); - canvas_draw_frame(canvas, 0, 0, 128, 13); - - // Display the user's name line at the bottom - canvas_set_font(canvas, FontBatteryPercent); - char prefix[15]; - snprintf(prefix, sizeof(prefix), "%s@fz:~$", furi_hal_version_get_name_ptr()); - canvas_draw_str(canvas, 2, 56, prefix); - - size_t name_start_x = 2 + (strlen(prefix) - 1) * 6; + snprintf(title, sizeof(title), "%s", furi_hal_version_get_name_ptr()); + canvas_draw_str(canvas, 5, 34, title); + DateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + int hour = curr_dt.hour; + int min = curr_dt.minute; + if(hour > 12) { + hour -= 12; + } + if(hour == 0) { + hour = 12; + } + canvas_set_font(canvas, FontSecondary); + char clk[20]; + snprintf(clk, sizeof(clk), "%02u:%02u", hour, min); + canvas_draw_str(canvas, 5, 46, clk); + + // Draw the selected menu item + MenuItem* item = MenuItemArray_get(model->items, position); + FuriString* name = furi_string_alloc(); + menu_short_name(item, name); + canvas_set_font(canvas, FontSecondary); + canvas_set_color(canvas, ColorBlack); + elements_bold_rounded_frame(canvas, 42, 23, 35, 33); + menu_centered_icon(canvas, item, 43, 24, 35, 32); + canvas_draw_frame(canvas, 0, 0, 128, 64); - for(size_t i = 0; i < 4 && (position + i) < items_count; i++) { - item = MenuItemArray_get(model->items, position + i); + int startY = 15; + int itemHeight = 10; + int itemMaxVisible = 5; + size_t startItem = model->vertical_offset; + size_t endItem = startItem + itemMaxVisible; + endItem = (endItem > MenuItemArray_size(model->items)) ? + MenuItemArray_size(model->items) : + endItem; + size_t scroll_counter = menu_scroll_counter(model, item); + + for(size_t i = startItem; i < endItem; i++) { + MenuItem* item = MenuItemArray_get(model->items, i); + FuriString* name = furi_string_alloc(); menu_short_name(item, name); - - size_t scroll_counter = menu_scroll_counter(model, item); - if(i == 0) { - // Display selected item to the right of the $ symbol - // May want to reduce spacing - elements_scrollable_text_line( - canvas, name_start_x, 56, 60, name, scroll_counter, false); - } else { - // Display the previous items above the user's name line - canvas_draw_str(canvas, 2, 56 - i * 12, item->label); - } + int yPos = startY + ((i - startItem) * itemHeight); + elements_scrollable_text_line(canvas, 83, yPos, 62, name, scroll_counter, false); + furi_string_free(name); } - + furi_string_free(name); + canvas_commit(canvas); break; } - default: break; } @@ -614,16 +635,13 @@ static void menu_process_up(Menu* menu) { switch(momentum_settings.menu_style) { case MenuStyleList: - case MenuStyleTerminal: + case MenuStyleMNTM: if(position > 0) { position--; - if(vertical_offset && vertical_offset == position) { - vertical_offset--; - } } else { position = count - 1; - vertical_offset = count - 8; } + vertical_offset = position; break; case MenuStyleWii: if(position % 2 || (position == count - 1 && count % 2)) { @@ -665,16 +683,13 @@ static void menu_process_down(Menu* menu) { switch(momentum_settings.menu_style) { case MenuStyleList: - case MenuStyleTerminal: + case MenuStyleMNTM: if(position < count - 1) { position++; - if(vertical_offset < count - 8 && vertical_offset == position - 7) { - vertical_offset++; - } } else { position = 0; - vertical_offset = 0; } + vertical_offset = position; break; case MenuStyleWii: if(position % 2 || (position == count - 1 && count % 2)) { diff --git a/lib/momentum/momentum.h b/lib/momentum/momentum.h index 6a8c82b5be..9b3974fd87 100644 --- a/lib/momentum/momentum.h +++ b/lib/momentum/momentum.h @@ -34,7 +34,7 @@ typedef enum { MenuStyleVertical, MenuStyleC64, MenuStyleCompact, - MenuStyleTerminal, + MenuStyleMNTM, MenuStyleCount, } MenuStyle; From 0baa796e335b313bb9124b527065bf37c474f23c Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 05:51:22 +0000 Subject: [PATCH 17/40] FindMy: Add import from nRF Connect .txt file --- applications/system/findmy/findmy_i.h | 1 + .../scenes/findmy_scene_config_import.c | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/applications/system/findmy/findmy_i.h b/applications/system/findmy/findmy_i.h index c7ff965ef3..e008d30b3d 100644 --- a/applications/system/findmy/findmy_i.h +++ b/applications/system/findmy/findmy_i.h @@ -8,6 +8,7 @@ #include "findmy_icons.h" #include #include +#include #include #include #include diff --git a/applications/system/findmy/scenes/findmy_scene_config_import.c b/applications/system/findmy/scenes/findmy_scene_config_import.c index 8dc9a07e79..aedd210668 100644 --- a/applications/system/findmy/scenes/findmy_scene_config_import.c +++ b/applications/system/findmy/scenes/findmy_scene_config_import.c @@ -1,9 +1,76 @@ #include "../findmy_i.h" enum VarItemListIndex { + VarItemListIndexNrfConnect, VarItemListIndexOpenHaystack, }; +static const char* parse_nrf_connect(FindMy* app, const char* path) { + const char* error = NULL; + + Stream* stream = file_stream_alloc(app->storage); + FuriString* line = furi_string_alloc(); + do { + // XX-XX-XX-XX-XX-XX_YYYY-MM-DD HH_MM_SS.txt + error = "Filename must\nhave MAC\naddress"; + uint8_t mac[EXTRA_BEACON_MAC_ADDR_SIZE]; + path_extract_filename_no_ext(path, line); + if(furi_string_size(line) < sizeof(mac) * 3 - 1) break; + error = NULL; + for(size_t i = 0; i < sizeof(mac); i++) { + char a = furi_string_get_char(line, i * 3); + char b = furi_string_get_char(line, i * 3 + 1); + if((a < 'A' && a > 'F') || (a < '0' && a > '9') || (b < 'A' && b > 'F') || + (b < '0' && b > '9') || !hex_char_to_uint8(a, b, &mac[i])) { + error = "Filename must\nhave MAC\naddress"; + break; + } + } + if(error) break; + furi_hal_bt_reverse_mac_addr(mac); + + error = "Can't open file"; + if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) break; + + // YYYY-MM-DD HH:MM:SS.ms, XX dBm, 0xXXXXX + error = "Wrong file format"; + if(!stream_read_line(stream, line)) break; + const char* marker = " dBm, 0x"; + size_t pos = furi_string_search(line, marker); + if(pos == FURI_STRING_FAILURE) break; + furi_string_right(line, pos + strlen(marker)); + furi_string_trim(line); + + error = "Wrong payload size"; + uint8_t data[EXTRA_BEACON_MAX_DATA_SIZE]; + if(furi_string_size(line) != sizeof(data) * 2) break; + error = NULL; + for(size_t i = 0; i < sizeof(data); i++) { + char a = furi_string_get_char(line, i * 2); + char b = furi_string_get_char(line, i * 2 + 1); + if((a < 'A' && a > 'F') || (a < '0' && a > '9') || (b < 'A' && b > 'F') || + (b < '0' && b > '9') || !hex_char_to_uint8(a, b, &data[i])) { + error = "Invalid payload"; + break; + } + } + if(error) break; + + memcpy(app->state.mac, mac, sizeof(app->state.mac)); + memcpy(app->state.data, data, sizeof(app->state.data)); + findmy_state_sync_config(&app->state); + findmy_state_save(&app->state); + + error = NULL; + + } while(false); + furi_string_free(line); + file_stream_close(stream); + stream_free(stream); + + return error; +} + static const char* parse_open_haystack(FindMy* app, const char* path) { const char* error = NULL; @@ -78,6 +145,8 @@ void findmy_scene_config_import_on_enter(void* context) { VariableItemList* var_item_list = app->var_item_list; VariableItem* item; + item = variable_item_list_add(var_item_list, "nRF Connect .txt", 0, NULL, NULL); + item = variable_item_list_add(var_item_list, "OpenHaystack .keys", 0, NULL, NULL); // This scene acts more like a submenu than a var item list tbh @@ -101,6 +170,9 @@ bool findmy_scene_config_import_on_event(void* context, SceneManagerEvent event) const char* extension = NULL; switch(event.event) { + case VarItemListIndexNrfConnect: + extension = ".txt"; + break; case VarItemListIndexOpenHaystack: extension = ".keys"; break; @@ -123,6 +195,9 @@ bool findmy_scene_config_import_on_event(void* context, SceneManagerEvent event) // Used in result to show success or error message const char* error = NULL; switch(event.event) { + case VarItemListIndexNrfConnect: + error = parse_nrf_connect(app, furi_string_get_cstr(path)); + break; case VarItemListIndexOpenHaystack: error = parse_open_haystack(app, furi_string_get_cstr(path)); break; From b253bede42138efb1e149c6ab1b2d03832983180 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 05:51:48 +0000 Subject: [PATCH 18/40] FindMy: Fix imported text position --- .../system/findmy/scenes/findmy_scene_config_import_result.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/findmy/scenes/findmy_scene_config_import_result.c b/applications/system/findmy/scenes/findmy_scene_config_import_result.c index 62056e78ab..f15f55c069 100644 --- a/applications/system/findmy/scenes/findmy_scene_config_import_result.c +++ b/applications/system/findmy/scenes/findmy_scene_config_import_result.c @@ -23,7 +23,7 @@ void findmy_scene_config_import_result_on_enter(void* context) { popup_disable_timeout(popup); } else { popup_set_icon(popup, 36, 5, &I_DolphinDone_80x58); - popup_set_header(popup, "Imported!", 13, 22, AlignLeft, AlignBottom); + popup_set_header(popup, "Imported!", 7, 14, AlignLeft, AlignBottom); popup_enable_timeout(popup); } popup_set_timeout(popup, 1500); From 52d3fe9b388da9673f02c27d66b8a25cdcb86f0b Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 05:54:52 +0000 Subject: [PATCH 19/40] FindMy: Add docs from app repo Co-authored-by: MatthewKuKanich --- applications/system/findmy/README.md | 74 ++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 applications/system/findmy/README.md diff --git a/applications/system/findmy/README.md b/applications/system/findmy/README.md new file mode 100644 index 0000000000..e697aeb7a0 --- /dev/null +++ b/applications/system/findmy/README.md @@ -0,0 +1,74 @@ +# FindMy Flipper - FindMy SmartTag Emulator + +This app extends the functionality of the FlipperZero's bluetooth capabilities, enabling it to act as an Apple AirTag or Samsung SmartTag, or even both simultaneously. It utilizes the FlipperZero's BLE beacon to broadcast a SmartTag signal to be picked up by the FindMy Network. I made this to serve as a versatile tool for tracking purposes, offering the ability to clone existing tags, generate OpenHaystack key pairs for integration with Apple's FindMy network, and tune the device's beacon broadcast settings. + +## Features + +1. Tag Emulation: Clone your existing Apple AirTag or Samsung SmartTag to the FlipperZero, or generate a key pair for use with the FindMy network without owning an actual AirTag. +2. Customization: Users can adjust the interval between beacon broadcasts and modify the transmit power to suit their needs, optimizing for both visibility and battery life. +3. Efficient Background Operation: The app is optimized to run in the background, ensuring that your FlipperZero can still be tracked with minimal battery usage and without stopping normal use. + +## Usage Guide + +### Step 1: Installation +- **Option A:** Use the released/precompiled firmware appropriate (FAP) for your device. +- **Option B:** Build the firmware yourself using `fbt/ufbt`. +- Both Installation options require you to be running a dev build of firmware. When release gets access to the extra BLE beacon this will change, thank you! +### Step 2: Obtaining SmartTag Data + +#### Option A: Open Haystack Method +1. **Generate a Tag:** Download the `generate_keys.py` file and execute it in your terminal. (You will need cryptography ```python3 -m pip install cryptography```) +2. **Follow Prompts:** During execution, you'll be prompted for inputs. By the end, you'll obtain a **Private Key**, **Public Key**, **Payload**, and **MAC Address**. + - **Private Key** is necessary to receive location reports from Apple. + - **MAC Address** should be registered in the FlipperZero app: + 1. Open the app and navigate to the config menu. + 2. Choose "register tag" and enter the MAC Address when prompted. + 3. A payload dialog will appear next. Enter your **Payload** here. + 4. Click save. +3. **Configuration Completion:** With this setup, your device is ready for Open Haystack. Proceed with the specific steps for Open Haystack or MaclessHaystack based on your setup. + - Don't Own a Mac: https://github.com/dchristl/macless-haystack + - Own a Mac: https://github.com/seemoo-lab/openhaystack + +#### Option B: Cloning Existing Tag +1. **Pair a Tag:** First, pair an AirTag or Samsung SmartTag with your device. +2. **Enter 'Lost' Mode:** Keep the tag away from the device it's registered to for approximately 15 minutes. +3. **Download nrfConnect:** Install nrfConnect from the Apple App Store or Google Play Store. +4. **Filter and Scan:** + - Open the app, click on filters, and exclude all except for the brand of your tag (Apple/Samsung). + - Adjust the RSSI to the lowest setting (-40 dBm). + - Initiate a scan. Wait for your SmartTag to appear as a "FindMy" device. +5. **Capture Data:** Click **Raw** or **View Raw** to capture your **payload** and note your tag's **MAC Address**. Immediately remove the tag's battery to prevent key/MAC rotation. +6. **Enter Data in FlipperZero App:** Input the captured **payload** and **MAC Address** into the FlipperZero app. + +### Step 3: Configuration +- Upon launching the app, choose whether to clone an AirTag or SmartTag, generate a new Open Haystack key pair, or adjust broadcast settings. + +### Step 4: Tracking +- Once the app is configured, your FlipperZero can be tracked using the relevant platform's tracking service (FindMy app for Apple devices, SmartThings for Samsung devices, and respective web browsers). + + +Customization + +- Beacon Interval: Adjust how frequently your FlipperZero broadcasts its presence. +- Transmit Power: Increase or decrease the signal strength to balance between tracking range and battery life. + +Background Use + +The app is designed to have a negligible impact on battery life, even when running in the background. This allows for continuous tracking without the need for frequent recharging. + +Compatibility + +- Apple devices for AirTag tracking via the FindMy network. +- Any device that supports Samsung SmartTag tracking, including web browsers (previously FindMyMobile). + +Thanks + +- Huge thanks to all the people that contributed to the OpenHaystack project, supporting projects, and guides on the subject. This wouldn't be a thing without any of you! + +Legal and Privacy + +This app is intended for personal and educational use. Users are responsible for complying with local privacy laws and regulations regarding tracking devices. The cloning and emulation of tracking tags should be done responsibly and with respect to the ownership of the original devices. + +Disclaimer + +This project is not affiliated with Apple Inc. or Samsung. All product names, logos, and brands are property of their respective owners. Use this app responsibly and ethically. From 177f85ddfa0e52be7567e01312e14d88733f9786 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 07:49:17 +0000 Subject: [PATCH 20/40] NfcMaker: Fix timeout on error screen --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index 3dbd298b07..95c4cc640e 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 3dbd298b0714312cc6af4d74c52cd802e718d343 +Subproject commit 95c4cc640e1c0a85bbbe352921d6607250080ada From c4b6f380ab139bfd8c68a7dcaa7146c7e2e6b7fe Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 07:08:30 +0000 Subject: [PATCH 21/40] Add to CODEOWNERS --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a1626c2cd8..20b11bca37 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @Willy-JL @Sil333033 +* @Willy-JL @Sil333033 @HaxSam @MatthewKuKanich From bce77a863305bf1a1328ee3b2015c97f389e7848 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 07:48:25 +0000 Subject: [PATCH 22/40] Main Menu: Cleanup code, fix scrolling text --- .../momentum_app_scene_interface_mainmenu.c | 2 +- applications/services/gui/modules/menu.c | 93 ++++++------------- 2 files changed, 27 insertions(+), 68 deletions(-) diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu.c b/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu.c index 628ca60bb0..e6a992d01b 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu.c @@ -22,7 +22,7 @@ const char* const menu_style_names[MenuStyleCount] = { "Vertical", "C64", "Compact", - "Terminal", + "MNTM", }; static void momentum_app_scene_interface_mainmenu_menu_style_changed(VariableItem* item) { MomentumApp* app = variable_item_get_context(item); diff --git a/applications/services/gui/modules/menu.c b/applications/services/gui/modules/menu.c index 7025b85a29..b472431b5a 100644 --- a/applications/services/gui/modules/menu.c +++ b/applications/services/gui/modules/menu.c @@ -247,6 +247,14 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { case MenuStyleVertical: { canvas_set_orientation(canvas, CanvasOrientationVertical); shift_position = model->vertical_offset; + if(shift_position >= position || shift_position + 7 <= position) { + // In case vertical_offset is out of sync due to changing menu styles + shift_position = CLAMP( + MAX((int32_t)position - 4, 0), + MAX((int32_t)MenuItemArray_size(model->items) - 8, 0), + 0); + model->vertical_offset = shift_position; + } canvas_set_font(canvas, FontSecondary); size_t item_i; size_t y_off; @@ -349,11 +357,6 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { break; } case MenuStyleMNTM: { - // Reset canvas and set background - canvas_reset(canvas); - canvas_set_color(canvas, ColorBlack); - canvas_clear(canvas); - canvas_set_bitmap_mode(canvas, true); canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 5, 13, "Momentum"); canvas_draw_icon(canvas, 62, 4, &I_Release_arrow_18x15); @@ -365,8 +368,8 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str(canvas, 5, 34, title); DateTime curr_dt; furi_hal_rtc_get_datetime(&curr_dt); - int hour = curr_dt.hour; - int min = curr_dt.minute; + uint8_t hour = curr_dt.hour; + uint8_t min = curr_dt.minute; if(hour > 12) { hour -= 12; } @@ -380,34 +383,26 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { // Draw the selected menu item MenuItem* item = MenuItemArray_get(model->items, position); - FuriString* name = furi_string_alloc(); menu_short_name(item, name); - canvas_set_font(canvas, FontSecondary); - canvas_set_color(canvas, ColorBlack); elements_bold_rounded_frame(canvas, 42, 23, 35, 33); menu_centered_icon(canvas, item, 43, 24, 35, 32); canvas_draw_frame(canvas, 0, 0, 128, 64); - int startY = 15; - int itemHeight = 10; - int itemMaxVisible = 5; - size_t startItem = model->vertical_offset; - size_t endItem = startItem + itemMaxVisible; + uint8_t startY = 15; + uint8_t itemHeight = 10; + uint8_t itemMaxVisible = 5; + size_t endItem = position + itemMaxVisible; endItem = (endItem > MenuItemArray_size(model->items)) ? MenuItemArray_size(model->items) : endItem; - size_t scroll_counter = menu_scroll_counter(model, item); - for(size_t i = startItem; i < endItem; i++) { + for(size_t i = position; i < endItem; i++) { MenuItem* item = MenuItemArray_get(model->items, i); - FuriString* name = furi_string_alloc(); menu_short_name(item, name); - int yPos = startY + ((i - startItem) * itemHeight); - elements_scrollable_text_line(canvas, 83, yPos, 62, name, scroll_counter, false); - furi_string_free(name); + uint8_t yPos = startY + ((i - position) * itemHeight); + size_t scroll_counter = menu_scroll_counter(model, i == position); + elements_scrollable_text_line(canvas, 83, yPos, 43, name, scroll_counter, false); } - furi_string_free(name); - canvas_commit(canvas); break; } default: @@ -433,7 +428,7 @@ static bool menu_input_callback(InputEvent* event, void* context) { } } - if(event->type == InputTypeShort) { + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { switch(event->key) { case InputKeyUp: menu_process_up(menu); @@ -448,25 +443,9 @@ static bool menu_input_callback(InputEvent* event, void* context) { menu_process_right(menu); break; case InputKeyOk: - menu_process_ok(menu); - break; - default: - consumed = false; - break; - } - } else if(event->type == InputTypeRepeat) { - switch(event->key) { - case InputKeyUp: - menu_process_up(menu); - break; - case InputKeyDown: - menu_process_down(menu); - break; - case InputKeyLeft: - menu_process_left(menu); - break; - case InputKeyRight: - menu_process_right(menu); + if(event->type != InputTypeRepeat) { + menu_process_ok(menu); + } break; default: consumed = false; @@ -631,7 +610,6 @@ static void menu_process_up(Menu* menu) { { position = model->position; size_t count = MenuItemArray_size(model->items); - size_t vertical_offset = model->vertical_offset; switch(momentum_settings.menu_style) { case MenuStyleList: @@ -641,7 +619,6 @@ static void menu_process_up(Menu* menu) { } else { position = count - 1; } - vertical_offset = position; break; case MenuStyleWii: if(position % 2 || (position == count - 1 && count % 2)) { @@ -649,7 +626,6 @@ static void menu_process_up(Menu* menu) { } else { position++; } - vertical_offset = CLAMP(MAX((int)position - 4, 0), MAX((int)count - 8, 0), 0); break; case MenuStyleC64: case MenuStyleCompact: @@ -658,14 +634,11 @@ static void menu_process_up(Menu* menu) { } else { position = count - 1; } - vertical_offset = CLAMP(MAX((int)position - 4, 0), MAX((int)count - 8, 0), 0); break; default: break; } - - model->vertical_offset = vertical_offset; }, false); menu_set_selected_item(menu, position); @@ -679,7 +652,6 @@ static void menu_process_down(Menu* menu) { { position = model->position; size_t count = MenuItemArray_size(model->items); - size_t vertical_offset = model->vertical_offset; switch(momentum_settings.menu_style) { case MenuStyleList: @@ -689,7 +661,6 @@ static void menu_process_down(Menu* menu) { } else { position = 0; } - vertical_offset = position; break; case MenuStyleWii: if(position % 2 || (position == count - 1 && count % 2)) { @@ -697,7 +668,6 @@ static void menu_process_down(Menu* menu) { } else { position++; } - vertical_offset = CLAMP(MAX((int)position - 4, 0), MAX((int)count - 8, 0), 0); break; case MenuStyleC64: case MenuStyleCompact: @@ -706,14 +676,11 @@ static void menu_process_down(Menu* menu) { } else { position = 0; } - vertical_offset = CLAMP(MAX((int)position - 4, 0), MAX((int)count - 8, 0), 0); break; default: break; } - - model->vertical_offset = vertical_offset; }, false); menu_set_selected_item(menu, position); @@ -727,7 +694,6 @@ static void menu_process_left(Menu* menu) { { position = model->position; size_t count = MenuItemArray_size(model->items); - size_t vertical_offset = model->vertical_offset; switch(momentum_settings.menu_style) { case MenuStyleWii: @@ -740,11 +706,11 @@ static void menu_process_left(Menu* menu) { } else { position -= 2; } - vertical_offset = CLAMP(MAX((int)position - 4, 0), MAX((int)count - 8, 0), 0); break; case MenuStyleDsi: case MenuStylePs4: case MenuStyleVertical: + size_t vertical_offset = model->vertical_offset; if(position > 0) { position--; if(vertical_offset && vertical_offset == position) { @@ -754,6 +720,7 @@ static void menu_process_left(Menu* menu) { position = count - 1; vertical_offset = count - 8; } + model->vertical_offset = vertical_offset; break; case MenuStyleC64: if((position % 10) < 5) { @@ -761,7 +728,6 @@ static void menu_process_left(Menu* menu) { } else { position = position - 5; } - vertical_offset = CLAMP(MAX((int)position - 4, 0), MAX((int)count - 8, 0), 0); break; case MenuStyleCompact: if((position % 16) < 8) { @@ -769,14 +735,11 @@ static void menu_process_left(Menu* menu) { } else { position = position - 8; } - vertical_offset = CLAMP(MAX((int)position - 4, 0), MAX((int)count - 8, 0), 0); break; default: break; } - - model->vertical_offset = vertical_offset; }, false); menu_set_selected_item(menu, position); @@ -790,7 +753,6 @@ static void menu_process_right(Menu* menu) { { position = model->position; size_t count = MenuItemArray_size(model->items); - size_t vertical_offset = model->vertical_offset; switch(momentum_settings.menu_style) { case MenuStyleWii: @@ -808,11 +770,11 @@ static void menu_process_right(Menu* menu) { position = position % 2; } } - vertical_offset = CLAMP(MAX((int)position - 4, 0), MAX((int)count - 8, 0), 0); break; case MenuStyleDsi: case MenuStylePs4: case MenuStyleVertical: + size_t vertical_offset = model->vertical_offset; if(position < count - 1) { position++; if(vertical_offset < count - 8 && vertical_offset == position - 7) { @@ -822,6 +784,7 @@ static void menu_process_right(Menu* menu) { position = 0; vertical_offset = 0; } + model->vertical_offset = vertical_offset; break; case MenuStyleC64: if((position % 10) < 5) { @@ -829,7 +792,6 @@ static void menu_process_right(Menu* menu) { } else { position = position - 5; } - vertical_offset = CLAMP(MAX((int)position - 4, 0), MAX((int)count - 8, 0), 0); break; case MenuStyleCompact: if((position % 16) < 8) { @@ -837,14 +799,11 @@ static void menu_process_right(Menu* menu) { } else { position = position - 8; } - vertical_offset = CLAMP(MAX((int)position - 4, 0), MAX((int)count - 8, 0), 0); break; default: break; } - - model->vertical_offset = vertical_offset; }, false); menu_set_selected_item(menu, position); From 4c80a50cbeef87bc0d020acf00859a5027a9e9c8 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:36:54 +0000 Subject: [PATCH 23/40] Apps: Use OFW main app names, shorten in display --- .../archive/scenes/archive_scene_browser.c | 4 +-- .../main/archive/views/archive_browser_view.c | 2 +- applications/main/lfrfid/application.fam | 2 +- applications/main/momentum_app/momentum_app.c | 10 ++++-- applications/main/subghz/application.fam | 4 +-- applications/services/gui/modules/menu.c | 34 +++++++++++++------ applications/services/loader/loader.c | 24 +++++++------ 7 files changed, 50 insertions(+), 30 deletions(-) diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 0bfde1d9d3..3f37990d11 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -19,9 +19,9 @@ const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { case ArchiveFileTypeNFC: return "NFC"; case ArchiveFileTypeSubGhz: - return "SubGHz"; + return "Sub-GHz"; case ArchiveFileTypeLFRFID: - return "RFID"; + return "125 kHz RFID"; case ArchiveFileTypeInfrared: return "Infrared"; case ArchiveFileTypeSubghzPlaylist: diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 57eb8310d4..130895743c 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -13,7 +13,7 @@ static const char* ArchiveTabNames[] = { [ArchiveTabIButton] = "iButton", [ArchiveTabNFC] = "NFC", [ArchiveTabSubGhz] = "Sub-GHz", - [ArchiveTabLFRFID] = "RFID", + [ArchiveTabLFRFID] = "RFID LF", [ArchiveTabInfrared] = "Infrared", [ArchiveTabBadKb] = "Bad KB", [ArchiveTabU2f] = "U2F", diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index b444303230..f8d062f36e 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -1,6 +1,6 @@ App( appid="lfrfid", - name="RFID", + name="125 kHz RFID", apptype=FlipperAppType.MENUEXTERNAL, targets=["f7"], entry_point="lfrfid_app", diff --git a/applications/main/momentum_app/momentum_app.c b/applications/main/momentum_app/momentum_app.c index 5502364457..eb4e488ef6 100644 --- a/applications/main/momentum_app/momentum_app.c +++ b/applications/main/momentum_app/momentum_app.c @@ -17,7 +17,7 @@ bool momentum_app_apply(MomentumApp* app) { if(app->save_mainmenu_apps) { Stream* stream = file_stream_alloc(storage); if(file_stream_open(stream, MAINMENU_APPS_PATH, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) { - stream_write_format(stream, "MenuAppList Version %u\n", 0); + stream_write_format(stream, "MenuAppList Version %u\n", 1); CharList_it_t it; CharList_it(it, app->mainmenu_app_exes); for(size_t i = 0; i < CharList_size(app->mainmenu_app_exes); i++) { @@ -249,7 +249,13 @@ MomentumApp* momentum_app_alloc() { furi_string_replace_all(line, "\n", ""); CharList_push_back(app->mainmenu_app_exes, strdup(furi_string_get_cstr(line))); flipper_application_load_name_and_icon(line, storage, NULL, line); - if(furi_string_start_with_str(line, "[")) { + if(!furi_string_cmp(line, "Momentum")) { + furi_string_set(line, "MNTM"); + } else if(!furi_string_cmp(line, "125 kHz RFID")) { + furi_string_set(line, "RFID"); + } else if(!furi_string_cmp(line, "Sub-GHz")) { + furi_string_set(line, "SubGHz"); + } else if(furi_string_start_with_str(line, "[")) { size_t trim = furi_string_search_str(line, "] ", 1); if(trim != FURI_STRING_FAILURE) { furi_string_right(line, trim + 2); diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 9666625478..b7c768bd21 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -1,6 +1,6 @@ App( appid="subghz", - name="SubGHz", + name="Sub-GHz", apptype=FlipperAppType.APP, targets=["f7"], entry_point="subghz_app", @@ -23,7 +23,7 @@ App( App( appid="subghz_fap", - name="SubGHz", + name="Sub-GHz", apptype=FlipperAppType.EXTERNAL, entry_point="subghz_fap", stack_size=3 * 1024, diff --git a/applications/services/gui/modules/menu.c b/applications/services/gui/modules/menu.c index b472431b5a..a78d9d274a 100644 --- a/applications/services/gui/modules/menu.c +++ b/applications/services/gui/modules/menu.c @@ -44,11 +44,23 @@ static void menu_process_left(Menu* menu); static void menu_process_right(Menu* menu); static void menu_process_ok(Menu* menu); -static void menu_short_name(MenuItem* item, FuriString* name) { +static void menu_get_name(MenuItem* item, FuriString* name, bool shorter) { furi_string_set(name, item->label); + if(shorter) { + if(!furi_string_cmp(name, "Momentum")) { + furi_string_set(name, "MNTM"); + return; + } else if(!furi_string_cmp(name, "125 kHz RFID")) { + furi_string_set(name, "RFID"); + return; + } else if(!furi_string_cmp(name, "Sub-GHz")) { + furi_string_set(name, "SubGHz"); + return; + } + } if(furi_string_start_with_str(name, "[")) { size_t trim = furi_string_search_str(name, "] ", 1); - if(trim != STRING_FAILURE) { + if(trim != FURI_STRING_FAILURE) { furi_string_right(name, trim + 2); } } @@ -97,7 +109,7 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { shift_position = (position + items_count + i - 1) % items_count; item = MenuItemArray_get(model->items, shift_position); menu_centered_icon(canvas, item, 4, 3 + 22 * i, 14, 14); - menu_short_name(item, name); + menu_get_name(item, name, false); size_t scroll_counter = menu_scroll_counter(model, i == 1); elements_scrollable_text_line( canvas, 22, 14 + 22 * i, 98, name, scroll_counter, false); @@ -132,7 +144,7 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { } item = MenuItemArray_get(model->items, item_i); menu_centered_icon(canvas, item, x_off, y_off, 40, 20); - menu_short_name(item, name); + menu_get_name(item, name, true); size_t scroll_counter = menu_scroll_counter(model, selected); elements_scrollable_text_line_centered( canvas, 20 + x_off, 26 + y_off, 36, name, scroll_counter, false, true); @@ -174,7 +186,7 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontPrimary); - menu_short_name(item, name); + menu_get_name(item, name, false); size_t scroll_counter = menu_scroll_counter(model, true); elements_scrollable_text_line_centered( canvas, @@ -225,7 +237,7 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - menu_short_name(item, name); + menu_get_name(item, name, true); size_t scroll_counter = menu_scroll_counter(model, true); elements_scrollable_text_line( canvas, @@ -269,7 +281,7 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { } item = MenuItemArray_get(model->items, item_i); menu_centered_icon(canvas, item, 0, y_off, 16, 16); - menu_short_name(item, name); + menu_get_name(item, name, true); size_t scroll_counter = menu_scroll_counter(model, selected); elements_scrollable_text_line( canvas, 17, y_off + 12, 46, name, scroll_counter, false); @@ -307,7 +319,7 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorWhite); } item = MenuItemArray_get(model->items, index); - menu_short_name(item, name); + menu_get_name(item, name, true); char indexstr[5]; snprintf(indexstr, sizeof(indexstr), "%d.", index); @@ -343,7 +355,7 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorWhite); } item = MenuItemArray_get(model->items, index); - menu_short_name(item, name); + menu_get_name(item, name, true); elements_scrollable_text_line( canvas, x_off + 1, y_off + 7, 62, name, scroll_counter, false); @@ -383,7 +395,7 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { // Draw the selected menu item MenuItem* item = MenuItemArray_get(model->items, position); - menu_short_name(item, name); + menu_get_name(item, name, true); elements_bold_rounded_frame(canvas, 42, 23, 35, 33); menu_centered_icon(canvas, item, 43, 24, 35, 32); canvas_draw_frame(canvas, 0, 0, 128, 64); @@ -398,7 +410,7 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { for(size_t i = position; i < endItem; i++) { MenuItem* item = MenuItemArray_get(model->items, i); - menu_short_name(item, name); + menu_get_name(item, name, true); uint8_t yPos = startY + ((i - position) * itemHeight); size_t scroll_counter = menu_scroll_counter(model, i == position); elements_scrollable_text_line(canvas, 83, yPos, 43, name, scroll_counter, false); diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 7c6369febb..55ce3e22bb 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -208,7 +208,7 @@ static void loader_make_menu_file(Storage* storage) { Stream* new = file_stream_alloc(storage); if(!storage_file_exists(storage, MAINMENU_APPS_PATH)) { if(file_stream_open(new, MAINMENU_APPS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { - stream_write_format(new, "MenuAppList Version %u\n", 0); + stream_write_format(new, "MenuAppList Version %u\n", 1); for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { stream_write_format(new, "%s\n", FLIPPER_APPS[i].name); } @@ -258,7 +258,7 @@ static Loader* loader_alloc() { uint32_t version; if(!stream_read_line(stream, line) || sscanf(furi_string_get_cstr(line), "MenuAppList Version %lu", &version) != 1 || - version > 0) { + version > 1) { file_stream_close(stream); storage_common_remove(storage, MAINMENU_APPS_PATH); loader_make_menu_file(storage); @@ -266,13 +266,20 @@ static Loader* loader_alloc() { break; if(!stream_read_line(stream, line) || sscanf(furi_string_get_cstr(line), "MenuAppList Version %lu", &version) != 1 || - version > 0) + version > 1) break; } while(stream_read_line(stream, line)) { furi_string_replace_all(line, "\r", ""); furi_string_replace_all(line, "\n", ""); + if(version == 0) { + if(!furi_string_cmp(line, "RFID")) { + furi_string_set(line, "125 kHz RFID"); + } else if(!furi_string_cmp(line, "SubGHz")) { + furi_string_set(line, "Sub-GHz"); + } + } const char* label = NULL; const Icon* icon = NULL; const char* exe = NULL; @@ -574,15 +581,10 @@ static LoaderStatus loader_do_start_by_name( break; } - // Translate app names (mainly for RPC, thanks OFW for not using a smart system like appid's :/) - if(!strncmp(name, "Bad USB", strlen("Bad USB"))) + // Translate app names (mainly for RPC) + if(!strncmp(name, "Bad USB", strlen("Bad USB"))) { name = "Bad KB"; - else if(!strncmp(name, "Applications", strlen("Applications"))) - name = "Apps"; - else if(!strncmp(name, "125 kHz RFID", strlen("125 kHz RFID"))) - name = "RFID"; - else if(!strncmp(name, "Sub-GHz", strlen("Sub-GHz"))) - name = "SubGHz"; + } // check internal apps { From a5ae1397d910493f3793510139fdc214c9674522 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 19:42:12 +0000 Subject: [PATCH 24/40] FindMy: Resume at system boot if enabled --- applications/system/application.fam | 1 + applications/system/findmy/application.fam | 9 +++++++++ applications/system/findmy/findmy_startup.c | 11 +++++++++++ 3 files changed, 21 insertions(+) create mode 100644 applications/system/findmy/findmy_startup.c diff --git a/applications/system/application.fam b/applications/system/application.fam index 2d4dfcbb82..d4d36908c9 100644 --- a/applications/system/application.fam +++ b/applications/system/application.fam @@ -6,6 +6,7 @@ App( "updater_app", "storage_move_to_sd", "js_app", + "findmy_startup", # "archive", ], ) diff --git a/applications/system/findmy/application.fam b/applications/system/findmy/application.fam index 2ef49310f0..3edafeb62d 100644 --- a/applications/system/findmy/application.fam +++ b/applications/system/findmy/application.fam @@ -13,3 +13,12 @@ App( fap_version="1.0", fap_description="BLE FindMy Location Beacon", ) + +App( + appid="findmy_startup", + targets=["f7"], + apptype=FlipperAppType.STARTUP, + entry_point="findmy_startup", + sources=["findmy_startup.c", "findmy_state.c"], + order=1000, +) diff --git a/applications/system/findmy/findmy_startup.c b/applications/system/findmy/findmy_startup.c new file mode 100644 index 0000000000..c24eaa7ce1 --- /dev/null +++ b/applications/system/findmy/findmy_startup.c @@ -0,0 +1,11 @@ +#include "findmy_state.h" +#include + +void findmy_startup() { + if(!furi_hal_is_normal_boot()) return; + + FindMyState state; + if(findmy_state_load(&state)) { + findmy_state_apply(&state); + } +} From 1274bd84acedee371511c8c445d9ba9a69d6f6a0 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 23:24:04 +0000 Subject: [PATCH 25/40] VGM: Add rainbow option to settings menu --- .../main/momentum_app/scenes/momentum_app_scene_misc_vgm.c | 1 + lib/momentum/momentum.h | 1 + 2 files changed, 2 insertions(+) diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_misc_vgm.c b/applications/main/momentum_app/scenes/momentum_app_scene_misc_vgm.c index 3091da0198..4c6d9a65aa 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_misc_vgm.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_misc_vgm.c @@ -14,6 +14,7 @@ void momentum_app_scene_misc_vgm_var_item_list_callback(void* context, uint32_t const char* const colors_names[VgmColorModeCount] = { "Default", "Custom", + "Rainbow", "RGB Backlight", }; static void momentum_app_scene_misc_vgm_colors_changed(VariableItem* item) { diff --git a/lib/momentum/momentum.h b/lib/momentum/momentum.h index 9b3974fd87..e2555c99c7 100644 --- a/lib/momentum/momentum.h +++ b/lib/momentum/momentum.h @@ -47,6 +47,7 @@ typedef enum { typedef enum { VgmColorModeDefault, VgmColorModeCustom, + VgmColorModeRainbow, VgmColorModeRgbBacklight, VgmColorModeCount, } VgmColorMode; From a2018ff365b723af363dac3011958b8a5d6d7492 Mon Sep 17 00:00:00 2001 From: HaxSam Date: Sun, 10 Mar 2024 01:38:29 +0100 Subject: [PATCH 26/40] sync apps --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index 95c4cc640e..a3b7d50469 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 95c4cc640e1c0a85bbbe352921d6607250080ada +Subproject commit a3b7d504694797cbe18c3b70f5cad848e97ec732 From 91d18ad32f56f13228e9f1b26d0f656c3881ba5f Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 10 Mar 2024 01:48:14 +0000 Subject: [PATCH 27/40] Add some stuff to features --- ReadMe.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index 81098f41f4..998e364616 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -93,12 +93,14 @@ Note that this repo is always updated with the great work from our friends at [U - More UI customization, redesigns and optimizations - Bad-Keyboard App - BLE Spam App +- FindMy App - NFC Maker App - Wardriver App - File Search across SD Card - Additional NFC parsers and protocols - Subdriving (saving GPS coordinates for Sub-GHz) - Easy spoofing (Name, MAC address, Serial number) +- Video Game Module color configuration right from Flipper - Enhanced RGB Backlight modes (Full customization & Rainbow mode) - File management on device (Cut, Copy, Paste, Show, New Dir, etc.) - Remember Infrared GPIO settings and add IR Blaster support in apps From 33aab8c4aa09c07411df29eeb84a9f9fe1d47c84 Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Sun, 10 Mar 2024 05:49:43 +0100 Subject: [PATCH 28/40] Update AssetPacks.md (naming fix) (#19) --- documentation/file_formats/AssetPacks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/file_formats/AssetPacks.md b/documentation/file_formats/AssetPacks.md index 1a319c7a6f..d7c9aea3b2 100644 --- a/documentation/file_formats/AssetPacks.md +++ b/documentation/file_formats/AssetPacks.md @@ -6,7 +6,7 @@ Asset Packs are an exclusive feature of Momentum Firmware that allows you to loa ## How to install Asset Packs? -Installing Asset Packs is quite easy and straightforward. First, make sure you're on an updated version of XFW before you begin, Asset Packs were added in v40! Then, find some packs to install (we have a channel in our discord where you can find some) or make your own (see below). Once you have some packs to install: +Installing Asset Packs is quite easy and straightforward. First, make sure you're on an updated version of Momentum before you begin, Asset Packs were added in v40! Then, find some packs to install (we have a channel in our discord where you can find some) or make your own (see below). Once you have some packs to install: - Open qFlipper and navigate to `SD Card` and into `asset_packs`; if you do not see this folder, try reinstalling the firmware, or create it yourself. @@ -56,7 +56,7 @@ SD/ Again, this is all fairly standard Flipper animation stuff, there are plenty of tutorials on YouTube. The key differences with the Asset Pack animation system are: - They go in `SD/asset_packs/PackName/Anims` instead of `SD/dolphin`. -- XFW has up to level 30, so make sure to update your manifest.txt accordingly! +- Momentum has up to level 30, so make sure to update your manifest.txt accordingly!
From d0b32f11242e25fb897ed7aae1a0a9a8194dd4e1 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 10 Mar 2024 10:17:59 +0000 Subject: [PATCH 29/40] New build system --- .github/workflow_data/commit.sh | 4 - .github/workflow_data/package.sh | 18 ---- .github/workflow_data/version.sh | 13 --- .github/workflow_data/webupdater.py | 36 -------- .github/workflows/build.yml | 81 +++++++++++++----- .github/workflows/lint.yml | 9 +- .github/workflows/reindex.yml | 27 ++++++ .github/workflows/release.yml | 83 ------------------- .github/workflows/sonarcloud.yaml | 65 --------------- .github/workflows/submodules.yml | 42 ---------- .github/workflows/webhook.yml | 5 +- applications/main/momentum_app/momentum_app.c | 9 +- fbt_options.py | 2 +- scripts/version.py | 2 +- 14 files changed, 101 insertions(+), 295 deletions(-) delete mode 100644 .github/workflow_data/commit.sh delete mode 100644 .github/workflow_data/package.sh delete mode 100644 .github/workflow_data/version.sh delete mode 100644 .github/workflow_data/webupdater.py create mode 100644 .github/workflows/reindex.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/sonarcloud.yaml delete mode 100644 .github/workflows/submodules.yml diff --git a/.github/workflow_data/commit.sh b/.github/workflow_data/commit.sh deleted file mode 100644 index ddfe7040fb..0000000000 --- a/.github/workflow_data/commit.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -export VERSION_TAG="$(python -c 'import fbt_options; print(fbt_options.DIST_SUFFIX, end="")')" -echo "VERSION_TAG=${VERSION_TAG}" >> $GITHUB_ENV diff --git a/.github/workflow_data/package.sh b/.github/workflow_data/package.sh deleted file mode 100644 index 02fa15b481..0000000000 --- a/.github/workflow_data/package.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -export ARTIFACT_DIR="${VERSION_TAG}" - -export ARTIFACT_TGZ="${VERSION_TAG}.tgz" -export ARTIFACT_ZIP="${VERSION_TAG}.zip" -export ARTIFACT_SDK="${VERSION_TAG}-sdk.zip" -cd dist/${DEFAULT_TARGET}-* -mv ${DEFAULT_TARGET}-update-* ${ARTIFACT_DIR} -tar --format=ustar -czvf ../../${ARTIFACT_TGZ} ${ARTIFACT_DIR} -cd ${ARTIFACT_DIR} -7z a ../../../${ARTIFACT_ZIP} . -cd .. -mv flipper-z-${DEFAULT_TARGET}-sdk-*.zip ../../${ARTIFACT_SDK} -cd ../.. - -echo "ARTIFACT_TGZ=${ARTIFACT_TGZ}" >> $GITHUB_ENV -echo "ARTIFACT_ZIP=${ARTIFACT_ZIP}" >> $GITHUB_ENV -echo "ARTIFACT_SDK=${ARTIFACT_SDK}" >> $GITHUB_ENV diff --git a/.github/workflow_data/version.sh b/.github/workflow_data/version.sh deleted file mode 100644 index 2e98c4b049..0000000000 --- a/.github/workflow_data/version.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -export VERSION_TAG="$(python -c ''' -import datetime as dt -import json -import os -with open(os.environ["GITHUB_EVENT_PATH"], "r") as f: - event = json.load(f) -version = int(event["pull_request"]["title"].removeprefix("V").removesuffix(" Release") -date = dt.datetime.now().strftime("%d%m%Y") -print(f"MNTM-{version:03}_{date}", end="") -''')" -echo "VERSION_TAG=${VERSION_TAG}" >> $GITHUB_ENV diff --git a/.github/workflow_data/webupdater.py b/.github/workflow_data/webupdater.py deleted file mode 100644 index b30fc17fe3..0000000000 --- a/.github/workflow_data/webupdater.py +++ /dev/null @@ -1,36 +0,0 @@ -import nextcloud_client -import requests -import json -import os - -if __name__ == "__main__": - client = nextcloud_client.Client(os.environ["NC_HOST"]) - client.login(os.environ["NC_USER"], os.environ["NC_PASS"]) - - file = os.environ["ARTIFACT_TGZ"] - path = f"MNTM-Release/{file}" - try: - client.delete(path) - except Exception: - pass - client.put_file(path, file) - - file = file.removesuffix(".tgz") + ".md" - path = path.removesuffix(".tgz") + ".md" - try: - client.delete(path) - except Exception: - pass - client.put_file(path, file) - - version = os.environ['VERSION_TAG'].split("_")[0] - files = ( - os.environ['ARTIFACT_TGZ'], - os.environ['ARTIFACT_TGZ'].removesuffix(".tgz") + ".md" - ) - for file in client.list("MNTM-Release"): - if file.name.startswith(version) and file.name not in files: - try: - client.delete(file.path) - except Exception: - pass diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d054252155..42bb8a8e21 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,12 +1,11 @@ -name: 'Build' +name: "Build" on: push: branches: - dev - - main tags: - - '*' + - "*" pull_request: concurrency: @@ -14,45 +13,87 @@ concurrency: cancel-in-progress: true env: - TARGETS: f7 DEFAULT_TARGET: f7 FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: - build: + main: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: [f7] steps: - - - name: 'Checkout code' + - name: "Checkout code" uses: actions/checkout@v4 with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - - name: "Read version tag" - run: bash .github/workflow_data/commit.sh + - name: "Get commit details" + id: names + run: | + BUILD_TYPE='DEBUG=0 COMPACT=1' + if [[ ${{ github.event_name }} == 'pull_request' ]]; then + TYPE="pull" + elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + TYPE="tag" + else + TYPE="other" + fi + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" + echo "event_type=$TYPE" >> $GITHUB_OUTPUT + echo "FBT_BUILD_TYPE=$BUILD_TYPE" >> $GITHUB_ENV + echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV + echo "TARGET_HW=$(echo "${{ matrix.target }}" | sed 's/f//')" >> $GITHUB_ENV - - name: 'Build the firmware' + - name: "Check API versions for consistency between targets" run: | set -e - for TARGET in ${TARGETS}; do - TARGET_HW="$(echo "${TARGET}" | sed 's/f//')"; \ - ./fbt TARGET_HW=$TARGET_HW DIST_SUFFIX=$VERSION_TAG updater_package - done + N_API_HEADER_SIGNATURES=`ls -1 targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l` + if [ $N_API_HEADER_SIGNATURES != 1 ] ; then + echo API versions aren\'t matching for available targets. Please update! + echo API versions are: + head -n2 targets/f*/api_symbols.csv + exit 1 + fi + + - name: "Build the firmware and apps" + id: build-fw + run: | + ./fbt TARGET_HW=$TARGET_HW $FBT_BUILD_TYPE updater_package + echo "firmware_api=$(./fbt TARGET_HW=$TARGET_HW get_apiversion)" >> $GITHUB_OUTPUT - name: "Check for uncommitted changes" run: | git diff --exit-code - - name: 'Dist artifact' + - name: "Upload artifacts to GitHub" uses: actions/upload-artifact@v3 with: - name: dist path: | - dist/${{ env.DEFAULT_TARGET }}-*/ + dist/${TARGET}-*/flipper-z-${TARGET}-update-* + dist/${TARGET}-*/flipper-z-${TARGET}-sdk-* - - name: "Make tgz, zip and sdk" - run: bash .github/workflow_data/package.sh + - name: "Copy build output" + run: | + set -e + rm -rf artifacts || true + mkdir artifacts + cp dist/${TARGET}-*/flipper-z-${TARGET}-{update,sdk}-* artifacts/ + cd dist/${TARGET}-*/${TARGET}-update-*/ + artifact="$basename "$(realpath .)")" + 7z a ../../../artifacts/flipper-z-${artifact}.zip . + + - name: "Upload artifacts to update server" + if: ${{ !github.event.pull_request.head.repo.fork }} + run: | + FILES=$(for ARTIFACT in $(find artifacts -maxdepth 1 -not -type d); do echo "-F files=@${ARTIFACT}"; done) + curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ + -F "branch=${BRANCH_NAME}" \ + -F "version_token=${COMMIT_SHA}" \ + ${FILES[@]} \ + "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles # - name: Send devbuild webhook # if: "github.event_name == 'push' && github.ref_name == 'dev' && !contains(github.event.head_commit.message, '--nobuild')" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index da9ca2a87c..f2aab96c89 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: 'Lint' +name: "Lint" on: push: @@ -6,7 +6,7 @@ on: - dev - main tags: - - '*' + - "*" pull_request: env: @@ -16,12 +16,11 @@ jobs: lint: runs-on: ubuntu-latest steps: - - - name: 'Checkout code' + - name: "Checkout code" uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - - name: 'Check code formatting' + - name: "Check code formatting" run: ./fbt lint lint_py diff --git a/.github/workflows/reindex.yml b/.github/workflows/reindex.yml new file mode 100644 index 0000000000..49e284d389 --- /dev/null +++ b/.github/workflows/reindex.yml @@ -0,0 +1,27 @@ +name: "Post-release hooks" + +on: + release: + types: [prereleased, released] + +jobs: + reindex: + name: "Post-release hooks" + runs-on: ubuntu-latest + steps: + - name: "Checkout code" + uses: actions/checkout@v4 + + - name: "Trigger reindex" + run: | + curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ + "${{ secrets.INDEXER_URL }}"/firmware/reindex; + + # - name: "Send release notification" + # if: ${{ github.event.action == 'released' }} + # run: | + # echo '${{ secrets.FIREBASE_TOKEN }}' > firebase-token.json; + # python3 -m pip install firebase-admin==6.4.0; + # python3 scripts/send_firebase_notification.py \ + # "--version=${{ github.event.release.name }}" \ + # "--token=firebase-token.json"; diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 1cfc54eb87..0000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: 'Release' - -on: - pull_request_review: - types: [submitted] - -env: - TARGETS: f7 - DEFAULT_TARGET: f7 - -jobs: - release: - if: | - github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name && - endsWith(github.event.pull_request.title, ' Release') && - github.event.review.author_association == 'MEMBER' && - startsWith(github.event.pull_request.title, 'V') && - github.event.pull_request.base.ref == 'main' && - github.event.pull_request.head.ref == 'dev' && - github.event.pull_request.state == 'open' && - github.event.pull_request.draft == false && - github.event.review.state == 'APPROVED' - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - - name: 'Checkout code' - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - - - name: "Read version tag" - run: bash .github/workflow_data/version.sh - - - name: 'Build the firmware' - run: | - set -e - for TARGET in ${TARGETS}; do - TARGET_HW="$(echo "${TARGET}" | sed 's/f//')"; \ - ./fbt TARGET_HW=$TARGET_HW DIST_SUFFIX=$VERSION_TAG updater_package - done - - - name: "Check for uncommitted changes" - run: | - git diff --exit-code - - - name: "Make tgz, zip and sdk" - run: bash .github/workflow_data/package.sh - - - name: "Update release notes" - run: python .github/workflow_data/release.py - - - name: "Upload to webupdater" - env: - NC_HOST: "https://cloud.cynthialabs.net/" - NC_USERAGENT: "${{ secrets.NC_USERAGENT }}" - NC_USER: "${{ secrets.NC_USER }}" - NC_PASS: "${{ secrets.NC_PASS }}" - run: | - python -m pip install pyncclient - python .github/workflow_data/webupdater.py - - - name: "Merge pull request" - uses: "pascalgn/automerge-action@v0.15.6" - env: - MERGE_LABELS: "" - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - - name: "Make release" - uses: softprops/action-gh-release@v1 - with: - body_path: ".github/workflow_data/release.md" - draft: false - prerelease: false - files: | - ${{ env.ARTIFACT_TGZ }} - ${{ env.ARTIFACT_ZIP }} - ${{ env.ARTIFACT_SDK }} - name: "${{ env.VERSION_TAG }}" - tag_name: "${{ env.VERSION_TAG }}" - target_commitish: ${{ github.event.pull_request.base.ref }} diff --git a/.github/workflows/sonarcloud.yaml b/.github/workflows/sonarcloud.yaml deleted file mode 100644 index 766a3487da..0000000000 --- a/.github/workflows/sonarcloud.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: 'SonarCloud' - -on: - workflow_dispatch: - # pull_request: - # types: [opened, synchronize, reopened] - -env: - TARGETS: f7 - DEFAULT_TARGET: f7 - -jobs: - sonarcloud: - runs-on: ubuntu-latest - env: - SONAR_SCANNER_VERSION: 4.7.0.2747 - SONAR_SERVER_URL: "https://sonarcloud.io" - BUILD_WRAPPER_OUT_DIR: "$HOME/.sonar/build_wrapper_output" # Directory where build-wrapper output will be placed - FBT_NO_SYNC: "true" - steps: - - - name: 'Checkout code' - uses: actions/checkout@v3 - with: - submodules: 'recursive' # FBT_NO_SYNC is on, get submodules now - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - ref: ${{ github.event.pull_request.head.sha }} - - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - - name: Download and set up sonar-scanner - env: - SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip - run: | - mkdir -p $HOME/.sonar - curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }} - unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/ - echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH - - - name: Download and set up build-wrapper - env: - BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip - run: | - curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }} - unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/ - echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH - - - name: Run build-wrapper - run: | - mkdir ${{ env.BUILD_WRAPPER_OUT_DIR }} - set -e - for TARGET in ${TARGETS}; do - TARGET_HW="$(echo "${TARGET}" | sed 's/f//')"; \ - build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} ./sonar-build "./fbt TARGET_HW=$TARGET_HW updater_package" - done - - - name: Run sonar-scanner - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: | - sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" diff --git a/.github/workflows/submodules.yml b/.github/workflows/submodules.yml deleted file mode 100644 index 52342fd04e..0000000000 --- a/.github/workflows/submodules.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: 'Submodules' - -on: - push: - branches: - - dev - - main - tags: - - '*' - pull_request: - -jobs: - submodules: - runs-on: ubuntu-latest - steps: - - - name: 'Checkout code' - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - - - name: 'Check protobuf branch' - run: | - git submodule update --init - SUB_PATH="assets/protobuf"; - SUB_BRANCH="dev"; - SUB_COMMITS_MIN=40; - cd "$SUB_PATH"; - SUBMODULE_HASH="$(git rev-parse HEAD)"; - BRANCHES=$(git branch -r --contains "$SUBMODULE_HASH"); - COMMITS_IN_BRANCH="$(git rev-list --count dev)"; - if [ $COMMITS_IN_BRANCH -lt $SUB_COMMITS_MIN ]; then - echo "name=fails::error" >> $GITHUB_OUTPUT - echo "::error::Error: Too low commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)"; - exit 1; - fi - if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then - echo "name=fails::error" >> $GITHUB_OUTPUT - echo "::error::Error: Submodule $SUB_PATH is not on branch $SUB_BRANCH"; - exit 1; - fi diff --git a/.github/workflows/webhook.yml b/.github/workflows/webhook.yml index 1de0b1de2d..4ad738611f 100644 --- a/.github/workflows/webhook.yml +++ b/.github/workflows/webhook.yml @@ -1,4 +1,4 @@ -name: 'Webhook' +name: "Webhook" on: push: @@ -32,8 +32,7 @@ jobs: webhook: runs-on: ubuntu-latest steps: - - - name: 'Checkout code' + - name: "Checkout code" uses: actions/checkout@v3 - name: Send webhook diff --git a/applications/main/momentum_app/momentum_app.c b/applications/main/momentum_app/momentum_app.c index eb4e488ef6..94ee34ee70 100644 --- a/applications/main/momentum_app/momentum_app.c +++ b/applications/main/momentum_app/momentum_app.c @@ -309,15 +309,16 @@ MomentumApp* momentum_app_alloc() { app->dolphin_angry = stats.butthurt; furi_record_close(RECORD_DOLPHIN); - if(strcmp(version_get_version(NULL), "MNTM-DEV") == 0) { - app->version_tag = furi_string_alloc_printf("%s ", version_get_version(NULL)); + app->version_tag = furi_string_alloc_printf("%s ", version_get_version(NULL)); + if(furi_string_start_with(app->version_tag, "mntm-dev")) { + furi_string_set(app->version_tag, "MNTM-DEV "); const char* sha = version_get_githash(NULL); for(size_t i = 0; i < strlen(sha); ++i) { furi_string_push_back(app->version_tag, toupper(sha[i])); } } else { - app->version_tag = furi_string_alloc_printf( - "%s %s", version_get_version(NULL), version_get_builddate(NULL)); + furi_string_replace(app->version_tag, "mntm", "MNTM"); + furi_string_cat(app->version_tag, version_get_builddate(NULL)); } return app; diff --git a/fbt_options.py b/fbt_options.py index 2753844ecf..d6b50519c2 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -18,7 +18,7 @@ # Suffix to add to files when building distribution # If OS environment has DIST_SUFFIX set, it will be used instead -DIST_SUFFIX = f"MNTM-DEV_@{subprocess.check_output(['git', 'rev-parse', '--short=7', 'HEAD']).decode().strip().upper()}" +DIST_SUFFIX = f"mntm-dev-{subprocess.check_output(['git', 'rev-parse', '--short=7', 'HEAD']).decode().strip()}" # Coprocessor firmware COPRO_OB_DATA = "scripts/ob.data" diff --git a/scripts/version.py b/scripts/version.py index c017056142..84294acc0f 100755 --- a/scripts/version.py +++ b/scripts/version.py @@ -36,7 +36,7 @@ def get_version_info(self): ) version = ( - self.suffix.split("_")[0] + self.suffix or os.environ.get("DIST_SUFFIX", None) or "unknown" ) From e755bdb4f14a17725b38bc35ca0c74fca1a797d3 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 10 Mar 2024 10:24:00 +0000 Subject: [PATCH 30/40] Fix typos --- .github/workflows/build.yml | 2 +- ReadMe.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 42bb8a8e21..b502ddfaa7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,7 +82,7 @@ jobs: mkdir artifacts cp dist/${TARGET}-*/flipper-z-${TARGET}-{update,sdk}-* artifacts/ cd dist/${TARGET}-*/${TARGET}-update-*/ - artifact="$basename "$(realpath .)")" + artifact="$(basename "$(realpath .)")" 7z a ../../../artifacts/flipper-z-${artifact}.zip . - name: "Upload artifacts to update server" diff --git a/ReadMe.md b/ReadMe.md index 998e364616..97fa3b09e6 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -93,7 +93,7 @@ Note that this repo is always updated with the great work from our friends at [U - More UI customization, redesigns and optimizations - Bad-Keyboard App - BLE Spam App -- FindMy App +- FindMy Flipper App - NFC Maker App - Wardriver App - File Search across SD Card From 61471a631c1d45abd7aecd7794351e9302702d67 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 10 Mar 2024 10:43:09 +0000 Subject: [PATCH 31/40] Use normal DIST_SUFFIX in CI --- site_scons/environ.scons | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/site_scons/environ.scons b/site_scons/environ.scons index ece8de2122..e904ef1621 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -55,10 +55,10 @@ coreenv = VAR_ENV.Clone( ) # If DIST_SUFFIX is set in environment, is has precedence (set by CI) -if os_suffix := os.environ.get("DIST_SUFFIX", None): - coreenv.Replace( - DIST_SUFFIX=os_suffix, - ) +# if os_suffix := os.environ.get("DIST_SUFFIX", None): +# coreenv.Replace( +# DIST_SUFFIX=os_suffix, +# ) # Default value for commandline options From 20e873f5c584ef398c97ee5d84e5ca19840f6dcb Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 10 Mar 2024 11:00:37 +0000 Subject: [PATCH 32/40] Devbuild embed (?) --- .github/workflow_data/devbuild.py | 38 +++++++------------------------ .github/workflows/build.yml | 23 ++++++++----------- 2 files changed, 18 insertions(+), 43 deletions(-) diff --git a/.github/workflow_data/devbuild.py b/.github/workflow_data/devbuild.py index 2f00e8a874..a3c05f9545 100644 --- a/.github/workflow_data/devbuild.py +++ b/.github/workflow_data/devbuild.py @@ -1,31 +1,17 @@ #!/usr/bin/env python -import nextcloud_client import datetime as dt import requests import json import os -dev_share_id = "" -dev_share = os.environ["NC_HOST"] + f"s/{dev_share_id}/download?path=/&files={{files}}" +artifact_tgz = f"{os.environ['INDEXER_URL']}/firmware/dev/{os.environ['ARTIFACT_TAG']}.tgz" +artifact_sdk = f"{os.environ['INDEXER_URL']}/firmware/dev/{os.environ['ARTIFACT_TAG'].replace('update', 'sdk')}.zip" + if __name__ == "__main__": with open(os.environ["GITHUB_EVENT_PATH"], "r") as f: event = json.load(f) - client = nextcloud_client.Client(os.environ["NC_HOST"]) - client.login(os.environ["NC_USER"], os.environ["NC_PASS"]) - - for file in ( - os.environ["ARTIFACT_TGZ"], - os.environ["ARTIFACT_SDK"], - ): - path = f"MNTM-Dev/{file}" - # try: - # client.delete(path) - # except Exception: - # pass - client.put_file(path, file) - requests.post( os.environ["BUILD_WEBHOOK"], headers={"Accept": "application/json", "Content-Type": "application/json"}, @@ -33,10 +19,10 @@ "content": None, "embeds": [ { - "title": "New Devbuild:", + "title": "New Devbuild!", "description": "", "url": "", - "color": 16734443, + "color": 11761899, "fields": [ { "name": "Changes since last commit:", @@ -44,21 +30,13 @@ }, { "name": "Changes since last release:", - "value": f"[Compare release to {event['after'][:7]}]({event['compare'].rsplit('/', 1)[0] + '/main...' + event['after']})" + "value": f"[Compare release to {event['after'][:7]}]({event['compare'].rsplit('/', 1)[0] + '/release...' + event['after']})" }, { - "name": "Firmware download:", - "value": f"- [Download SDK for development]({dev_share.format(files=os.environ['ARTIFACT_SDK'])})\n- [Download Firmware TGZ]({dev_share.format(files=os.environ['ARTIFACT_TGZ'])})" + "name": "Download artifacts:", + "value": f"- [Download Firmware TGZ]({artifact_tgz})\n- [SDK (for development)]({artifact_sdk})" } ], - "author": { - "name": "Build Succeeded!", - # "icon_url": "" - }, - # "footer": { - # "text": "Build go brrrr", - # "icon_url": "" - # }, "timestamp": dt.datetime.utcnow().isoformat() } ], diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b502ddfaa7..0acd3319ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,8 +82,9 @@ jobs: mkdir artifacts cp dist/${TARGET}-*/flipper-z-${TARGET}-{update,sdk}-* artifacts/ cd dist/${TARGET}-*/${TARGET}-update-*/ - artifact="$(basename "$(realpath .)")" - 7z a ../../../artifacts/flipper-z-${artifact}.zip . + ARTIFACT_TAG=flipper-z-"$(basename "$(realpath .)")" + 7z a ../../../artifacts/${ARTIFACT_TAG}.zip . + echo "ARTIFACT_TAG=$ARTIFACT_TAG" >> $GITHUB_ENV - name: "Upload artifacts to update server" if: ${{ !github.event.pull_request.head.repo.fork }} @@ -95,14 +96,10 @@ jobs: ${FILES[@]} \ "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles - # - name: Send devbuild webhook - # if: "github.event_name == 'push' && github.ref_name == 'dev' && !contains(github.event.head_commit.message, '--nobuild')" - # env: - # NC_HOST: "https://cloud.cynthialabs.net/" - # NC_USERAGENT: "${{ secrets.NC_USERAGENT }}" - # NC_USER: "${{ secrets.NC_USER }}" - # NC_PASS: "${{ secrets.NC_PASS }}" - # BUILD_WEBHOOK: ${{ secrets.BUILD_WEBHOOK }} - # run: | - # python -m pip install pyncclient - # python .github/workflow_data/devbuild.py + - name: Send devbuild webhook + if: "github.event_name == 'push' && github.ref_name == 'dev' && !contains(github.event.head_commit.message, '--nobuild')" + env: + INDEXER_URL: ${{ secrets.INDEXER_URL }} + BUILD_WEBHOOK: ${{ secrets.BUILD_WEBHOOK }} + run: | + python .github/workflow_data/devbuild.py From 7ff3ea036414198adf71c1aafb947dd7d4de5d3b Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 10 Mar 2024 11:17:32 +0000 Subject: [PATCH 33/40] No more "--nobuild", indexer wouldn't work --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0acd3319ef..8c37c5b5aa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,7 +97,7 @@ jobs: "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles - name: Send devbuild webhook - if: "github.event_name == 'push' && github.ref_name == 'dev' && !contains(github.event.head_commit.message, '--nobuild')" + if: "github.event_name == 'push' && github.ref_name == 'dev'" env: INDEXER_URL: ${{ secrets.INDEXER_URL }} BUILD_WEBHOOK: ${{ secrets.BUILD_WEBHOOK }} From c0ed5bcd85f53459f772f216196df30fa1f09598 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 10 Mar 2024 11:17:42 +0000 Subject: [PATCH 34/40] Format --- .github/workflow_data/webhook.py | 44 -------------------------------- .github/workflows/build.yml | 2 +- .github/workflows/docs.yml | 19 +++++++------- .github/workflows/lint.yml | 1 - .github/workflows/webhook.yml | 5 ---- scripts/version.py | 6 +---- 6 files changed, 11 insertions(+), 66 deletions(-) diff --git a/.github/workflow_data/webhook.py b/.github/workflow_data/webhook.py index aca529c6c9..b9a03a0359 100644 --- a/.github/workflow_data/webhook.py +++ b/.github/workflow_data/webhook.py @@ -43,50 +43,6 @@ url = event["compare"] color = 16723712 if event["forced"] else 3669797 - # case "pull_request": - # pr = event["pull_request"] - # url = pr["html_url"] - # branch = pr["base"]["ref"] + ( - # "" - # if pr["base"]["repo"]["full_name"] != pr["head"]["repo"]["full_name"] - # else f" <- {pr['head']['ref']}" - # ) - # name = pr["title"][:50] + ("..." if len(pr["title"]) > 50 else "") - # title = f"Pull Request {event['action'].title()} ({branch}): {name}" - # match event["action"]: - # case "opened": - # desc = (pr["body"][:2045] + "...") if len(pr["body"]) > 2048 else pr["body"] - # color = 3669797 - - # fields.append( - # { - # "name": "Changed Files:", - # "value": str(pr["changed_files"]), - # "inline": True, - # } - # ) - # fields.append( - # { - # "name": "Added:", - # "value": "+" + str(pr["additions"]), - # "inline": True, - # } - # ) - # fields.append( - # { - # "name": "Removed:", - # "value": "-" + str(pr["deletions"]), - # "inline": True, - # } - # ) - - # case "closed": - # color = 16723712 - # case "reopened": - # color = 16751872 - # case _: - # sys.exit(1) - case "release": match event["action"]: case "published": diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c37c5b5aa..4a6af091fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ env: FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: - main: + build: runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e6e53ee016..bb058d07cb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,4 +1,4 @@ -name: 'Generate documentation with Doxygen' +name: "Docs" on: push: @@ -10,21 +10,21 @@ env: DEFAULT_TARGET: f7 jobs: - doxygen: + docs: if: ${{ !github.event.pull_request.head.repo.fork }} runs-on: ubuntu-latest steps: - - name: 'Wipe workspace' + - name: "Wipe workspace" run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - - name: 'Checkout code' + - name: "Checkout code" uses: actions/checkout@v4 with: submodules: true fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - - name: 'Get commit details' + - name: "Get commit details" id: names run: | if [[ ${{ github.event_name }} == 'pull_request' ]]; then @@ -36,13 +36,13 @@ jobs: fi python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - - name: 'Generate documentation' + - name: "Generate documentation" uses: mattnotmitt/doxygen-action@v1.9.8 with: - working-directory: 'documentation/' - doxyfile-path: './doxygen/Doxyfile-awesome.cfg' + working-directory: "documentation/" + doxyfile-path: "./doxygen/Doxyfile-awesome.cfg" - - name: 'Upload documentation' + - name: "Upload documentation" uses: jakejarvis/s3-sync-action@v0.5.1 env: AWS_S3_BUCKET: "${{ secrets.FW_DOCS_AWS_BUCKET }}" @@ -53,4 +53,3 @@ jobs: DEST_DIR: "${{steps.names.outputs.branch_name}}" with: args: "--delete" - diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f2aab96c89..0fd4af0085 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,7 +4,6 @@ on: push: branches: - dev - - main tags: - "*" pull_request: diff --git a/.github/workflows/webhook.yml b/.github/workflows/webhook.yml index 4ad738611f..1925517253 100644 --- a/.github/workflows/webhook.yml +++ b/.github/workflows/webhook.yml @@ -2,11 +2,6 @@ name: "Webhook" on: push: - # pull_request: - # types: - # - "opened" - # - "closed" - # - "reopened" release: types: - "published" diff --git a/scripts/version.py b/scripts/version.py index 84294acc0f..bf2777a2d1 100755 --- a/scripts/version.py +++ b/scripts/version.py @@ -35,11 +35,7 @@ def get_version_info(self): or "unknown" ) - version = ( - self.suffix - or os.environ.get("DIST_SUFFIX", None) - or "unknown" - ) + version = self.suffix or os.environ.get("DIST_SUFFIX", None) or "unknown" if "SOURCE_DATE_EPOCH" in os.environ: commit_date = datetime.utcfromtimestamp( From ed08b194e5814276aa9e363a1773dc694f2b8752 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 10 Mar 2024 12:45:49 +0000 Subject: [PATCH 35/40] New release workflow --- .github/workflow_data/devbuild.py | 2 +- .github/workflow_data/release.md | 4 +-- .github/workflow_data/release.py | 21 ++++++++---- .github/workflow_data/webhook.py | 53 ++++++------------------------- .github/workflows/reindex.yml | 27 ---------------- .github/workflows/release.yml | 47 +++++++++++++++++++++++++++ .github/workflows/webhook.yml | 3 -- 7 files changed, 73 insertions(+), 84 deletions(-) delete mode 100644 .github/workflows/reindex.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflow_data/devbuild.py b/.github/workflow_data/devbuild.py index a3c05f9545..d0660866b1 100644 --- a/.github/workflow_data/devbuild.py +++ b/.github/workflow_data/devbuild.py @@ -22,7 +22,7 @@ "title": "New Devbuild!", "description": "", "url": "", - "color": 11761899, + "color": 16751147, "fields": [ { "name": "Changes since last commit:", diff --git a/.github/workflow_data/release.md b/.github/workflow_data/release.md index 8402dd1083..8a33c3aca7 100644 --- a/.github/workflow_data/release.md +++ b/.github/workflow_data/release.md @@ -1,9 +1,9 @@ ## ⬇️ Download >### [🖥️ Web Updater (chrome)](https://momentum-fw.dev/update) [recommended] ->### [🐬 qFlipper Package (.tgz)](https://github.com/Next-Flip/Momentum-Firmware/releases/download/{VERSION_TAG}/{ARTIFACT_TGZ}) +>### [🐬 qFlipper Package (.tgz)](https://github.com/Next-Flip/Momentum-Firmware/releases/download/{VERSION_TAG}/flipper-z-f7-update-{VERSION_TAG}.tgz) ->### [📦 Zipped Archive (.zip)](https://github.com/Next-Flip/Momentum-Firmware/releases/download/{VERSION_TAG}/{ARTIFACT_ZIP}) +>### [📦 Zipped Archive (.zip)](https://github.com/Next-Flip/Momentum-Firmware/releases/download/{VERSION_TAG}/flipper-z-f7-update-{VERSION_TAG}.zip) **Check the [install guide](https://github.com/Next-Flip/Momentum-Firmware#install) if you're not sure, or [join our Discord](https://discord.gg/momentum) if you have questions or encounter issues!** diff --git a/.github/workflow_data/release.py b/.github/workflow_data/release.py index 77f2363889..6c7b0fc764 100644 --- a/.github/workflow_data/release.py +++ b/.github/workflow_data/release.py @@ -1,20 +1,27 @@ #!/usr/bin/env python +import requests import json import os if __name__ == "__main__": + with open(os.environ["GITHUB_EVENT_PATH"], "r") as f: + event = json.load(f) + release = requests.get( + event["release"]["url"], + headers={ + "Accept": "application/vnd.github.v3+json", + "Authorization": f"token {os.environ['GITHUB_TOKEN']}" + } + ).json() + version_tag = release["tag_name"] + changelog = release["body"] + notes_path = '.github/workflow_data/release.md' - with open(os.environ['GITHUB_EVENT_PATH'], "r") as f: - changelog = json.load(f)['pull_request']['body'] with open(notes_path, "r") as f: template = f.read() notes = template.format( - ARTIFACT_TGZ=os.environ['ARTIFACT_TGZ'], - ARTIFACT_ZIP=os.environ['ARTIFACT_ZIP'], - VERSION_TAG=os.environ['VERSION_TAG'], + VERSION_TAG=version_tag, CHANGELOG=changelog ) with open(notes_path, "w") as f: f.write(notes) - with open(os.environ["ARTIFACT_TGZ"].removesuffix(".tgz") + ".md", "w") as f: - f.write(changelog.strip() + "\n\n") diff --git a/.github/workflow_data/webhook.py b/.github/workflow_data/webhook.py index b9a03a0359..1f1531e116 100644 --- a/.github/workflow_data/webhook.py +++ b/.github/workflow_data/webhook.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +import datetime as dt import requests import json import sys @@ -44,50 +45,13 @@ color = 16723712 if event["forced"] else 3669797 case "release": - match event["action"]: - case "published": - webhook = "RELEASE_WEBHOOK" - color = 13845998 - title = f"New Release published: {event['name']}" - desc += f"Changelog:" - - changelog = "".join( - event["body"] - .split("Changelog")[1] - .split("