Przeglądaj źródła

[FL-1720] BLE GUI refactoring (#678)

* dialog_ex: add clean method, fix documentation
* application: add bt debug and settings application
* bt: add debug application
* bt: add settings application
* bt: rework bt service
* bt debug: fix carrier debug app
* assets: add debug animation to main menu
* bt debug: fix bt packet test
* bt service: rework bt service
* bt: cleanup
* Assets: fix spelling
* Ble: wait for core 2 startup complete before initializing radio stack.
* Accessor: remove dead code, switch port PA6 to PA4, because interrupt line 6 is used by down button.
* FuriHal: assert interrupt line on add_int_callback.
* Bt: update icon on core2 startup complete.

Co-authored-by: あく <alleteam@gmail.com>
gornekich 4 lat temu
rodzic
commit
420c03bb58
46 zmienionych plików z 1474 dodań i 728 usunięć
  1. 0 10
      applications/accessor/accessor-app.cpp
  2. 6 7
      applications/accessor/helpers/wiegand.cpp
  3. 10 0
      applications/applications.c
  4. 0 266
      applications/bt/bt.c
  5. 94 0
      applications/bt/bt_debug_app/bt_debug_app.c
  6. 26 0
      applications/bt/bt_debug_app/bt_debug_app.h
  7. 188 0
      applications/bt/bt_debug_app/views/bt_carrier_test.c
  8. 10 0
      applications/bt/bt_debug_app/views/bt_carrier_test.h
  9. 155 0
      applications/bt/bt_debug_app/views/bt_packet_test.c
  10. 10 0
      applications/bt/bt_debug_app/views/bt_packet_test.h
  11. 422 0
      applications/bt/bt_debug_app/views/bt_test.c
  12. 46 0
      applications/bt/bt_debug_app/views/bt_test.h
  13. 30 0
      applications/bt/bt_debug_app/views/bt_test_types.h
  14. 0 53
      applications/bt/bt_i.h
  15. 74 0
      applications/bt/bt_service/bt.c
  16. 0 0
      applications/bt/bt_service/bt.h
  17. 29 0
      applications/bt/bt_service/bt_i.h
  18. 50 0
      applications/bt/bt_settings.c
  19. 15 0
      applications/bt/bt_settings.h
  20. 67 0
      applications/bt/bt_settings_app/bt_settings_app.c
  21. 27 0
      applications/bt/bt_settings_app/bt_settings_app.h
  22. 30 0
      applications/bt/bt_settings_app/scenes/bt_settings_scene.c
  23. 29 0
      applications/bt/bt_settings_app/scenes/bt_settings_scene.h
  24. 2 0
      applications/bt/bt_settings_app/scenes/bt_settings_scene_config.h
  25. 44 0
      applications/bt/bt_settings_app/scenes/bt_settings_scene_disable_dialog.c
  26. 56 0
      applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c
  27. 0 65
      applications/bt/bt_types.h
  28. 0 250
      applications/bt/bt_views.c
  29. 0 54
      applications/bt/bt_views.h
  30. 19 0
      applications/gui/modules/dialog_ex.c
  31. 21 15
      applications/gui/modules/dialog_ex.h
  32. 1 1
      applications/loader/loader.c
  33. 0 0
      assets/compiled/assets_icons.c
  34. 5 4
      assets/compiled/assets_icons.h
  35. BIN
      assets/icons/MainMenu/Debug_14/frame_01.png
  36. BIN
      assets/icons/MainMenu/Debug_14/frame_02.png
  37. BIN
      assets/icons/MainMenu/Debug_14/frame_03.png
  38. BIN
      assets/icons/MainMenu/Debug_14/frame_04.png
  39. 1 0
      assets/icons/MainMenu/Debug_14/frame_rate
  40. 0 0
      assets/icons/SubGhz/Scanning_123x52.png
  41. 0 0
      assets/icons/SubGhz/lock_7x8.png
  42. 0 0
      assets/icons/SubGhz/quest_7x8.png
  43. 0 0
      assets/icons/SubGhz/unlock_7x8.png
  44. 3 3
      firmware/targets/f6/furi-hal/furi-hal-bt.c
  45. 1 0
      firmware/targets/f6/furi-hal/furi-hal-gpio.c
  46. 3 0
      firmware/targets/furi-hal-include/furi-hal-bt.h

+ 0 - 10
applications/accessor/accessor-app.cpp

@@ -109,16 +109,6 @@ void AccessorApp::notify_green_blink() {
 
 
 void AccessorApp::notify_success() {
 void AccessorApp::notify_success() {
     notification_message(notification, &sequence_success);
     notification_message(notification, &sequence_success);
-
-    hal_pwm_set(0.5, 1760 / 2, &htim2, TIM_CHANNEL_2);
-    delay(100);
-    hal_pwm_stop(&htim2, TIM_CHANNEL_2);
-
-    delay(100);
-
-    hal_pwm_set(0.5, 1760, &htim2, TIM_CHANNEL_2);
-    delay(100);
-    hal_pwm_stop(&htim2, TIM_CHANNEL_2);
 }
 }
 
 
 /*************************** TEXT STORE *****************************/
 /*************************** TEXT STORE *****************************/

+ 6 - 7
applications/accessor/helpers/wiegand.cpp

@@ -11,6 +11,8 @@ volatile int WIEGAND::_bitCount = 0;
 int WIEGAND::_wiegandType = 0;
 int WIEGAND::_wiegandType = 0;
 
 
 constexpr uint32_t clocks_in_ms = 64 * 1000;
 constexpr uint32_t clocks_in_ms = 64 * 1000;
+const GpioPin* pinD0 = &gpio_ext_pa4;
+const GpioPin* pinD1 = &gpio_ext_pa7;
 
 
 WIEGAND::WIEGAND() {
 WIEGAND::WIEGAND() {
 }
 }
@@ -53,9 +55,6 @@ void WIEGAND::begin() {
     _wiegandType = 0;
     _wiegandType = 0;
     _bitCount = 0;
     _bitCount = 0;
 
 
-    const GpioPin* pinD0 = &gpio_ext_pa6;
-    const GpioPin* pinD1 = &gpio_ext_pa7;
-
     hal_gpio_init_simple(pinD0, GpioModeInterruptFall); // Set D0 pin as input
     hal_gpio_init_simple(pinD0, GpioModeInterruptFall); // Set D0 pin as input
     hal_gpio_init_simple(pinD1, GpioModeInterruptFall); // Set D1 pin as input
     hal_gpio_init_simple(pinD1, GpioModeInterruptFall); // Set D1 pin as input
 
 
@@ -64,11 +63,11 @@ void WIEGAND::begin() {
 }
 }
 
 
 void WIEGAND::end() {
 void WIEGAND::end() {
-    hal_gpio_remove_int_callback(&gpio_ext_pa6);
-    hal_gpio_remove_int_callback(&gpio_ext_pa7);
+    hal_gpio_remove_int_callback(pinD0);
+    hal_gpio_remove_int_callback(pinD1);
 
 
-    hal_gpio_init_simple(&gpio_ext_pa6, GpioModeAnalog);
-    hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog);
+    hal_gpio_init_simple(pinD0, GpioModeAnalog);
+    hal_gpio_init_simple(pinD1, GpioModeAnalog);
 }
 }
 
 
 void WIEGAND::ReadD0() {
 void WIEGAND::ReadD0() {

+ 10 - 0
applications/applications.c

@@ -32,6 +32,7 @@ extern int32_t scened_app(void* p);
 extern int32_t storage_test_app(void* p);
 extern int32_t storage_test_app(void* p);
 extern int32_t subghz_app(void* p);
 extern int32_t subghz_app(void* p);
 extern int32_t vibro_test_app(void* p);
 extern int32_t vibro_test_app(void* p);
+extern int32_t bt_debug_app(void* p);
 
 
 // Plugins
 // Plugins
 extern int32_t music_player_app(void* p);
 extern int32_t music_player_app(void* p);
@@ -48,6 +49,7 @@ extern void subghz_cli_init();
 // Settings
 // Settings
 extern int32_t notification_settings_app(void* p);
 extern int32_t notification_settings_app(void* p);
 extern int32_t storage_settings_app(void* p);
 extern int32_t storage_settings_app(void* p);
+extern int32_t bt_settings_app(void* p);
 
 
 const FlipperApplication FLIPPER_SERVICES[] = {
 const FlipperApplication FLIPPER_SERVICES[] = {
 /* Services */
 /* Services */
@@ -236,6 +238,10 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = {
 #ifdef APP_LF_RFID
 #ifdef APP_LF_RFID
     {.app = lfrfid_debug_app, .name = "LF-RFID Debug", .stack_size = 1024, .icon = &A_125khz_14},
     {.app = lfrfid_debug_app, .name = "LF-RFID Debug", .stack_size = 1024, .icon = &A_125khz_14},
 #endif
 #endif
+
+#ifdef SRV_BT
+    {.app = bt_debug_app, .name = "Bluetooth Debug", .stack_size = 1024, .icon = NULL},
+#endif
 };
 };
 
 
 const size_t FLIPPER_DEBUG_APPS_COUNT = sizeof(FLIPPER_DEBUG_APPS) / sizeof(FlipperApplication);
 const size_t FLIPPER_DEBUG_APPS_COUNT = sizeof(FLIPPER_DEBUG_APPS) / sizeof(FlipperApplication);
@@ -254,6 +260,10 @@ const FlipperApplication FLIPPER_SETTINGS_APPS[] = {
 #ifdef SRV_STORAGE
 #ifdef SRV_STORAGE
     {.app = storage_settings_app, .name = "Storage", .stack_size = 2048, .icon = NULL},
     {.app = storage_settings_app, .name = "Storage", .stack_size = 2048, .icon = NULL},
 #endif
 #endif
+
+#ifdef SRV_BT
+    {.app = bt_settings_app, .name = "Bluetooth", .stack_size = 1024, .icon = NULL},
+#endif
 };
 };
 
 
 const size_t FLIPPER_SETTINGS_APPS_COUNT =
 const size_t FLIPPER_SETTINGS_APPS_COUNT =

+ 0 - 266
applications/bt/bt.c

@@ -1,266 +0,0 @@
-#include "bt_i.h"
-
-uint32_t bt_view_exit(void* context) {
-    (void)context;
-    return VIEW_NONE;
-}
-
-void bt_update_statusbar(void* arg) {
-    furi_assert(arg);
-    Bt* bt = arg;
-    BtMessage m = {.type = BtMessageTypeUpdateStatusbar};
-    furi_check(osMessageQueuePut(bt->message_queue, &m, 0, osWaitForever) == osOK);
-}
-
-void bt_update_param(void* arg) {
-    furi_assert(arg);
-    Bt* bt = arg;
-    BtMessage m;
-    if(bt->state.type == BtStateHoppingTx || bt->state.type == BtStateCarrierRxRunning) {
-        m.type = BtMessageTypeStartTestCarrier;
-    } else if(bt->state.type == BtStatePacketRunning) {
-        m.type = BtMessageTypeStartTestPacketRx;
-    }
-    furi_check(osMessageQueuePut(bt->message_queue, &m, 0, osWaitForever) == osOK);
-}
-
-Bt* bt_alloc() {
-    Bt* bt = furi_alloc(sizeof(Bt));
-
-    bt->message_queue = osMessageQueueNew(8, sizeof(BtMessage), NULL);
-    bt->update_status_timer = osTimerNew(bt_update_statusbar, osTimerPeriodic, bt, NULL);
-    osTimerStart(bt->update_status_timer, 4000);
-    bt->update_param_timer = osTimerNew(bt_update_param, osTimerPeriodic, bt, NULL);
-    bt->gui = furi_record_open("gui");
-    bt->menu = furi_record_open("menu");
-
-    bt->state.type = BtStateReady;
-    bt->state.param.channel = BtChannel2402;
-    bt->state.param.power = BtPower0dB;
-    bt->state.param.datarate = BtDataRate1M;
-
-    bt->statusbar_view_port = view_port_alloc();
-    view_port_set_width(bt->statusbar_view_port, 5);
-    view_port_draw_callback_set(bt->statusbar_view_port, bt_draw_statusbar_callback, bt);
-    view_port_enabled_set(bt->statusbar_view_port, false);
-    gui_add_view_port(bt->gui, bt->statusbar_view_port, GuiLayerStatusBarLeft);
-
-    bt->menu_icon = icon_animation_alloc(&A_Bluetooth_14);
-    bt->menu_item = menu_item_alloc_menu("Bluetooth", bt->menu_icon);
-    menu_item_subitem_add(
-        bt->menu_item, menu_item_alloc_function("Carrier test", NULL, bt_menu_test_carrier, bt));
-    menu_item_subitem_add(
-        bt->menu_item,
-        menu_item_alloc_function("Test packet TX", NULL, bt_menu_test_packet_tx, bt));
-    menu_item_subitem_add(
-        bt->menu_item, menu_item_alloc_function("Start app", NULL, bt_menu_start_app, bt));
-    menu_item_subitem_add(
-        bt->menu_item,
-        menu_item_alloc_function("Test packet RX", NULL, bt_menu_test_packet_rx, bt));
-
-    // Carrier test
-    bt->view_test_carrier = view_alloc();
-    view_set_context(bt->view_test_carrier, bt);
-    view_set_draw_callback(bt->view_test_carrier, bt_view_test_carrier_draw);
-    view_allocate_model(
-        bt->view_test_carrier, ViewModelTypeLocking, sizeof(BtViewTestCarrierModel));
-    view_set_input_callback(bt->view_test_carrier, bt_view_test_carrier_input);
-
-    // Packet TX test
-    bt->view_test_packet_tx = view_alloc();
-    view_set_context(bt->view_test_packet_tx, bt);
-    view_set_draw_callback(bt->view_test_packet_tx, bt_view_test_packet_tx_draw);
-    view_allocate_model(
-        bt->view_test_packet_tx, ViewModelTypeLocking, sizeof(BtViewTestPacketTxModel));
-    view_set_input_callback(bt->view_test_packet_tx, bt_view_test_packet_tx_input);
-
-    // Packet RX test
-    bt->view_test_packet_rx = view_alloc();
-    view_set_context(bt->view_test_packet_rx, bt);
-    view_set_draw_callback(bt->view_test_packet_rx, bt_view_test_packet_rx_draw);
-    view_allocate_model(
-        bt->view_test_packet_rx, ViewModelTypeLocking, sizeof(BtViewTestPacketRxModel));
-    view_set_input_callback(bt->view_test_packet_rx, bt_view_test_packet_rx_input);
-
-    // Start app
-    bt->view_start_app = view_alloc();
-    view_set_context(bt->view_start_app, bt);
-    view_set_draw_callback(bt->view_start_app, bt_view_app_draw);
-    view_set_previous_callback(bt->view_start_app, bt_view_exit);
-
-    // View dispatcher
-    bt->view_dispatcher = view_dispatcher_alloc();
-    view_dispatcher_add_view(bt->view_dispatcher, BtViewTestCarrier, bt->view_test_carrier);
-    view_dispatcher_add_view(bt->view_dispatcher, BtViewTestPacketTx, bt->view_test_packet_tx);
-    view_dispatcher_add_view(bt->view_dispatcher, BtViewTestPacketRx, bt->view_test_packet_rx);
-    view_dispatcher_add_view(bt->view_dispatcher, BtViewStartApp, bt->view_start_app);
-
-    Gui* gui = furi_record_open("gui");
-    view_dispatcher_attach_to_gui(bt->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
-
-    with_value_mutex(
-        bt->menu, (Menu * menu) { menu_item_add(menu, bt->menu_item); });
-    return bt;
-}
-
-void bt_draw_statusbar_callback(Canvas* canvas, void* context) {
-    canvas_draw_icon(canvas, 0, 0, &I_Bluetooth_5x8);
-}
-
-void bt_menu_test_carrier(void* context) {
-    furi_assert(context);
-    Bt* bt = context;
-    bt->state.type = BtStateCarrierTx;
-    BtMessage message = {
-        .type = BtMessageTypeStartTestCarrier,
-        .param.channel = bt->state.param.channel,
-        .param.power = bt->state.param.power};
-    furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
-}
-
-void bt_menu_test_packet_tx(void* context) {
-    furi_assert(context);
-    Bt* bt = context;
-    bt->state.type = BtStatePacketSetup;
-    BtMessage message = {
-        .type = BtMessageTypeSetupTestPacketTx,
-        .param.channel = bt->state.param.channel,
-        .param.datarate = bt->state.param.datarate};
-    furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
-}
-
-void bt_menu_test_packet_rx(void* context) {
-    furi_assert(context);
-    Bt* bt = context;
-    bt->state.type = BtStatePacketSetup;
-    BtMessage message = {
-        .type = BtMessageTypeSetupTestPacketRx,
-        .param.channel = bt->state.param.channel,
-        .param.datarate = bt->state.param.datarate};
-    furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
-}
-
-void bt_menu_start_app(void* context) {
-    furi_assert(context);
-    Bt* bt = context;
-    bt->state.type = BtStateStartedApp;
-    BtMessage message = {.type = BtMessageTypeStartApp};
-    furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
-}
-
-int32_t bt_srv() {
-    Bt* bt = bt_alloc();
-
-    furi_record_create("bt", bt);
-
-    furi_hal_bt_init();
-    BtMessage message;
-    while(1) {
-        furi_check(osMessageQueueGet(bt->message_queue, &message, NULL, osWaitForever) == osOK);
-        if(message.type == BtMessageTypeStartTestCarrier) {
-            // Start carrier test
-            furi_hal_bt_stop_tone_tx();
-            if(bt->state.type == BtStateCarrierTx) {
-                furi_hal_bt_start_tone_tx(message.param.channel, message.param.power);
-            } else if(bt->state.type == BtStateHoppingTx) {
-                bt->state.param.channel =
-                    bt_switch_channel(InputKeyRight, bt->state.param.channel);
-                furi_hal_bt_start_tone_tx(bt->state.param.channel, bt->state.param.power);
-            } else if(bt->state.type == BtStateCarrierRxStart) {
-                furi_hal_bt_start_packet_rx(bt->state.param.channel, bt->state.param.datarate);
-                bt->state.type = BtStateCarrierRxRunning;
-            } else if(bt->state.type == BtStateCarrierRxRunning) {
-                bt->state.param.rssi = furi_hal_bt_get_rssi();
-            }
-            with_view_model(
-                bt->view_test_carrier, (BtViewTestCarrierModel * model) {
-                    model->type = bt->state.type;
-                    model->channel = bt->state.param.channel;
-                    model->power = bt->state.param.power;
-                    model->rssi = bt->state.param.rssi;
-                    return true;
-                });
-            view_dispatcher_switch_to_view(bt->view_dispatcher, BtViewTestCarrier);
-        } else if(message.type == BtMessageTypeStopTestCarrier) {
-            if(bt->state.type == BtStateCarrierRxRunning) {
-                furi_hal_bt_stop_packet_test();
-            } else {
-                furi_hal_bt_stop_tone_tx();
-            }
-            bt->state.type = BtStateReady;
-        } else if(message.type == BtMessageTypeSetupTestPacketTx) {
-            // Update packet test setup
-            furi_hal_bt_stop_packet_test();
-            with_view_model(
-                bt->view_test_packet_tx, (BtViewTestPacketTxModel * model) {
-                    model->type = bt->state.type;
-                    model->channel = bt->state.param.channel;
-                    model->datarate = bt->state.param.datarate;
-                    return true;
-                });
-            view_dispatcher_switch_to_view(bt->view_dispatcher, BtViewTestPacketTx);
-        } else if(message.type == BtMessageTypeStartTestPacketTx) {
-            // Start sending packets
-            if(bt->state.type == BtStatePacketStart) {
-                furi_hal_bt_start_packet_tx(message.param.channel, 1, message.param.datarate);
-            } else if(bt->state.type == BtStatePacketSetup) {
-                furi_hal_bt_stop_packet_test();
-                bt->state.param.packets_sent = furi_hal_bt_get_transmitted_packets();
-            }
-            with_view_model(
-                bt->view_test_packet_tx, (BtViewTestPacketTxModel * model) {
-                    model->type = bt->state.type;
-                    model->channel = bt->state.param.channel;
-                    model->datarate = bt->state.param.datarate;
-                    model->packets_sent = bt->state.param.packets_sent;
-                    return true;
-                });
-            view_dispatcher_switch_to_view(bt->view_dispatcher, BtViewTestPacketTx);
-        } else if(message.type == BtMessageTypeSetupTestPacketRx) {
-            // Update packet test setup
-            furi_hal_bt_stop_packet_test();
-            with_view_model(
-                bt->view_test_packet_rx, (BtViewTestPacketRxModel * model) {
-                    model->type = bt->state.type;
-                    model->channel = bt->state.param.channel;
-                    model->datarate = bt->state.param.datarate;
-                    return true;
-                });
-            view_dispatcher_switch_to_view(bt->view_dispatcher, BtViewTestPacketRx);
-        } else if(message.type == BtMessageTypeStartTestPacketRx) {
-            // Start test rx
-            if(bt->state.type == BtStatePacketStart) {
-                furi_hal_bt_start_packet_rx(message.param.channel, message.param.datarate);
-                bt->state.type = BtStatePacketRunning;
-            } else if(bt->state.type == BtStatePacketRunning) {
-                bt->state.param.rssi = furi_hal_bt_get_rssi();
-            } else if(bt->state.type == BtStatePacketSetup) {
-                bt->state.param.packets_received = furi_hal_bt_stop_packet_test();
-            }
-            with_view_model(
-                bt->view_test_packet_rx, (BtViewTestPacketRxModel * model) {
-                    model->type = bt->state.type;
-                    model->channel = bt->state.param.channel;
-                    model->datarate = bt->state.param.datarate;
-                    model->packets_received = bt->state.param.packets_received;
-                    model->rssi = bt->state.param.rssi;
-                    return true;
-                });
-            view_dispatcher_switch_to_view(bt->view_dispatcher, BtViewTestPacketRx);
-        } else if(message.type == BtMessageTypeStopTestPacket) {
-            // Stop test packet tx
-            furi_hal_bt_stop_packet_test();
-            bt->state.type = BtStateReady;
-        } else if(message.type == BtMessageTypeStartApp) {
-            // Start app
-            view_dispatcher_switch_to_view(bt->view_dispatcher, BtViewStartApp);
-            if(furi_hal_bt_start_app()) {
-                bt->state.type = BtStateStartedApp;
-            }
-        } else if(message.type == BtMessageTypeUpdateStatusbar) {
-            // Update statusbar
-            view_port_enabled_set(bt->statusbar_view_port, furi_hal_bt_is_alive());
-        }
-    }
-    return 0;
-}

+ 94 - 0
applications/bt/bt_debug_app/bt_debug_app.c

@@ -0,0 +1,94 @@
+#include "bt_debug_app.h"
+
+enum BtDebugSubmenuIndex {
+    BtDebugSubmenuIndexCarrierTest,
+    BtDebugSubmenuIndexPacketTest,
+};
+
+void bt_debug_submenu_callback(void* context, uint32_t index) {
+    furi_assert(context);
+    BtDebugApp* app = context;
+    if(index == BtDebugSubmenuIndexCarrierTest) {
+        view_dispatcher_switch_to_view(app->view_dispatcher, BtDebugAppViewCarrierTest);
+    } else if(index == BtDebugSubmenuIndexPacketTest) {
+        view_dispatcher_switch_to_view(app->view_dispatcher, BtDebugAppViewPacketTest);
+    }
+}
+
+uint32_t bt_debug_exit(void* context) {
+    return VIEW_NONE;
+}
+
+uint32_t bt_debug_start_view(void* context) {
+    return BtDebugAppViewSubmenu;
+}
+
+BtDebugApp* bt_debug_app_alloc() {
+    BtDebugApp* app = furi_alloc(sizeof(BtDebugApp));
+    // Gui
+    app->gui = furi_record_open("gui");
+
+    // View dispatcher
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    // Views
+    app->submenu = submenu_alloc();
+    submenu_add_item(
+        app->submenu,
+        "Carrier test",
+        BtDebugSubmenuIndexCarrierTest,
+        bt_debug_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu, "Packet test", BtDebugSubmenuIndexPacketTest, bt_debug_submenu_callback, app);
+    view_set_previous_callback(submenu_get_view(app->submenu), bt_debug_exit);
+    view_dispatcher_add_view(
+        app->view_dispatcher, BtDebugAppViewSubmenu, submenu_get_view(app->submenu));
+    app->bt_carrier_test = bt_carrier_test_alloc();
+    view_set_previous_callback(
+        bt_carrier_test_get_view(app->bt_carrier_test), bt_debug_start_view);
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        BtDebugAppViewCarrierTest,
+        bt_carrier_test_get_view(app->bt_carrier_test));
+    app->bt_packet_test = bt_packet_test_alloc();
+    view_set_previous_callback(bt_packet_test_get_view(app->bt_packet_test), bt_debug_start_view);
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        BtDebugAppViewPacketTest,
+        bt_packet_test_get_view(app->bt_packet_test));
+
+    // Switch to menu
+    view_dispatcher_switch_to_view(app->view_dispatcher, BtDebugAppViewSubmenu);
+
+    return app;
+}
+
+void bt_debug_app_free(BtDebugApp* app) {
+    furi_assert(app);
+
+    // Free views
+    view_dispatcher_remove_view(app->view_dispatcher, BtDebugAppViewSubmenu);
+    submenu_free(app->submenu);
+    view_dispatcher_remove_view(app->view_dispatcher, BtDebugAppViewCarrierTest);
+    bt_carrier_test_free(app->bt_carrier_test);
+    view_dispatcher_remove_view(app->view_dispatcher, BtDebugAppViewPacketTest);
+    bt_packet_test_free(app->bt_packet_test);
+    view_dispatcher_free(app->view_dispatcher);
+
+    // Close gui record
+    furi_record_close("gui");
+    app->gui = NULL;
+
+    // Free rest
+    free(app);
+}
+
+int32_t bt_debug_app(void* p) {
+    BtDebugApp* app = bt_debug_app_alloc();
+    view_dispatcher_run(app->view_dispatcher);
+    bt_debug_app_free(app);
+    return 0;
+}

+ 26 - 0
applications/bt/bt_debug_app/bt_debug_app.h

@@ -0,0 +1,26 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/view_dispatcher.h>
+
+#include <gui/modules/submenu.h>
+#include "views/bt_carrier_test.h"
+#include "views/bt_packet_test.h"
+#include "../bt_settings.h"
+
+typedef struct {
+    BtSettings settings;
+    Gui* gui;
+    ViewDispatcher* view_dispatcher;
+    Submenu* submenu;
+    BtCarrierTest* bt_carrier_test;
+    BtPacketTest* bt_packet_test;
+} BtDebugApp;
+
+typedef enum {
+    BtDebugAppViewSubmenu,
+    BtDebugAppViewCarrierTest,
+    BtDebugAppViewPacketTest,
+} BtDebugAppView;

+ 188 - 0
applications/bt/bt_debug_app/views/bt_carrier_test.c

@@ -0,0 +1,188 @@
+#include "bt_carrier_test.h"
+#include "bt_test.h"
+#include "bt_test_types.h"
+#include "furi-hal-bt.h"
+
+struct BtCarrierTest {
+    BtTest* bt_test;
+    BtTestParam* bt_param_channel;
+    BtTestMode mode;
+    BtTestChannel channel;
+    BtTestPower power;
+    osTimerId_t timer;
+};
+
+static BtTestParamValue bt_param_mode[] = {
+    {.value = BtTestModeRx, .str = "Rx"},
+    {.value = BtTestModeTx, .str = "Tx"},
+    {.value = BtTestModeTxHopping, .str = "Hopping Tx"},
+};
+
+static BtTestParamValue bt_param_channel[] = {
+    {.value = BtTestChannel2402, .str = "2402 MHz"},
+    {.value = BtTestChannel2440, .str = "2440 MHz"},
+    {.value = BtTestChannel2480, .str = "2480 MHz"},
+};
+
+static BtTestParamValue bt_param_power[] = {
+    {.value = BtPower0dB, .str = "0 dB"},
+    {.value = BtPower2dB, .str = "2 dB"},
+    {.value = BtPower4dB, .str = "4 dB"},
+    {.value = BtPower6dB, .str = "6 dB"},
+};
+
+static void bt_carrier_test_start(BtCarrierTest* bt_carrier_test) {
+    furi_assert(bt_carrier_test);
+    if(bt_carrier_test->mode == BtTestModeRx) {
+        furi_hal_bt_start_packet_rx(bt_carrier_test->channel, 1);
+        osTimerStart(bt_carrier_test->timer, 1024 / 4);
+    } else if(bt_carrier_test->mode == BtTestModeTxHopping) {
+        furi_hal_bt_start_tone_tx(bt_carrier_test->channel, bt_carrier_test->power);
+        osTimerStart(bt_carrier_test->timer, 2048);
+    } else if(bt_carrier_test->mode == BtTestModeTx) {
+        furi_hal_bt_start_tone_tx(bt_carrier_test->channel, bt_carrier_test->power);
+    }
+}
+
+static void bt_carrier_test_switch_channel(BtCarrierTest* bt_carrier_test) {
+    furi_assert(bt_carrier_test);
+    furi_hal_bt_stop_tone_tx();
+    uint8_t channel_i = 0;
+    if(bt_carrier_test->channel == BtTestChannel2402) {
+        bt_carrier_test->channel = BtTestChannel2440;
+        channel_i = 1;
+    } else if(bt_carrier_test->channel == BtTestChannel2440) {
+        bt_carrier_test->channel = BtTestChannel2480;
+        channel_i = 2;
+    } else if(bt_carrier_test->channel == BtTestChannel2480) {
+        bt_carrier_test->channel = BtTestChannel2402;
+        channel_i = 0;
+    }
+    furi_hal_bt_start_tone_tx(bt_carrier_test->channel, bt_carrier_test->power);
+    bt_test_set_current_value_index(bt_carrier_test->bt_param_channel, channel_i);
+    bt_test_set_current_value_text(
+        bt_carrier_test->bt_param_channel, bt_param_channel[channel_i].str);
+}
+
+static void bt_carrier_test_stop(BtCarrierTest* bt_carrier_test) {
+    furi_assert(bt_carrier_test);
+    if(bt_carrier_test->mode == BtTestModeTxHopping) {
+        furi_hal_bt_stop_tone_tx();
+        osTimerStop(bt_carrier_test->timer);
+    } else if(bt_carrier_test->mode == BtTestModeTx) {
+        furi_hal_bt_stop_tone_tx();
+    } else if(bt_carrier_test->mode == BtTestModeRx) {
+        furi_hal_bt_stop_packet_test();
+        osTimerStop(bt_carrier_test->timer);
+    }
+}
+
+static uint32_t bt_carrier_test_param_changed(BtTestParam* param, BtTestParamValue* param_val) {
+    furi_assert(param);
+    uint8_t index = bt_test_get_current_value_index(param);
+    bt_test_set_current_value_text(param, param_val[index].str);
+    return param_val[index].value;
+}
+
+static void bt_carrier_test_mode_changed(BtTestParam* param) {
+    BtCarrierTest* bt_carrier_test = bt_test_get_context(param);
+    bt_carrier_test_stop(bt_carrier_test);
+    bt_carrier_test->mode = bt_carrier_test_param_changed(param, bt_param_mode);
+}
+
+static void bt_carrier_test_channel_changed(BtTestParam* param) {
+    BtCarrierTest* bt_carrier_test = bt_test_get_context(param);
+    bt_carrier_test_stop(bt_carrier_test);
+    bt_carrier_test->channel = bt_carrier_test_param_changed(param, bt_param_channel);
+}
+
+static void bt_carrier_test_param_channel(BtTestParam* param) {
+    BtCarrierTest* bt_carrier_test = bt_test_get_context(param);
+    bt_carrier_test_stop(bt_carrier_test);
+    bt_carrier_test->power = bt_carrier_test_param_changed(param, bt_param_power);
+}
+
+static void bt_carrier_test_change_state_callback(BtTestState state, void* context) {
+    furi_assert(context);
+    BtCarrierTest* bt_carrier_test = context;
+    furi_hal_bt_stop_tone_tx();
+    if(state == BtTestStateStarted) {
+        bt_carrier_test_start(bt_carrier_test);
+    } else if(state == BtTestStateStopped) {
+        bt_carrier_test_stop(bt_carrier_test);
+    }
+}
+
+static void bt_carrier_test_exit_callback(void* context) {
+    furi_assert(context);
+    BtCarrierTest* bt_carrier_test = context;
+    bt_carrier_test_stop(bt_carrier_test);
+}
+
+static void bt_test_carrier_timer_callback(void* context) {
+    furi_assert(context);
+    BtCarrierTest* bt_carrier_test = context;
+    if(bt_carrier_test->mode == BtTestModeRx) {
+        bt_test_set_rssi(bt_carrier_test->bt_test, furi_hal_bt_get_rssi());
+    } else if(bt_carrier_test->mode == BtTestModeTxHopping) {
+        bt_carrier_test_switch_channel(bt_carrier_test);
+    }
+}
+
+BtCarrierTest* bt_carrier_test_alloc() {
+    BtCarrierTest* bt_carrier_test = furi_alloc(sizeof(BtCarrierTest));
+    bt_carrier_test->bt_test = bt_test_alloc();
+    bt_test_set_context(bt_carrier_test->bt_test, bt_carrier_test);
+    bt_test_set_change_state_callback(
+        bt_carrier_test->bt_test, bt_carrier_test_change_state_callback);
+    bt_test_set_back_callback(bt_carrier_test->bt_test, bt_carrier_test_exit_callback);
+
+    BtTestParam* param;
+    param = bt_test_param_add(
+        bt_carrier_test->bt_test,
+        "Mode",
+        SIZEOF_ARRAY(bt_param_mode),
+        bt_carrier_test_mode_changed,
+        bt_carrier_test);
+    bt_test_set_current_value_index(param, 0);
+    bt_test_set_current_value_text(param, bt_param_mode[0].str);
+    bt_carrier_test->mode = BtTestModeRx;
+
+    param = bt_test_param_add(
+        bt_carrier_test->bt_test,
+        "Channel",
+        SIZEOF_ARRAY(bt_param_channel),
+        bt_carrier_test_channel_changed,
+        bt_carrier_test);
+    bt_test_set_current_value_index(param, 0);
+    bt_test_set_current_value_text(param, bt_param_channel[0].str);
+    bt_carrier_test->channel = BtTestChannel2402;
+    bt_carrier_test->bt_param_channel = param;
+
+    param = bt_test_param_add(
+        bt_carrier_test->bt_test,
+        "Power",
+        SIZEOF_ARRAY(bt_param_power),
+        bt_carrier_test_param_channel,
+        bt_carrier_test);
+    bt_test_set_current_value_index(param, 0);
+    bt_test_set_current_value_text(param, bt_param_power[0].str);
+    bt_carrier_test->power = BtPower0dB;
+
+    bt_carrier_test->timer =
+        osTimerNew(bt_test_carrier_timer_callback, osTimerPeriodic, bt_carrier_test, NULL);
+
+    return bt_carrier_test;
+}
+
+void bt_carrier_test_free(BtCarrierTest* bt_carrier_test) {
+    furi_assert(bt_carrier_test);
+    bt_test_free(bt_carrier_test->bt_test);
+    osTimerDelete(bt_carrier_test->timer);
+    free(bt_carrier_test);
+}
+
+View* bt_carrier_test_get_view(BtCarrierTest* bt_carrier_test) {
+    furi_assert(bt_carrier_test);
+    return bt_test_get_view(bt_carrier_test->bt_test);
+}

+ 10 - 0
applications/bt/bt_debug_app/views/bt_carrier_test.h

@@ -0,0 +1,10 @@
+#pragma once
+#include <gui/view.h>
+
+typedef struct BtCarrierTest BtCarrierTest;
+
+BtCarrierTest* bt_carrier_test_alloc();
+
+void bt_carrier_test_free(BtCarrierTest* bt_carrier_test);
+
+View* bt_carrier_test_get_view(BtCarrierTest* bt_carrier_test);

+ 155 - 0
applications/bt/bt_debug_app/views/bt_packet_test.c

@@ -0,0 +1,155 @@
+#include "bt_packet_test.h"
+#include "bt_test.h"
+#include "bt_test_types.h"
+#include "furi-hal-bt.h"
+
+struct BtPacketTest {
+    BtTest* bt_test;
+    BtTestMode mode;
+    BtTestChannel channel;
+    BtTestDataRate data_rate;
+    osTimerId_t timer;
+};
+
+static BtTestParamValue bt_param_mode[] = {
+    {.value = BtTestModeRx, .str = "Rx"},
+    {.value = BtTestModeTx, .str = "Tx"},
+};
+
+static BtTestParamValue bt_param_channel[] = {
+    {.value = BtTestChannel2402, .str = "2402 MHz"},
+    {.value = BtTestChannel2440, .str = "2440 MHz"},
+    {.value = BtTestChannel2480, .str = "2480 MHz"},
+};
+
+static BtTestParamValue bt_param_data_rate[] = {
+    {.value = BtDataRate1M, .str = "1 Mbps"},
+    {.value = BtDataRate2M, .str = "2 Mbps"},
+};
+
+static void bt_packet_test_start(BtPacketTest* bt_packet_test) {
+    furi_assert(bt_packet_test);
+    if(bt_packet_test->mode == BtTestModeRx) {
+        furi_hal_bt_start_packet_rx(bt_packet_test->channel, bt_packet_test->data_rate);
+        osTimerStart(bt_packet_test->timer, 1024 / 4);
+    } else if(bt_packet_test->mode == BtTestModeTx) {
+        furi_hal_bt_start_packet_tx(bt_packet_test->channel, 1, bt_packet_test->data_rate);
+    }
+}
+
+static void bt_packet_test_stop(BtPacketTest* bt_packet_test) {
+    furi_assert(bt_packet_test);
+    if(bt_packet_test->mode == BtTestModeTx) {
+        furi_hal_bt_stop_packet_test();
+        bt_test_set_packets_tx(bt_packet_test->bt_test, furi_hal_bt_get_transmitted_packets());
+    } else if(bt_packet_test->mode == BtTestModeRx) {
+        bt_test_set_packets_rx(bt_packet_test->bt_test, furi_hal_bt_stop_packet_test());
+        osTimerStop(bt_packet_test->timer);
+    }
+}
+
+static uint32_t bt_packet_test_param_changed(BtTestParam* param, BtTestParamValue* param_val) {
+    furi_assert(param);
+    uint8_t index = bt_test_get_current_value_index(param);
+    bt_test_set_current_value_text(param, param_val[index].str);
+    return param_val[index].value;
+}
+
+static void bt_packet_test_mode_changed(BtTestParam* param) {
+    BtPacketTest* bt_packet_test = bt_test_get_context(param);
+    bt_packet_test_stop(bt_packet_test);
+    bt_packet_test->mode = bt_packet_test_param_changed(param, bt_param_mode);
+}
+
+static void bt_packet_test_channel_changed(BtTestParam* param) {
+    BtPacketTest* bt_packet_test = bt_test_get_context(param);
+    bt_packet_test_stop(bt_packet_test);
+    bt_packet_test->channel = bt_packet_test_param_changed(param, bt_param_channel);
+}
+
+static void bt_packet_test_param_channel(BtTestParam* param) {
+    BtPacketTest* bt_packet_test = bt_test_get_context(param);
+    bt_packet_test_stop(bt_packet_test);
+    bt_packet_test->data_rate = bt_packet_test_param_changed(param, bt_param_data_rate);
+}
+
+static void bt_packet_test_change_state_callback(BtTestState state, void* context) {
+    furi_assert(context);
+    BtPacketTest* bt_packet_test = context;
+    if(state == BtTestStateStarted) {
+        bt_packet_test_start(bt_packet_test);
+    } else if(state == BtTestStateStopped) {
+        bt_packet_test_stop(bt_packet_test);
+    }
+}
+
+static void bt_packet_test_exit_callback(void* context) {
+    furi_assert(context);
+    BtPacketTest* bt_packet_test = context;
+    bt_packet_test_stop(bt_packet_test);
+}
+
+static void bt_test_packet_timer_callback(void* context) {
+    furi_assert(context);
+    BtPacketTest* bt_packet_test = context;
+    if(bt_packet_test->mode == BtTestModeRx) {
+        bt_test_set_rssi(bt_packet_test->bt_test, furi_hal_bt_get_rssi());
+    }
+}
+
+BtPacketTest* bt_packet_test_alloc() {
+    BtPacketTest* bt_packet_test = furi_alloc(sizeof(BtPacketTest));
+    bt_packet_test->bt_test = bt_test_alloc();
+    bt_test_set_context(bt_packet_test->bt_test, bt_packet_test);
+    bt_test_set_change_state_callback(
+        bt_packet_test->bt_test, bt_packet_test_change_state_callback);
+    bt_test_set_back_callback(bt_packet_test->bt_test, bt_packet_test_exit_callback);
+
+    BtTestParam* param;
+    param = bt_test_param_add(
+        bt_packet_test->bt_test,
+        "Mode",
+        SIZEOF_ARRAY(bt_param_mode),
+        bt_packet_test_mode_changed,
+        bt_packet_test);
+    bt_test_set_current_value_index(param, 0);
+    bt_test_set_current_value_text(param, bt_param_mode[0].str);
+    bt_packet_test->mode = BtTestModeRx;
+
+    param = bt_test_param_add(
+        bt_packet_test->bt_test,
+        "Channel",
+        SIZEOF_ARRAY(bt_param_channel),
+        bt_packet_test_channel_changed,
+        bt_packet_test);
+    bt_test_set_current_value_index(param, 0);
+    bt_test_set_current_value_text(param, bt_param_channel[0].str);
+    bt_packet_test->channel = BtTestChannel2402;
+
+    param = bt_test_param_add(
+        bt_packet_test->bt_test,
+        "Data rate",
+        SIZEOF_ARRAY(bt_param_data_rate),
+        bt_packet_test_param_channel,
+        bt_packet_test);
+    bt_test_set_current_value_index(param, 0);
+    bt_test_set_current_value_text(param, bt_param_data_rate[0].str);
+    bt_packet_test->data_rate = BtDataRate1M;
+
+    bt_packet_test->timer =
+        osTimerNew(bt_test_packet_timer_callback, osTimerPeriodic, bt_packet_test, NULL);
+
+    return bt_packet_test;
+}
+
+void bt_packet_test_free(BtPacketTest* bt_packet_test) {
+    furi_assert(bt_packet_test);
+    bt_test_free(bt_packet_test->bt_test);
+    osTimerDelete(bt_packet_test->timer);
+    free(bt_packet_test);
+}
+
+View* bt_packet_test_get_view(BtPacketTest* bt_packet_test) {
+    furi_assert(bt_packet_test);
+    return bt_test_get_view(bt_packet_test->bt_test);
+}

+ 10 - 0
applications/bt/bt_debug_app/views/bt_packet_test.h

@@ -0,0 +1,10 @@
+#pragma once
+#include <gui/view.h>
+
+typedef struct BtPacketTest BtPacketTest;
+
+BtPacketTest* bt_packet_test_alloc();
+
+void bt_packet_test_free(BtPacketTest* bt_packet_test);
+
+View* bt_packet_test_get_view(BtPacketTest* bt_packet_test);

+ 422 - 0
applications/bt/bt_debug_app/views/bt_test.c

@@ -0,0 +1,422 @@
+#include "bt_test.h"
+
+#include <gui/canvas.h>
+#include <gui/elements.h>
+#include <m-array.h>
+#include <m-string.h>
+#include <furi.h>
+#include <stdint.h>
+
+struct BtTestParam {
+    const char* label;
+    uint8_t current_value_index;
+    string_t current_value_text;
+    uint8_t values_count;
+    BtTestParamChangeCallback change_callback;
+    void* context;
+};
+
+ARRAY_DEF(BtTestParamArray, BtTestParam, M_POD_OPLIST);
+
+struct BtTest {
+    View* view;
+    BtTestChangeStateCallback change_state_callback;
+    BtTestBackCallback back_callback;
+    void* context;
+};
+
+typedef struct {
+    BtTestState state;
+    BtTestParamArray_t params;
+    uint8_t position;
+    uint8_t window_position;
+    const char* message;
+    float rssi;
+    uint32_t packets_num_rx;
+    uint32_t packets_num_tx;
+} BtTestModel;
+
+#define BT_TEST_START_MESSAGE "Ok - Start"
+#define BT_TEST_STOP_MESSAGE "Ok - Stop"
+
+static void bt_test_process_up(BtTest* bt_test);
+static void bt_test_process_down(BtTest* bt_test);
+static void bt_test_process_left(BtTest* bt_test);
+static void bt_test_process_right(BtTest* bt_test);
+static void bt_test_process_ok(BtTest* bt_test);
+static void bt_test_process_back(BtTest* bt_test);
+
+static void bt_test_draw_callback(Canvas* canvas, void* _model) {
+    BtTestModel* model = _model;
+    char info_str[32];
+
+    const uint8_t param_height = 16;
+    const uint8_t param_width = 123;
+
+    canvas_clear(canvas);
+
+    uint8_t position = 0;
+    BtTestParamArray_it_t it;
+
+    canvas_set_font(canvas, FontSecondary);
+    for(BtTestParamArray_it(it, model->params); !BtTestParamArray_end_p(it);
+        BtTestParamArray_next(it)) {
+        uint8_t param_position = position - model->window_position;
+        uint8_t params_on_screen = 3;
+        uint8_t y_offset = 0;
+
+        if(param_position < params_on_screen) {
+            const BtTestParam* param = BtTestParamArray_cref(it);
+            uint8_t param_y = y_offset + (param_position * param_height);
+            uint8_t param_text_y = param_y + param_height - 4;
+
+            if(position == model->position) {
+                canvas_set_color(canvas, ColorBlack);
+                elements_slightly_rounded_box(
+                    canvas, 0, param_y + 1, param_width, param_height - 2);
+                canvas_set_color(canvas, ColorWhite);
+            } else {
+                canvas_set_color(canvas, ColorBlack);
+            }
+
+            canvas_draw_str(canvas, 6, param_text_y, param->label);
+
+            if(param->current_value_index > 0) {
+                canvas_draw_str(canvas, 50, param_text_y, "<");
+            }
+
+            canvas_draw_str(canvas, 61, param_text_y, string_get_cstr(param->current_value_text));
+
+            if(param->current_value_index < (param->values_count - 1)) {
+                canvas_draw_str(canvas, 113, param_text_y, ">");
+            }
+        }
+
+        position++;
+    }
+
+    elements_scrollbar(canvas, model->position, BtTestParamArray_size(model->params));
+    canvas_draw_str(canvas, 6, 60, model->message);
+    if(model->state == BtTestStateStarted) {
+        if(model->rssi != 0.0f) {
+            snprintf(info_str, sizeof(info_str), "RSSI:%3.1f dB", model->rssi);
+            canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
+        }
+    } else if(model->state == BtTestStateStopped) {
+        if(model->packets_num_rx) {
+            snprintf(info_str, sizeof(info_str), "%ld pack rcv", model->packets_num_rx);
+            canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
+        } else if(model->packets_num_tx) {
+            snprintf(info_str, sizeof(info_str), "%ld pack sent", model->packets_num_tx);
+            canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
+        }
+    }
+}
+
+static bool bt_test_input_callback(InputEvent* event, void* context) {
+    BtTest* bt_test = context;
+    furi_assert(bt_test);
+    bool consumed = false;
+
+    if(event->type == InputTypeShort) {
+        switch(event->key) {
+        case InputKeyUp:
+            consumed = true;
+            bt_test_process_up(bt_test);
+            break;
+        case InputKeyDown:
+            consumed = true;
+            bt_test_process_down(bt_test);
+            break;
+        case InputKeyLeft:
+            consumed = true;
+            bt_test_process_left(bt_test);
+            break;
+        case InputKeyRight:
+            consumed = true;
+            bt_test_process_right(bt_test);
+            break;
+        case InputKeyOk:
+            consumed = true;
+            bt_test_process_ok(bt_test);
+            break;
+        case InputKeyBack:
+            consumed = false;
+            bt_test_process_back(bt_test);
+            break;
+        default:
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void bt_test_process_up(BtTest* bt_test) {
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            uint8_t params_on_screen = 3;
+            if(model->position > 0) {
+                model->position--;
+                if(((model->position - model->window_position) < 1) &&
+                   model->window_position > 0) {
+                    model->window_position--;
+                }
+            } else {
+                model->position = BtTestParamArray_size(model->params) - 1;
+                if(model->position > (params_on_screen - 1)) {
+                    model->window_position = model->position - (params_on_screen - 1);
+                }
+            }
+            return true;
+        });
+}
+
+void bt_test_process_down(BtTest* bt_test) {
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            uint8_t params_on_screen = 3;
+            if(model->position < (BtTestParamArray_size(model->params) - 1)) {
+                model->position++;
+                if((model->position - model->window_position) > (params_on_screen - 2) &&
+                   model->window_position <
+                       (BtTestParamArray_size(model->params) - params_on_screen)) {
+                    model->window_position++;
+                }
+            } else {
+                model->position = 0;
+                model->window_position = 0;
+            }
+            return true;
+        });
+}
+
+BtTestParam* bt_test_get_selected_param(BtTestModel* model) {
+    BtTestParam* param = NULL;
+
+    BtTestParamArray_it_t it;
+    uint8_t position = 0;
+    for(BtTestParamArray_it(it, model->params); !BtTestParamArray_end_p(it);
+        BtTestParamArray_next(it)) {
+        if(position == model->position) {
+            break;
+        }
+        position++;
+    }
+
+    param = BtTestParamArray_ref(it);
+
+    furi_assert(param);
+    return param;
+}
+
+void bt_test_process_left(BtTest* bt_test) {
+    BtTestParam* param;
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            param = bt_test_get_selected_param(model);
+            if(param->current_value_index > 0) {
+                param->current_value_index--;
+                if(param->change_callback) {
+                    model->state = BtTestStateStopped;
+                    model->message = BT_TEST_START_MESSAGE;
+                    model->rssi = 0.0f;
+                    model->packets_num_rx = 0;
+                    model->packets_num_tx = 0;
+                }
+            }
+            return true;
+        });
+    if(param->change_callback) {
+        param->change_callback(param);
+    }
+}
+
+void bt_test_process_right(BtTest* bt_test) {
+    BtTestParam* param;
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            param = bt_test_get_selected_param(model);
+            if(param->current_value_index < (param->values_count - 1)) {
+                param->current_value_index++;
+                if(param->change_callback) {
+                    model->state = BtTestStateStopped;
+                    model->message = BT_TEST_START_MESSAGE;
+                    model->rssi = 0.0f;
+                    model->packets_num_rx = 0;
+                    model->packets_num_tx = 0;
+                }
+            }
+            return true;
+        });
+    if(param->change_callback) {
+        param->change_callback(param);
+    }
+}
+
+void bt_test_process_ok(BtTest* bt_test) {
+    BtTestState state;
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            if(model->state == BtTestStateStarted) {
+                model->state = BtTestStateStopped;
+                model->message = BT_TEST_START_MESSAGE;
+                model->rssi = 0.0f;
+                model->packets_num_rx = 0;
+                model->packets_num_tx = 0;
+            } else if(model->state == BtTestStateStopped) {
+                model->state = BtTestStateStarted;
+                model->message = BT_TEST_STOP_MESSAGE;
+            }
+            state = model->state;
+            return true;
+        });
+    if(bt_test->change_state_callback) {
+        bt_test->change_state_callback(state, bt_test->context);
+    }
+}
+
+void bt_test_process_back(BtTest* bt_test) {
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            model->state = BtTestStateStopped;
+            model->rssi = 0.0f;
+            model->packets_num_rx = 0;
+            model->packets_num_tx = 0;
+            return false;
+        });
+    if(bt_test->back_callback) {
+        bt_test->back_callback(bt_test->context);
+    }
+}
+
+BtTest* bt_test_alloc() {
+    BtTest* bt_test = furi_alloc(sizeof(BtTest));
+    bt_test->view = view_alloc();
+    view_set_context(bt_test->view, bt_test);
+    view_allocate_model(bt_test->view, ViewModelTypeLocking, sizeof(BtTestModel));
+    view_set_draw_callback(bt_test->view, bt_test_draw_callback);
+    view_set_input_callback(bt_test->view, bt_test_input_callback);
+
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            model->state = BtTestStateStopped;
+            model->message = "Ok - Start";
+            BtTestParamArray_init(model->params);
+            model->position = 0;
+            model->window_position = 0;
+            model->rssi = 0.0f;
+            model->packets_num_tx = 0;
+            model->packets_num_rx = 0;
+            return true;
+        });
+
+    return bt_test;
+}
+
+void bt_test_free(BtTest* bt_test) {
+    furi_assert(bt_test);
+
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            BtTestParamArray_it_t it;
+            for(BtTestParamArray_it(it, model->params); !BtTestParamArray_end_p(it);
+                BtTestParamArray_next(it)) {
+                string_clear(BtTestParamArray_ref(it)->current_value_text);
+            }
+            BtTestParamArray_clear(model->params);
+            return false;
+        });
+    view_free(bt_test->view);
+    free(bt_test);
+}
+
+View* bt_test_get_view(BtTest* bt_test) {
+    furi_assert(bt_test);
+    return bt_test->view;
+}
+
+BtTestParam* bt_test_param_add(
+    BtTest* bt_test,
+    const char* label,
+    uint8_t values_count,
+    BtTestParamChangeCallback change_callback,
+    void* context) {
+    BtTestParam* param = NULL;
+    furi_assert(label);
+    furi_assert(bt_test);
+
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            param = BtTestParamArray_push_new(model->params);
+            param->label = label;
+            param->values_count = values_count;
+            param->change_callback = change_callback;
+            param->context = context;
+            param->current_value_index = 0;
+            string_init(param->current_value_text);
+            return true;
+        });
+
+    return param;
+}
+
+void bt_test_set_rssi(BtTest* bt_test, float rssi) {
+    furi_assert(bt_test);
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            model->rssi = rssi;
+            return true;
+        });
+}
+
+void bt_test_set_packets_tx(BtTest* bt_test, uint32_t packets_num) {
+    furi_assert(bt_test);
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            model->packets_num_tx = packets_num;
+            return true;
+        });
+}
+
+void bt_test_set_packets_rx(BtTest* bt_test, uint32_t packets_num) {
+    furi_assert(bt_test);
+    with_view_model(
+        bt_test->view, (BtTestModel * model) {
+            model->packets_num_rx = packets_num;
+            return true;
+        });
+}
+
+void bt_test_set_change_state_callback(BtTest* bt_test, BtTestChangeStateCallback callback) {
+    furi_assert(bt_test);
+    furi_assert(callback);
+    bt_test->change_state_callback = callback;
+}
+
+void bt_test_set_back_callback(BtTest* bt_test, BtTestBackCallback callback) {
+    furi_assert(bt_test);
+    furi_assert(callback);
+    bt_test->back_callback = callback;
+}
+
+void bt_test_set_context(BtTest* bt_test, void* context) {
+    furi_assert(bt_test);
+    bt_test->context = context;
+}
+
+void bt_test_set_current_value_index(BtTestParam* param, uint8_t current_value_index) {
+    param->current_value_index = current_value_index;
+}
+
+void bt_test_set_current_value_text(BtTestParam* param, const char* current_value_text) {
+    string_set_str(param->current_value_text, current_value_text);
+}
+
+uint8_t bt_test_get_current_value_index(BtTestParam* param) {
+    return param->current_value_index;
+}
+
+void* bt_test_get_context(BtTestParam* param) {
+    return param->context;
+}

+ 46 - 0
applications/bt/bt_debug_app/views/bt_test.h

@@ -0,0 +1,46 @@
+#pragma once
+#include <gui/view.h>
+
+typedef enum {
+    BtTestStateStarted,
+    BtTestStateStopped,
+} BtTestState;
+
+typedef struct BtTest BtTest;
+typedef void (*BtTestChangeStateCallback)(BtTestState state, void* context);
+typedef void (*BtTestBackCallback)(void* context);
+typedef struct BtTestParam BtTestParam;
+typedef void (*BtTestParamChangeCallback)(BtTestParam* param);
+
+BtTest* bt_test_alloc();
+
+void bt_test_free(BtTest* bt_test);
+
+View* bt_test_get_view(BtTest* bt_test);
+
+BtTestParam* bt_test_param_add(
+    BtTest* bt_test,
+    const char* label,
+    uint8_t values_count,
+    BtTestParamChangeCallback change_callback,
+    void* context);
+
+void bt_test_set_change_state_callback(BtTest* bt_test, BtTestChangeStateCallback callback);
+
+void bt_test_set_back_callback(BtTest* bt_test, BtTestBackCallback callback);
+
+void bt_test_set_context(BtTest* bt_test, void* context);
+
+void bt_test_set_rssi(BtTest* bt_test, float rssi);
+
+void bt_test_set_packets_tx(BtTest* bt_test, uint32_t packets_num);
+
+void bt_test_set_packets_rx(BtTest* bt_test, uint32_t packets_num);
+
+void bt_test_set_current_value_index(BtTestParam* param, uint8_t current_value_index);
+
+void bt_test_set_current_value_text(BtTestParam* param, const char* current_value_text);
+
+uint8_t bt_test_get_current_value_index(BtTestParam* param);
+
+void* bt_test_get_context(BtTestParam* param);

+ 30 - 0
applications/bt/bt_debug_app/views/bt_test_types.h

@@ -0,0 +1,30 @@
+#pragma once
+
+typedef enum {
+    BtTestModeRx,
+    BtTestModeTx,
+    BtTestModeTxHopping,
+} BtTestMode;
+
+typedef enum {
+    BtTestChannel2402 = 0,
+    BtTestChannel2440 = 19,
+    BtTestChannel2480 = 39,
+} BtTestChannel;
+
+typedef enum {
+    BtPower0dB = 0x19,
+    BtPower2dB = 0x1B,
+    BtPower4dB = 0x1D,
+    BtPower6dB = 0x1F,
+} BtTestPower;
+
+typedef enum {
+    BtDataRate1M = 1,
+    BtDataRate2M = 2,
+} BtTestDataRate;
+
+typedef struct {
+    uint32_t value;
+    const char* str;
+} BtTestParamValue;

+ 0 - 53
applications/bt/bt_i.h

@@ -1,53 +0,0 @@
-#pragma once
-
-#include "bt.h"
-#include "bt_views.h"
-#include "bt_types.h"
-
-#include <furi.h>
-#include <furi-hal.h>
-
-#include <cli/cli.h>
-
-#include <gui/gui.h>
-#include <gui/view_port.h>
-#include <gui/view.h>
-#include <gui/view_dispatcher.h>
-
-#include <menu/menu.h>
-#include <menu/menu_item.h>
-
-struct Bt {
-    osMessageQueueId_t message_queue;
-    BtState state;
-    osTimerId_t update_status_timer;
-    osTimerId_t update_param_timer;
-    Gui* gui;
-    ValueMutex* menu;
-    // Status bar
-    ViewPort* statusbar_view_port;
-    // Menu
-    IconAnimation* menu_icon;
-    MenuItem* menu_item;
-    View* view_test_carrier;
-    View* view_test_packet_tx;
-    View* view_test_packet_rx;
-    View* view_start_app;
-    ViewDispatcher* view_dispatcher;
-};
-
-Bt* bt_alloc();
-
-void bt_draw_statusbar_callback(Canvas* canvas, void* context);
-
-BtTestChannel bt_switch_channel(InputKey key, BtTestChannel inst_chan);
-
-void bt_draw_statusbar_callback(Canvas* canvas, void* context);
-
-void bt_menu_test_carrier(void* context);
-
-void bt_menu_test_packet_tx(void* context);
-
-void bt_menu_test_packet_rx(void* context);
-
-void bt_menu_start_app(void* context);

+ 74 - 0
applications/bt/bt_service/bt.c

@@ -0,0 +1,74 @@
+#include "bt_i.h"
+
+#define BT_SERVICE_TAG "BT"
+
+// static void bt_update_statusbar(void* arg) {
+//     furi_assert(arg);
+//     Bt* bt = arg;
+//     BtMessage m = {.type = BtMessageTypeUpdateStatusbar};
+//     furi_check(osMessageQueuePut(bt->message_queue, &m, 0, osWaitForever) == osOK);
+// }
+
+static void bt_draw_statusbar_callback(Canvas* canvas, void* context) {
+    canvas_draw_icon(canvas, 0, 0, &I_Bluetooth_5x8);
+}
+
+static ViewPort* bt_statusbar_view_port_alloc() {
+    ViewPort* statusbar_view_port = view_port_alloc();
+    view_port_set_width(statusbar_view_port, 5);
+    view_port_draw_callback_set(statusbar_view_port, bt_draw_statusbar_callback, NULL);
+    view_port_enabled_set(statusbar_view_port, false);
+    return statusbar_view_port;
+}
+
+Bt* bt_alloc() {
+    Bt* bt = furi_alloc(sizeof(Bt));
+    // Load settings
+    if(!bt_settings_load(&bt->bt_settings)) {
+        bt_settings_save(&bt->bt_settings);
+    }
+    // Alloc queue
+    bt->message_queue = osMessageQueueNew(8, sizeof(BtMessage), NULL);
+
+    // doesn't make sense if we waiting for transition on service start
+    // bt->update_status_timer = osTimerNew(bt_update_statusbar, osTimerPeriodic, bt, NULL);
+    // osTimerStart(bt->update_status_timer, 4000);
+
+    // Setup statusbar view port
+    bt->statusbar_view_port = bt_statusbar_view_port_alloc();
+    // Gui
+    bt->gui = furi_record_open("gui");
+    gui_add_view_port(bt->gui, bt->statusbar_view_port, GuiLayerStatusBarLeft);
+
+    return bt;
+}
+
+int32_t bt_srv() {
+    Bt* bt = bt_alloc();
+    furi_record_create("bt", bt);
+    furi_hal_bt_init();
+
+    if(bt->bt_settings.enabled) {
+        if(!furi_hal_bt_wait_startup()) {
+            FURI_LOG_E(BT_SERVICE_TAG, "Core2 startup failed");
+        } else {
+            view_port_enabled_set(bt->statusbar_view_port, true);
+            bool bt_app_started = furi_hal_bt_start_app();
+            if(!bt_app_started) {
+                FURI_LOG_E(BT_SERVICE_TAG, "BT App start failed");
+            } else {
+                FURI_LOG_I(BT_SERVICE_TAG, "BT App started");
+            }
+        }
+    }
+
+    BtMessage message;
+    while(1) {
+        furi_check(osMessageQueueGet(bt->message_queue, &message, NULL, osWaitForever) == osOK);
+        if(message.type == BtMessageTypeUpdateStatusbar) {
+            // Update statusbar
+            view_port_enabled_set(bt->statusbar_view_port, furi_hal_bt_is_alive());
+        }
+    }
+    return 0;
+}

+ 0 - 0
applications/bt/bt.h → applications/bt/bt_service/bt.h


+ 29 - 0
applications/bt/bt_service/bt_i.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include "bt.h"
+
+#include <furi.h>
+#include <furi-hal.h>
+
+#include <gui/gui.h>
+#include <gui/view_port.h>
+#include <gui/view.h>
+
+#include "../bt_settings.h"
+
+typedef enum {
+    BtMessageTypeUpdateStatusbar,
+} BtMessageType;
+
+typedef struct {
+    BtMessageType type;
+    void* param;
+} BtMessage;
+
+struct Bt {
+    BtSettings bt_settings;
+    osMessageQueueId_t message_queue;
+    osTimerId_t update_status_timer;
+    Gui* gui;
+    ViewPort* statusbar_view_port;
+};

+ 50 - 0
applications/bt/bt_settings.c

@@ -0,0 +1,50 @@
+#include "bt_settings.h"
+#include <furi.h>
+#include <file-worker.h>
+
+#define BT_SETTINGS_TAG "bt settings"
+#define BT_SETTINGS_PATH "/int/bt.settings"
+
+bool bt_settings_load(BtSettings* bt_settings) {
+    furi_assert(bt_settings);
+    bool file_loaded = false;
+    BtSettings settings = {};
+
+    FURI_LOG_I(BT_SETTINGS_TAG, "Loading settings from \"%s\"", BT_SETTINGS_PATH);
+    FileWorker* file_worker = file_worker_alloc(true);
+    if(file_worker_open(file_worker, BT_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
+        if(file_worker_read(file_worker, &settings, sizeof(settings))) {
+            file_loaded = true;
+        }
+    }
+    file_worker_free(file_worker);
+
+    if(file_loaded) {
+        FURI_LOG_I(BT_SETTINGS_TAG, "Settings load success");
+        if(settings.version != BT_SETTINGS_VERSION) {
+            FURI_LOG_E(BT_SETTINGS_TAG, "Settings version mismatch");
+        } else {
+            osKernelLock();
+            *bt_settings = settings;
+            osKernelUnlock();
+        }
+    } else {
+        FURI_LOG_E(BT_SETTINGS_TAG, "Settings load failed");
+    }
+    return file_loaded;
+}
+
+bool bt_settings_save(BtSettings* bt_settings) {
+    furi_assert(bt_settings);
+    bool result = false;
+
+    FileWorker* file_worker = file_worker_alloc(true);
+    if(file_worker_open(file_worker, BT_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
+        if(file_worker_write(file_worker, bt_settings, sizeof(BtSettings))) {
+            FURI_LOG_I(BT_SETTINGS_TAG, "Settings saved to \"%s\"", BT_SETTINGS_PATH);
+            result = true;
+        }
+    }
+    file_worker_free(file_worker);
+    return result;
+}

+ 15 - 0
applications/bt/bt_settings.h

@@ -0,0 +1,15 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define BT_SETTINGS_VERSION (0)
+
+typedef struct {
+    uint8_t version;
+    bool enabled;
+} BtSettings;
+
+bool bt_settings_load(BtSettings* bt_settings);
+
+bool bt_settings_save(BtSettings* bt_settings);

+ 67 - 0
applications/bt/bt_settings_app/bt_settings_app.c

@@ -0,0 +1,67 @@
+#include "bt_settings_app.h"
+
+static bool bt_settings_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    BtSettingsApp* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool bt_settings_back_event_callback(void* context) {
+    furi_assert(context);
+    BtSettingsApp* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+BtSettingsApp* bt_settings_app_alloc() {
+    BtSettingsApp* app = furi_alloc(sizeof(BtSettingsApp));
+
+    // Load settings
+    bt_settings_load(&app->settings);
+    app->gui = furi_record_open("gui");
+
+    app->view_dispatcher = view_dispatcher_alloc();
+    app->scene_manager = scene_manager_alloc(&bt_settings_scene_handlers, app);
+    view_dispatcher_enable_queue(app->view_dispatcher);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+
+    view_dispatcher_set_custom_event_callback(
+        app->view_dispatcher, bt_settings_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        app->view_dispatcher, bt_settings_back_event_callback);
+
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    app->submenu = submenu_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, BtSettingsAppViewSubmenu, submenu_get_view(app->submenu));
+    app->dialog_ex = dialog_ex_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, BtSettingsAppViewDialogEx, dialog_ex_get_view(app->dialog_ex));
+
+    scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneStart);
+    return app;
+}
+
+void bt_settings_app_free(BtSettingsApp* app) {
+    furi_assert(app);
+    // Submenu
+    view_dispatcher_remove_view(app->view_dispatcher, BtSettingsAppViewSubmenu);
+    submenu_free(app->submenu);
+    // Dialog
+    view_dispatcher_remove_view(app->view_dispatcher, BtSettingsAppViewDialogEx);
+    dialog_ex_free(app->dialog_ex);
+    // View dispatcher
+    view_dispatcher_free(app->view_dispatcher);
+    scene_manager_free(app->scene_manager);
+    // Records
+    furi_record_close("gui");
+    free(app);
+}
+
+extern int32_t bt_settings_app(void* p) {
+    BtSettingsApp* app = bt_settings_app_alloc();
+    view_dispatcher_run(app->view_dispatcher);
+    bt_settings_save(&app->settings);
+    bt_settings_app_free(app);
+    return 0;
+}

+ 27 - 0
applications/bt/bt_settings_app/bt_settings_app.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+
+#include <gui/modules/submenu.h>
+#include <gui/modules/dialog_ex.h>
+
+#include "../bt_settings.h"
+#include "scenes/bt_settings_scene.h"
+
+typedef struct {
+    BtSettings settings;
+    Gui* gui;
+    SceneManager* scene_manager;
+    ViewDispatcher* view_dispatcher;
+    Submenu* submenu;
+    DialogEx* dialog_ex;
+} BtSettingsApp;
+
+typedef enum {
+    BtSettingsAppViewSubmenu,
+    BtSettingsAppViewDialogEx,
+} BtSettingsAppView;

+ 30 - 0
applications/bt/bt_settings_app/scenes/bt_settings_scene.c

@@ -0,0 +1,30 @@
+#include "bt_settings_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const bt_settings_on_enter_handlers[])(void*) = {
+#include "bt_settings_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const bt_settings_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "bt_settings_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const bt_settings_on_exit_handlers[])(void* context) = {
+#include "bt_settings_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers bt_settings_scene_handlers = {
+    .on_enter_handlers = bt_settings_on_enter_handlers,
+    .on_event_handlers = bt_settings_on_event_handlers,
+    .on_exit_handlers = bt_settings_on_exit_handlers,
+    .scene_num = BtSettingsAppSceneNum,
+};

+ 29 - 0
applications/bt/bt_settings_app/scenes/bt_settings_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) BtSettingsAppScene##id,
+typedef enum {
+#include "bt_settings_scene_config.h"
+    BtSettingsAppSceneNum,
+} BtSettingsAppScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers bt_settings_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "bt_settings_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "bt_settings_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "bt_settings_scene_config.h"
+#undef ADD_SCENE

+ 2 - 0
applications/bt/bt_settings_app/scenes/bt_settings_scene_config.h

@@ -0,0 +1,2 @@
+ADD_SCENE(bt_settings, start, Start)
+ADD_SCENE(bt_settings, disable_dialog, DisableDialog)

+ 44 - 0
applications/bt/bt_settings_app/scenes/bt_settings_scene_disable_dialog.c

@@ -0,0 +1,44 @@
+#include "../bt_settings_app.h"
+#include <furi-hal-boot.h>
+#include <furi-hal-power.h>
+
+static void bt_setting_disable_dialog_callback(DialogExResult result, void* context) {
+    BtSettingsApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, result);
+}
+
+void bt_settings_scene_disable_dialog_on_enter(void* context) {
+    BtSettingsApp* app = context;
+    DialogEx* dialog_ex = app->dialog_ex;
+    dialog_ex_set_text(
+        dialog_ex, "Reboot device\nto disable Bluetooth", 64, 32, AlignCenter, AlignCenter);
+    dialog_ex_set_left_button_text(dialog_ex, "Back");
+    dialog_ex_set_right_button_text(dialog_ex, "Reboot");
+    dialog_ex_set_result_callback(dialog_ex, bt_setting_disable_dialog_callback);
+    dialog_ex_set_context(dialog_ex, app);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, BtSettingsAppViewDialogEx);
+}
+
+bool bt_settings_scene_disable_dialog_on_event(void* context, SceneManagerEvent event) {
+    BtSettingsApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == DialogExResultLeft) {
+            scene_manager_previous_scene(app->scene_manager);
+            consumed = true;
+        } else if(event.event == DialogExResultRight) {
+            app->settings.enabled = false;
+            bt_settings_save(&app->settings);
+            furi_hal_boot_set_mode(FuriHalBootModeNormal);
+            furi_hal_power_reset();
+        }
+    }
+    return consumed;
+}
+
+void bt_settings_scene_disable_dialog_on_exit(void* context) {
+    BtSettingsApp* app = context;
+    dialog_ex_clean(app->dialog_ex);
+}

+ 56 - 0
applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c

@@ -0,0 +1,56 @@
+#include "../bt_settings_app.h"
+#include "furi-hal-bt.h"
+
+enum BtSettingsAppStartSubmenuIndex {
+    BtSettingsAppStartSubmenuIndexEnable,
+};
+
+static void bt_settings_scene_start_submenu_callback(void* context, uint32_t index) {
+    BtSettingsApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void bt_settings_scene_start_on_enter(void* context) {
+    BtSettingsApp* app = context;
+    Submenu* submenu = app->submenu;
+
+    const char* submenu_label = app->settings.enabled ? "Disable" : "Enable";
+    submenu_add_item(
+        submenu,
+        submenu_label,
+        BtSettingsAppStartSubmenuIndexEnable,
+        bt_settings_scene_start_submenu_callback,
+        app);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, BtSettingsAppViewSubmenu);
+}
+
+bool bt_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
+    BtSettingsApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == BtSettingsAppStartSubmenuIndexEnable) {
+            if(!app->settings.enabled) {
+                app->settings.enabled = true;
+                furi_hal_bt_start_app();
+                submenu_clean(app->submenu);
+                submenu_add_item(
+                    app->submenu,
+                    "Disable",
+                    BtSettingsAppStartSubmenuIndexEnable,
+                    bt_settings_scene_start_submenu_callback,
+                    app);
+            } else {
+                scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneDisableDialog);
+            }
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void bt_settings_scene_start_on_exit(void* context) {
+    BtSettingsApp* app = context;
+    submenu_clean(app->submenu);
+}

+ 0 - 65
applications/bt/bt_types.h

@@ -1,65 +0,0 @@
-#pragma once
-
-#include <stdint.h>
-
-typedef enum {
-    BtMessageTypeStartTestCarrier,
-    BtMessageTypeHoppingTx,
-    BtMessageTypeStopTestCarrier,
-    BtMessageTypeSetupTestPacketTx,
-    BtMessageTypeSetupTestPacketRx,
-    BtMessageTypeStartTestPacketTx,
-    BtMessageTypeStartTestPacketRx,
-    BtMessageTypeStopTestPacket,
-    BtMessageTypeStartApp,
-    BtMessageTypeUpdateStatusbar,
-} BtMessageType;
-
-typedef enum {
-    BtStateReady,
-    BtStateCarrierTx,
-    BtStateHoppingTx,
-    BtStateCarrierRxStart,
-    BtStateCarrierRxRunning,
-    BtStatePacketSetup,
-    BtStatePacketStart,
-    BtStatePacketRunning,
-    BtStateStartedApp,
-} BtStateType;
-
-typedef enum {
-    BtChannel2402 = 0,
-    BtChannel2440 = 19,
-    BtChannel2480 = 39,
-} BtTestChannel;
-
-typedef enum {
-    BtPower0dB = 0x19,
-    BtPower2dB = 0x1B,
-    BtPower4dB = 0x1D,
-    BtPower6dB = 0x1F,
-} BtTestPower;
-
-typedef enum {
-    BtDataRate1M = 1,
-    BtDataRate2M = 2,
-} BtTestDataRate;
-
-typedef struct {
-    BtTestChannel channel;
-    BtTestPower power;
-    BtTestDataRate datarate;
-    float rssi;
-    uint16_t packets_sent;
-    uint16_t packets_received;
-} BtTestParam;
-
-typedef struct {
-    BtMessageType type;
-    BtTestParam param;
-} BtMessage;
-
-typedef struct {
-    BtStateType type;
-    BtTestParam param;
-} BtState;

+ 0 - 250
applications/bt/bt_views.c

@@ -1,250 +0,0 @@
-#include "bt_views.h"
-
-void bt_view_test_carrier_draw(Canvas* canvas, void* model) {
-    BtViewTestCarrierModel* m = model;
-    canvas_clear(canvas);
-    canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str(canvas, 0, 12, "Performing Carrier test");
-    if(m->type == BtStateCarrierTx) {
-        canvas_draw_str(canvas, 0, 24, "Manual Carrier TX");
-    } else if(m->type == BtStateHoppingTx) {
-        canvas_draw_str(canvas, 0, 24, "Carrier TX Hopping mode");
-    } else if(m->type == BtStateCarrierRxRunning) {
-        canvas_draw_str(canvas, 0, 24, "Manual Carrier RX");
-    }
-    char buffer[32];
-    snprintf(buffer, sizeof(buffer), "Channel:%d MHz", m->channel * 2 + 2402);
-    canvas_draw_str(canvas, 0, 36, buffer);
-    if(m->type == BtStateCarrierTx || m->type == BtStateHoppingTx) {
-        snprintf(buffer, sizeof(buffer), "Power:%d dB", m->power - BtPower0dB);
-    } else if(m->type == BtStateCarrierRxRunning) {
-        snprintf(buffer, sizeof(buffer), "RSSI: %3.1f dB", m->rssi);
-    }
-    canvas_draw_str(canvas, 0, 48, buffer);
-}
-
-void bt_view_test_packet_rx_draw(Canvas* canvas, void* model) {
-    BtViewTestPacketRxModel* m = model;
-    canvas_clear(canvas);
-    canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str(canvas, 0, 12, "Performing packets RX test");
-    if(m->type == BtStatePacketSetup) {
-        canvas_draw_str(canvas, 0, 24, "Setup parameters. Ok to start");
-    } else {
-        canvas_draw_str(canvas, 0, 24, "Receiving packets ...");
-    }
-    char buffer[32];
-    snprintf(buffer, sizeof(buffer), "Channel:%d MHz", m->channel * 2 + 2402);
-    canvas_draw_str(canvas, 0, 36, buffer);
-    snprintf(buffer, sizeof(buffer), "Datarate:%d Mbps", m->datarate);
-    canvas_draw_str(canvas, 0, 48, buffer);
-    if(m->type == BtStatePacketSetup) {
-        snprintf(buffer, sizeof(buffer), "%d packets received", m->packets_received);
-    } else {
-        snprintf(buffer, sizeof(buffer), "RSSI: %3.1f dB", m->rssi);
-    }
-    canvas_draw_str(canvas, 0, 60, buffer);
-}
-
-void bt_view_test_packet_tx_draw(Canvas* canvas, void* model) {
-    BtViewTestPacketTxModel* m = model;
-    canvas_clear(canvas);
-    canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str(canvas, 0, 12, "Packets send TX test");
-    if(m->type == BtStatePacketSetup) {
-        canvas_draw_str(canvas, 0, 24, "Setup parameters. Ok to start");
-    } else {
-        canvas_draw_str(canvas, 0, 24, "Sending packets ...");
-    }
-    char buffer[32];
-    snprintf(buffer, sizeof(buffer), "Channel:%d MHz", m->channel * 2 + 2402);
-    canvas_draw_str(canvas, 0, 36, buffer);
-    snprintf(buffer, sizeof(buffer), "Datarate:%d Mbps", m->datarate);
-    canvas_draw_str(canvas, 0, 48, buffer);
-    if(m->packets_sent && m->type == BtStatePacketSetup) {
-        snprintf(buffer, sizeof(buffer), "%d packets sent", m->packets_sent);
-        canvas_draw_str(canvas, 0, 60, buffer);
-    }
-}
-
-void bt_view_app_draw(Canvas* canvas, void* model) {
-    canvas_clear(canvas);
-    canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str(canvas, 0, 12, "Start BLE app");
-}
-
-BtTestChannel bt_switch_channel(InputKey key, BtTestChannel inst_chan) {
-    uint8_t pos = 0;
-    BtTestChannel arr[] = {BtChannel2402, BtChannel2440, BtChannel2480};
-    for(pos = 0; pos < sizeof(arr); pos++) {
-        if(arr[pos] == inst_chan) {
-            break;
-        }
-    }
-    if(key == InputKeyRight) {
-        pos = (pos + 1) % sizeof(arr);
-        return arr[pos];
-    } else if(key == InputKeyLeft) {
-        if(pos) {
-            return arr[pos - 1];
-        } else {
-            return arr[sizeof(arr) - 1];
-        }
-    }
-    return arr[0];
-}
-
-bool bt_view_test_carrier_input(InputEvent* event, void* context) {
-    furi_assert(event);
-    furi_assert(context);
-    Bt* bt = context;
-    if(event->type == InputTypeShort) {
-        if(event->key == InputKeyBack) {
-            if(osTimerIsRunning(bt->update_param_timer)) {
-                osTimerStop(bt->update_param_timer);
-            }
-            BtMessage m = {.type = BtMessageTypeStopTestCarrier};
-            furi_check(osMessageQueuePut(bt->message_queue, &m, 0, osWaitForever) == osOK);
-            view_dispatcher_switch_to_view(bt->view_dispatcher, VIEW_NONE);
-            return true;
-        } else {
-            if(event->key == InputKeyRight || event->key == InputKeyLeft) {
-                bt->state.param.channel = bt_switch_channel(event->key, bt->state.param.channel);
-            } else if(event->key == InputKeyUp) {
-                if(bt->state.param.power < BtPower6dB) {
-                    bt->state.param.power += 2;
-                }
-            } else if(event->key == InputKeyDown) {
-                if(bt->state.param.power > BtPower0dB) {
-                    bt->state.param.power -= 2;
-                }
-            } else if(event->key == InputKeyOk) {
-                if(bt->state.type == BtStateCarrierTx) {
-                    bt->state.type = BtStateHoppingTx;
-                    osTimerStart(bt->update_param_timer, 2000);
-                } else if(bt->state.type == BtStateHoppingTx) {
-                    osTimerStop(bt->update_param_timer);
-                    bt->state.type = BtStateCarrierRxStart;
-                    osTimerStart(bt->update_param_timer, 200);
-                } else {
-                    osTimerStop(bt->update_param_timer);
-                    bt->state.type = BtStateCarrierTx;
-                }
-            }
-            BtMessage m = {
-                .type = BtMessageTypeStartTestCarrier,
-                .param.channel = bt->state.param.channel,
-                .param.power = bt->state.param.power};
-            furi_check(osMessageQueuePut(bt->message_queue, &m, 0, osWaitForever) == osOK);
-            return true;
-        }
-    }
-    return false;
-}
-
-bool bt_view_test_packet_tx_input(InputEvent* event, void* context) {
-    furi_assert(event);
-    furi_assert(context);
-    Bt* bt = context;
-    if(event->type == InputTypeShort) {
-        if(event->key < InputKeyOk) {
-            // Process InputKeyUp, InputKeyDown, InputKeyLeft, InputKeyRight
-            if(event->key == InputKeyRight || event->key == InputKeyLeft) {
-                bt->state.param.channel = bt_switch_channel(event->key, bt->state.param.channel);
-            } else if(event->key == InputKeyUp) {
-                if(bt->state.param.datarate < BtDataRate2M) {
-                    bt->state.param.datarate += 1;
-                }
-            } else if(event->key == InputKeyDown) {
-                if(bt->state.param.datarate > BtDataRate1M) {
-                    bt->state.param.datarate -= 1;
-                }
-            }
-            bt->state.type = BtStatePacketSetup;
-            BtMessage m = {
-                .type = BtMessageTypeSetupTestPacketTx,
-                .param.channel = bt->state.param.channel,
-                .param.datarate = bt->state.param.datarate,
-            };
-            furi_check(osMessageQueuePut(bt->message_queue, &m, 0, osWaitForever) == osOK);
-            return true;
-        } else if(event->key == InputKeyOk) {
-            if(bt->state.type == BtStatePacketSetup) {
-                bt->state.type = BtStatePacketStart;
-            } else if(bt->state.type == BtStatePacketStart) {
-                bt->state.type = BtStatePacketSetup;
-            }
-            BtMessage m = {
-                .type = BtMessageTypeStartTestPacketTx,
-                .param.channel = bt->state.param.channel,
-                .param.datarate = bt->state.param.datarate,
-            };
-            furi_check(osMessageQueuePut(bt->message_queue, &m, 0, osWaitForever) == osOK);
-            return true;
-        } else if(event->key == InputKeyBack) {
-            BtMessage m = {
-                .type = BtMessageTypeStopTestPacket,
-            };
-            furi_check(osMessageQueuePut(bt->message_queue, &m, 0, osWaitForever) == osOK);
-            view_dispatcher_switch_to_view(bt->view_dispatcher, VIEW_NONE);
-            return true;
-        }
-    }
-    return false;
-}
-
-bool bt_view_test_packet_rx_input(InputEvent* event, void* context) {
-    furi_assert(event);
-    furi_assert(context);
-    Bt* bt = context;
-    if(event->type == InputTypeShort) {
-        if(event->key < InputKeyOk) {
-            // Process InputKeyUp, InputKeyDown, InputKeyLeft, InputKeyRight
-            if(event->key == InputKeyRight || event->key == InputKeyLeft) {
-                bt->state.param.channel = bt_switch_channel(event->key, bt->state.param.channel);
-            } else if(event->key == InputKeyUp) {
-                if(bt->state.param.datarate < BtDataRate2M) {
-                    bt->state.param.datarate += 1;
-                }
-            } else if(event->key == InputKeyDown) {
-                if(bt->state.param.datarate > BtDataRate1M) {
-                    bt->state.param.datarate -= 1;
-                }
-            }
-            bt->state.type = BtStatePacketSetup;
-            BtMessage m = {
-                .type = BtMessageTypeSetupTestPacketRx,
-                .param.channel = bt->state.param.channel,
-                .param.datarate = bt->state.param.datarate,
-            };
-            furi_check(osMessageQueuePut(bt->message_queue, &m, 0, osWaitForever) == osOK);
-            return true;
-        } else if(event->key == InputKeyOk) {
-            if(bt->state.type == BtStatePacketSetup) {
-                bt->state.type = BtStatePacketStart;
-                osTimerStart(bt->update_param_timer, 200);
-            } else if(bt->state.type == BtStatePacketRunning) {
-                bt->state.type = BtStatePacketSetup;
-                osTimerStop(bt->update_param_timer);
-            }
-            BtMessage m = {
-                .type = BtMessageTypeStartTestPacketRx,
-                .param.channel = bt->state.param.channel,
-                .param.datarate = bt->state.param.datarate,
-            };
-            furi_check(osMessageQueuePut(bt->message_queue, &m, 0, osWaitForever) == osOK);
-            return true;
-        } else if(event->key == InputKeyBack) {
-            if(osTimerIsRunning(bt->update_param_timer)) {
-                osTimerStop(bt->update_param_timer);
-            }
-            BtMessage m = {
-                .type = BtMessageTypeStopTestPacket,
-            };
-            furi_check(osMessageQueuePut(bt->message_queue, &m, 0, osWaitForever) == osOK);
-            view_dispatcher_switch_to_view(bt->view_dispatcher, VIEW_NONE);
-            return true;
-        }
-    }
-    return false;
-}

+ 0 - 54
applications/bt/bt_views.h

@@ -1,54 +0,0 @@
-#pragma once
-
-#include "bt_i.h"
-#include "bt_types.h"
-
-#include <stdint.h>
-#include <stdbool.h>
-#include <gui/canvas.h>
-#include <furi.h>
-#include <gui/view_dispatcher.h>
-#include <gui/view.h>
-
-typedef enum {
-    BtViewTestCarrier,
-    BtViewTestPacketTx,
-    BtViewTestPacketRx,
-    BtViewStartApp,
-} BtView;
-
-typedef struct {
-    BtStateType type;
-    BtTestChannel channel;
-    BtTestPower power;
-    float rssi;
-} BtViewTestCarrierModel;
-
-typedef struct {
-    BtStateType type;
-    BtTestChannel channel;
-    BtTestDataRate datarate;
-    uint16_t packets_sent;
-} BtViewTestPacketTxModel;
-
-typedef struct {
-    BtStateType type;
-    BtTestChannel channel;
-    BtTestDataRate datarate;
-    float rssi;
-    uint16_t packets_received;
-} BtViewTestPacketRxModel;
-
-void bt_view_test_carrier_draw(Canvas* canvas, void* model);
-
-bool bt_view_test_carrier_input(InputEvent* event, void* context);
-
-void bt_view_test_packet_tx_draw(Canvas* canvas, void* model);
-
-bool bt_view_test_packet_tx_input(InputEvent* event, void* context);
-
-void bt_view_test_packet_rx_draw(Canvas* canvas, void* model);
-
-bool bt_view_test_packet_rx_input(InputEvent* event, void* context);
-
-void bt_view_app_draw(Canvas* canvas, void* model);

+ 19 - 0
applications/gui/modules/dialog_ex.c

@@ -243,3 +243,22 @@ void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text) {
             return true;
             return true;
         });
         });
 }
 }
+
+void dialog_ex_clean(DialogEx* dialog_ex) {
+    furi_assert(dialog_ex);
+    TextElement clean_text_el = {
+        .text = NULL, .x = 0, .y = 0, .horizontal = AlignLeft, .vertical = AlignLeft};
+    IconElement clean_icon_el = {.icon = NULL, .x = 0, .y = 0};
+    with_view_model(
+        dialog_ex->view, (DialogExModel * model) {
+            model->header = clean_text_el;
+            model->text = clean_text_el;
+            model->icon = clean_icon_el;
+            model->left_text = NULL;
+            model->center_text = NULL;
+            model->right_text = NULL;
+            return true;
+        });
+    dialog_ex->context = NULL;
+    dialog_ex->callback = NULL;
+}

+ 21 - 15
applications/gui/modules/dialog_ex.h

@@ -5,50 +5,51 @@
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-/* Dialog anonymous structure */
+/** Dialog anonymous structure */
 typedef struct DialogEx DialogEx;
 typedef struct DialogEx DialogEx;
 
 
-/* DialogEx result */
+/** DialogEx result */
 typedef enum {
 typedef enum {
     DialogExResultLeft,
     DialogExResultLeft,
     DialogExResultCenter,
     DialogExResultCenter,
     DialogExResultRight,
     DialogExResultRight,
 } DialogExResult;
 } DialogExResult;
 
 
-/* DialogEx result callback type
+/** DialogEx result callback type
  * @warning comes from GUI thread
  * @warning comes from GUI thread
  */
  */
 typedef void (*DialogExResultCallback)(DialogExResult result, void* context);
 typedef void (*DialogExResultCallback)(DialogExResult result, void* context);
 
 
-/* Allocate and initialize dialog
- * This dialog used to ask simple questions like Yes/
+/** Allocate and initialize dialog
+ * This dialog used to ask simple questions
+ * @return DialogEx instance
  */
  */
 DialogEx* dialog_ex_alloc();
 DialogEx* dialog_ex_alloc();
 
 
-/* Deinitialize and free dialog
+/** Deinitialize and free dialog
  * @param dialog - DialogEx instance
  * @param dialog - DialogEx instance
  */
  */
 void dialog_ex_free(DialogEx* dialog_ex);
 void dialog_ex_free(DialogEx* dialog_ex);
 
 
-/* Get dialog view
+/** Get dialog view
  * @param dialog - DialogEx instance
  * @param dialog - DialogEx instance
  * @return View instance that can be used for embedding
  * @return View instance that can be used for embedding
  */
  */
 View* dialog_ex_get_view(DialogEx* dialog_ex);
 View* dialog_ex_get_view(DialogEx* dialog_ex);
 
 
-/* Set dialog result callback
+/** Set dialog result callback
  * @param dialog_ex - DialogEx instance
  * @param dialog_ex - DialogEx instance
  * @param callback - result callback function
  * @param callback - result callback function
  */
  */
 void dialog_ex_set_result_callback(DialogEx* dialog_ex, DialogExResultCallback callback);
 void dialog_ex_set_result_callback(DialogEx* dialog_ex, DialogExResultCallback callback);
 
 
-/* Set dialog context
+/** Set dialog context
  * @param dialog_ex - DialogEx instance
  * @param dialog_ex - DialogEx instance
  * @param context - context pointer, will be passed to result callback
  * @param context - context pointer, will be passed to result callback
  */
  */
 void dialog_ex_set_context(DialogEx* dialog_ex, void* context);
 void dialog_ex_set_context(DialogEx* dialog_ex, void* context);
 
 
-/* Set dialog header text
+/** Set dialog header text
  * If text is null, dialog header will not be rendered
  * If text is null, dialog header will not be rendered
  * @param dialog - DialogEx instance
  * @param dialog - DialogEx instance
  * @param text - text to be shown, can be multiline
  * @param text - text to be shown, can be multiline
@@ -63,7 +64,7 @@ void dialog_ex_set_header(
     Align horizontal,
     Align horizontal,
     Align vertical);
     Align vertical);
 
 
-/* Set dialog text
+/** Set dialog text
  * If text is null, dialog text will not be rendered
  * If text is null, dialog text will not be rendered
  * @param dialog - DialogEx instance
  * @param dialog - DialogEx instance
  * @param text - text to be shown, can be multiline
  * @param text - text to be shown, can be multiline
@@ -78,7 +79,7 @@ void dialog_ex_set_text(
     Align horizontal,
     Align horizontal,
     Align vertical);
     Align vertical);
 
 
-/* Set dialog icon
+/** Set dialog icon
  * If x or y is negative, dialog icon will not be rendered
  * If x or y is negative, dialog icon will not be rendered
  * @param dialog - DialogEx instance
  * @param dialog - DialogEx instance
  * @param x, y - icon position
  * @param x, y - icon position
@@ -86,27 +87,32 @@ void dialog_ex_set_text(
  */
  */
 void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* icon);
 void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* icon);
 
 
-/* Set left button text
+/** Set left button text
  * If text is null, left button will not be rendered and processed
  * If text is null, left button will not be rendered and processed
  * @param dialog - DialogEx instance
  * @param dialog - DialogEx instance
  * @param text - text to be shown
  * @param text - text to be shown
  */
  */
 void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text);
 void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text);
 
 
-/* Set center button text
+/** Set center button text
  * If text is null, center button will not be rendered and processed
  * If text is null, center button will not be rendered and processed
  * @param dialog - DialogEx instance
  * @param dialog - DialogEx instance
  * @param text - text to be shown
  * @param text - text to be shown
  */
  */
 void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text);
 void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text);
 
 
-/* Set right button text
+/** Set right button text
  * If text is null, right button will not be rendered and processed
  * If text is null, right button will not be rendered and processed
  * @param dialog - DialogEx instance
  * @param dialog - DialogEx instance
  * @param text - text to be shown
  * @param text - text to be shown
  */
  */
 void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text);
 void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text);
 
 
+/** Clean dialog
+ * @param dialog_ex DialogEx instance
+ */
+void dialog_ex_clean(DialogEx* dialog_ex);
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

+ 1 - 1
applications/loader/loader.c

@@ -238,7 +238,7 @@ static void loader_build_menu() {
     with_value_mutex(
     with_value_mutex(
         loader_instance->menu_vm, (Menu * menu) {
         loader_instance->menu_vm, (Menu * menu) {
             MenuItem* menu_debug =
             MenuItem* menu_debug =
-                menu_item_alloc_menu("Debug tools", icon_animation_alloc(&A_Settings_14));
+                menu_item_alloc_menu("Debug tools", icon_animation_alloc(&A_Debug_14));
 
 
             for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) {
             for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) {
                 // Add menu item
                 // Add menu item

Plik diff jest za duży
+ 0 - 0
assets/compiled/assets_icons.c


+ 5 - 4
assets/compiled/assets_icons.h

@@ -29,10 +29,6 @@ extern const Icon I_DolphinFirstStart8_56x51;
 extern const Icon I_DolphinFirstStart7_61x51;
 extern const Icon I_DolphinFirstStart7_61x51;
 extern const Icon I_Flipper_young_80x60;
 extern const Icon I_Flipper_young_80x60;
 extern const Icon I_DolphinFirstStart3_57x48;
 extern const Icon I_DolphinFirstStart3_57x48;
-extern const Icon I_Scanning_123x52;
-extern const Icon I_Quest_7x8;
-extern const Icon I_Unlock_7x8;
-extern const Icon I_Lock_7x8;
 extern const Icon I_PassportBottom_128x17;
 extern const Icon I_PassportBottom_128x17;
 extern const Icon I_DoorLeft_8x56;
 extern const Icon I_DoorLeft_8x56;
 extern const Icon I_DoorLocked_10x56;
 extern const Icon I_DoorLocked_10x56;
@@ -67,6 +63,7 @@ extern const Icon I_KeySaveSelected_24x11;
 extern const Icon I_KeyBackspace_16x9;
 extern const Icon I_KeyBackspace_16x9;
 extern const Icon A_125khz_14;
 extern const Icon A_125khz_14;
 extern const Icon A_Bluetooth_14;
 extern const Icon A_Bluetooth_14;
+extern const Icon A_Debug_14;
 extern const Icon A_FileManager_14;
 extern const Icon A_FileManager_14;
 extern const Icon A_GPIO_14;
 extern const Icon A_GPIO_14;
 extern const Icon A_Games_14;
 extern const Icon A_Games_14;
@@ -109,6 +106,10 @@ extern const Icon I_SDcardFail_11x8;
 extern const Icon I_USBConnected_15x8;
 extern const Icon I_USBConnected_15x8;
 extern const Icon I_Bluetooth_5x8;
 extern const Icon I_Bluetooth_5x8;
 extern const Icon I_Background_128x11;
 extern const Icon I_Background_128x11;
+extern const Icon I_Scanning_123x52;
+extern const Icon I_Quest_7x8;
+extern const Icon I_Unlock_7x8;
+extern const Icon I_Lock_7x8;
 extern const Icon I_DolphinMafia_115x62;
 extern const Icon I_DolphinMafia_115x62;
 extern const Icon I_DolphinExcited_64x63;
 extern const Icon I_DolphinExcited_64x63;
 extern const Icon I_iButtonDolphinSuccess_109x60;
 extern const Icon I_iButtonDolphinSuccess_109x60;

BIN
assets/icons/MainMenu/Debug_14/frame_01.png


BIN
assets/icons/MainMenu/Debug_14/frame_02.png


BIN
assets/icons/MainMenu/Debug_14/frame_03.png


BIN
assets/icons/MainMenu/Debug_14/frame_04.png


+ 1 - 0
assets/icons/MainMenu/Debug_14/frame_rate

@@ -0,0 +1 @@
+3

+ 0 - 0
assets/icons/GubGHz/Scanning_123x52.png → assets/icons/SubGhz/Scanning_123x52.png


+ 0 - 0
assets/icons/GubGHz/lock_7x8.png → assets/icons/SubGhz/lock_7x8.png


+ 0 - 0
assets/icons/GubGHz/quest_7x8.png → assets/icons/SubGhz/quest_7x8.png


+ 0 - 0
assets/icons/GubGHz/unlock_7x8.png → assets/icons/SubGhz/unlock_7x8.png


+ 3 - 3
firmware/targets/f6/furi-hal/furi-hal-bt.c

@@ -43,7 +43,7 @@ bool furi_hal_bt_is_alive() {
     return APPE_Status() == BleGlueStatusStarted;
     return APPE_Status() == BleGlueStatusStarted;
 }
 }
 
 
-bool furi_hal_bt_wait_transition() {
+bool furi_hal_bt_wait_startup() {
     uint8_t counter = 0;
     uint8_t counter = 0;
     while (APPE_Status() == BleGlueStatusStartup) {
     while (APPE_Status() == BleGlueStatusStartup) {
         osDelay(10);
         osDelay(10);
@@ -56,7 +56,7 @@ bool furi_hal_bt_wait_transition() {
 }
 }
 
 
 bool furi_hal_bt_lock_flash() {
 bool furi_hal_bt_lock_flash() {
-    if (!furi_hal_bt_wait_transition()) {
+    if (!furi_hal_bt_wait_startup()) {
         return false;
         return false;
     }
     }
     if (APPE_Status() == BleGlueStatusUninitialized) {
     if (APPE_Status() == BleGlueStatusUninitialized) {
@@ -100,7 +100,7 @@ void furi_hal_bt_start_packet_rx(uint8_t channel, uint8_t datarate) {
 }
 }
 
 
 uint16_t furi_hal_bt_stop_packet_test() {
 uint16_t furi_hal_bt_stop_packet_test() {
-    uint16_t num_of_packets;
+    uint16_t num_of_packets = 0;
     hci_le_test_end(&num_of_packets);
     hci_le_test_end(&num_of_packets);
     return num_of_packets;
     return num_of_packets;
 }
 }

+ 1 - 0
firmware/targets/f6/furi-hal/furi-hal-gpio.c

@@ -151,6 +151,7 @@ void hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, void* c
 
 
     __disable_irq();
     __disable_irq();
     uint8_t pin_num = hal_gpio_get_pin_num(gpio);
     uint8_t pin_num = hal_gpio_get_pin_num(gpio);
+    furi_assert(gpio_interrupt[pin_num].callback == NULL);
     gpio_interrupt[pin_num].callback = cb;
     gpio_interrupt[pin_num].callback = cb;
     gpio_interrupt[pin_num].context = ctx;
     gpio_interrupt[pin_num].context = ctx;
     gpio_interrupt[pin_num].ready = true;
     gpio_interrupt[pin_num].ready = true;

+ 3 - 0
firmware/targets/furi-hal-include/furi-hal-bt.h

@@ -19,6 +19,9 @@ void furi_hal_bt_dump_state(string_t buffer);
 /** Get BT/BLE system component state */
 /** Get BT/BLE system component state */
 bool furi_hal_bt_is_alive();
 bool furi_hal_bt_is_alive();
 
 
+/** Wait for Core2 startup */
+bool furi_hal_bt_wait_startup();
+
 /**
 /**
  * Lock shared access to flash controller
  * Lock shared access to flash controller
  * @return true if lock was successful, false if not
  * @return true if lock was successful, false if not

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików