Przeglądaj źródła

Squashed merge from dev

0xchocolate 3 lat temu
rodzic
commit
a2f4413e56
80 zmienionych plików z 1220 dodań i 289 usunięć
  1. 3 1
      applications/about/about.c
  2. 2 2
      applications/accessor/accessor_app.cpp
  3. 2 1
      applications/accessor/accessor_view_manager.cpp
  4. 2 0
      applications/applications.h
  5. 6 6
      applications/archive/helpers/archive_favorites.c
  6. 1 1
      applications/cli/cli_commands.c
  7. 2 1
      applications/desktop/views/desktop_view_debug.c
  8. 2 0
      applications/ibutton/ibutton.c
  9. 5 5
      applications/ibutton/scenes/ibutton_scene_rpc.c
  10. 22 1
      applications/infrared/infrared.c
  11. 2 0
      applications/infrared/infrared_i.h
  12. 8 34
      applications/infrared/scenes/infrared_scene_learn_success.c
  13. 21 16
      applications/infrared/scenes/infrared_scene_remote.c
  14. 36 13
      applications/infrared/scenes/infrared_scene_rpc.c
  15. 6 0
      applications/lfrfid/lfrfid_app.cpp
  16. 0 1
      applications/lfrfid/lfrfid_app.h
  17. 12 20
      applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp
  18. 1 1
      applications/lfrfid/scene/lfrfid_app_scene_write.cpp
  19. 1 0
      applications/lfrfid_debug/lfrfid_debug_app.cpp
  20. 1 1
      applications/loader/application.fam
  21. 3 3
      applications/loader/loader.c
  22. 2 0
      applications/nfc/nfc.c
  23. 1 1
      applications/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c
  24. 1 1
      applications/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c
  25. 6 8
      applications/nfc/scenes/nfc_scene_rpc.c
  26. 21 17
      applications/rpc/rpc_storage.c
  27. 1 1
      applications/storage_settings/scenes/storage_settings_scene_benchmark.c
  28. 1 1
      applications/storage_settings/scenes/storage_settings_scene_format_confirm.c
  29. 1 1
      applications/storage_settings/scenes/storage_settings_scene_formatting.c
  30. 1 1
      applications/storage_settings/scenes/storage_settings_scene_sd_info.c
  31. 1 1
      applications/storage_settings/scenes/storage_settings_scene_unmount_confirm.c
  32. 1 1
      applications/storage_settings/scenes/storage_settings_scene_unmounted.c
  33. 1 1
      applications/subghz/scenes/subghz_scene_need_saving.c
  34. 1 1
      applications/subghz/scenes/subghz_scene_receiver_info.c
  35. 27 18
      applications/subghz/scenes/subghz_scene_rpc.c
  36. 1 1
      applications/subghz/scenes/subghz_scene_show_error_sub.c
  37. 12 8
      applications/subghz/scenes/subghz_scene_show_only_rx.c
  38. 8 0
      applications/subghz/subghz.c
  39. 94 1
      applications/subghz/subghz_cli.c
  40. 10 10
      applications/subghz/subghz_i.c
  41. 17 1
      applications/unit_tests/subghz/subghz_test.c
  42. BIN
      assets/icons/Dolphin/DolphinCommon_56x48.png
  43. BIN
      assets/icons/Dolphin/DolphinFirstStart7_61x51.png
  44. BIN
      assets/icons/Dolphin/DolphinFirstStart8_56x51.png
  45. 1 1
      assets/protobuf
  46. 2 2
      assets/resources/Manifest
  47. 1 1
      assets/resources/badusb/demo_windows.txt
  48. 7 0
      assets/unit_tests/subghz/honeywell_wdb.sub
  49. 5 0
      assets/unit_tests/subghz/honeywell_wdb_raw.sub
  50. 0 0
      assets/unit_tests/subghz/test_random_raw.sub
  51. 5 0
      fbt_options.py
  52. 10 2
      firmware.scons
  53. 1 0
      firmware/targets/f7/furi_hal/furi_hal.c
  54. 4 1
      firmware/targets/f7/furi_hal/furi_hal_clock.c
  55. 7 1
      firmware/targets/f7/furi_hal/furi_hal_info.c
  56. 135 0
      firmware/targets/f7/furi_hal/furi_hal_region.c
  57. 2 45
      firmware/targets/f7/furi_hal/furi_hal_subghz.c
  58. 2 0
      firmware/targets/f7/furi_hal/furi_hal_version.c
  59. 1 0
      firmware/targets/furi_hal_include/furi_hal.h
  60. 73 0
      firmware/targets/furi_hal_include/furi_hal_region.h
  61. 0 8
      firmware/targets/furi_hal_include/furi_hal_subghz.h
  62. 1 0
      firmware/targets/furi_hal_include/furi_hal_version.h
  63. 4 1
      lib/app-scened-template/view_controller.hpp
  64. 8 0
      lib/subghz/blocks/math.c
  65. 8 0
      lib/subghz/blocks/math.h
  66. 4 4
      lib/subghz/protocols/bett.c
  67. 1 1
      lib/subghz/protocols/came_twee.c
  68. 399 0
      lib/subghz/protocols/honeywell_wdb.c
  69. 111 0
      lib/subghz/protocols/honeywell_wdb.h
  70. 11 10
      lib/subghz/protocols/registry.c
  71. 1 0
      lib/subghz/protocols/registry.h
  72. 1 1
      lib/subghz/subghz_tx_rx_worker.c
  73. 52 0
      scripts/fwsize.py
  74. 1 0
      scripts/otp.py
  75. 1 0
      scripts/toolchain/fbtenv.cmd
  76. 6 0
      site_scons/commandline.scons
  77. 7 2
      site_scons/fbt/appmanifest.py
  78. 1 3
      site_scons/site_tools/crosscc.py
  79. 1 1
      site_scons/site_tools/fbt_apps.py
  80. 0 24
      site_scons/site_tools/size.py

+ 3 - 1
applications/about/about.c

@@ -5,6 +5,7 @@
 #include <gui/modules/empty_screen.h>
 #include <m-string.h>
 #include <furi_hal_version.h>
+#include <furi_hal_region.h>
 #include <furi_hal_bt.h>
 
 typedef DialogMessageButton (*AboutDialogScreen)(DialogsApp* dialogs, DialogMessage* message);
@@ -83,12 +84,13 @@ static DialogMessageButton hw_version_screen(DialogsApp* dialogs, DialogMessage*
 
     string_cat_printf(
         buffer,
-        "%d.F%dB%dC%d %s %s\n",
+        "%d.F%dB%dC%d %s:%s %s\n",
         furi_hal_version_get_hw_version(),
         furi_hal_version_get_hw_target(),
         furi_hal_version_get_hw_body(),
         furi_hal_version_get_hw_connect(),
         furi_hal_version_get_hw_region_name(),
+        furi_hal_region_get_name(),
         my_name ? my_name : "Unknown");
 
     string_cat_printf(buffer, "Serial Number:\n");

+ 2 - 2
applications/accessor/accessor_app.cpp

@@ -32,14 +32,14 @@ void AccessorApp::run(void) {
 }
 
 AccessorApp::AccessorApp() {
-    notification = static_cast<NotificationApp*>(furi_record_open("notification"));
+    notification = static_cast<NotificationApp*>(furi_record_open(RECORD_NOTIFICATION));
     onewire_host = onewire_host_alloc();
     furi_hal_power_enable_otg();
 }
 
 AccessorApp::~AccessorApp() {
     furi_hal_power_disable_otg();
-    furi_record_close("notification");
+    furi_record_close(RECORD_NOTIFICATION);
     onewire_host_free(onewire_host);
 }
 

+ 2 - 1
applications/accessor/accessor_view_manager.cpp

@@ -15,7 +15,7 @@ AccessorAppViewManager::AccessorAppViewManager() {
     popup = popup_alloc();
     add_view(ViewType::Popup, popup_get_view(popup));
 
-    gui = static_cast<Gui*>(furi_record_open("gui"));
+    gui = static_cast<Gui*>(furi_record_open(RECORD_GUI));
     view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
 
     // set previous view callback for all views
@@ -31,6 +31,7 @@ AccessorAppViewManager::~AccessorAppViewManager() {
         view_dispatcher, static_cast<uint32_t>(AccessorAppViewManager::ViewType::Popup));
 
     // free view modules
+    furi_record_close(RECORD_GUI);
     submenu_free(submenu);
     popup_free(popup);
 

+ 2 - 0
applications/applications.h

@@ -18,6 +18,8 @@ typedef struct {
 
 typedef void (*FlipperOnStartHook)(void);
 
+extern const char* FLIPPER_AUTORUN_APP_NAME;
+
 /* Services list
  * Spawned on startup
  */

+ 6 - 6
applications/archive/helpers/archive_favorites.c

@@ -64,7 +64,7 @@ uint16_t archive_favorites_count(void* context) {
                 break;
             }
             if(!string_size(buffer)) {
-                break;
+                continue; // Skip empty lines
             }
             ++lines;
         }
@@ -93,7 +93,7 @@ static bool archive_favourites_rescan() {
                 break;
             }
             if(!string_size(buffer)) {
-                break;
+                continue;
             }
 
             if(string_search(buffer, "/app:") == 0) {
@@ -152,7 +152,7 @@ bool archive_favorites_read(void* context) {
                 break;
             }
             if(!string_size(buffer)) {
-                break;
+                continue;
             }
 
             if(string_search(buffer, "/app:") == 0) {
@@ -215,7 +215,7 @@ bool archive_favorites_delete(const char* format, ...) {
                 break;
             }
             if(!string_size(buffer)) {
-                break;
+                continue;
             }
 
             if(string_search(buffer, filename)) {
@@ -259,7 +259,7 @@ bool archive_is_favorite(const char* format, ...) {
                 break;
             }
             if(!string_size(buffer)) {
-                break;
+                continue;
             }
             if(!string_search(buffer, filename)) {
                 found = true;
@@ -299,7 +299,7 @@ bool archive_favorites_rename(const char* src, const char* dst) {
                 break;
             }
             if(!string_size(buffer)) {
-                break;
+                continue;
             }
 
             archive_file_append(

+ 1 - 1
applications/cli/cli_commands.c

@@ -15,7 +15,7 @@
 void cli_command_device_info_callback(const char* key, const char* value, bool last, void* context) {
     UNUSED(context);
     UNUSED(last);
-    printf("%-24s: %s\r\n", key, value);
+    printf("%-30s: %s\r\n", key, value);
 }
 
 /* 

+ 2 - 1
applications/desktop/views/desktop_view_debug.c

@@ -36,12 +36,13 @@ void desktop_debug_render(Canvas* canvas, void* model) {
         snprintf(
             buffer,
             sizeof(buffer),
-            "%d.F%dB%dC%d %s %s",
+            "%d.F%dB%dC%d %s:%s %s",
             furi_hal_version_get_hw_version(),
             furi_hal_version_get_hw_target(),
             furi_hal_version_get_hw_body(),
             furi_hal_version_get_hw_connect(),
             furi_hal_version_get_hw_region_name(),
+            furi_hal_region_get_name(),
             my_name ? my_name : "Unknown");
         canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer);
 

+ 2 - 0
applications/ibutton/ibutton.c

@@ -87,6 +87,8 @@ static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context)
     if(event == RpcAppEventSessionClose) {
         view_dispatcher_send_custom_event(
             ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose);
+        rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
+        ibutton->rpc_ctx = NULL;
     } else if(event == RpcAppEventAppExit) {
         view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
     } else if(event == RpcAppEventLoadFile) {

+ 5 - 5
applications/ibutton/scenes/ibutton_scene_rpc.c

@@ -29,7 +29,7 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
         if(event.event == iButtonCustomEventRpcLoad) {
             const char* arg = rpc_system_app_get_data(ibutton->rpc_ctx);
             bool result = false;
-            if(arg) {
+            if(arg && (string_empty_p(ibutton->file_path))) {
                 string_set_str(ibutton->file_path, arg);
                 if(ibutton_load_key_data(ibutton, ibutton->file_path, false)) {
                     ibutton_worker_emulate_start(ibutton->key_worker, ibutton->key);
@@ -51,17 +51,17 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
 
                     string_clear(key_name);
                     result = true;
+                } else {
+                    string_reset(ibutton->file_path);
                 }
             }
             rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventLoadFile, result);
         } else if(event.event == iButtonCustomEventRpcExit) {
             rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventAppExit, true);
-            ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
+            scene_manager_stop(ibutton->scene_manager);
             view_dispatcher_stop(ibutton->view_dispatcher);
         } else if(event.event == iButtonCustomEventRpcSessionClose) {
-            rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
-            ibutton->rpc_ctx = NULL;
-            ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
+            scene_manager_stop(ibutton->scene_manager);
             view_dispatcher_stop(ibutton->view_dispatcher);
         }
     }

+ 22 - 1
applications/infrared/infrared.c

@@ -46,6 +46,8 @@ static void infrared_rpc_command_callback(RpcAppSystemEvent event, void* context
     if(event == RpcAppEventSessionClose) {
         view_dispatcher_send_custom_event(
             infrared->view_dispatcher, InfraredCustomEventTypeRpcSessionClose);
+        rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL);
+        infrared->rpc_ctx = NULL;
     } else if(event == RpcAppEventAppExit) {
         view_dispatcher_send_custom_event(
             infrared->view_dispatcher, InfraredCustomEventTypeRpcExit);
@@ -293,6 +295,13 @@ bool infrared_rename_current_remote(Infrared* infrared, const char* name) {
 }
 
 void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
+    if(infrared->app_state.is_transmitting) {
+        FURI_LOG_D(INFRARED_LOG_TAG, "Transmitter is already active");
+        return;
+    } else {
+        infrared->app_state.is_transmitting = true;
+    }
+
     if(infrared_signal_is_raw(signal)) {
         InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
         infrared_worker_set_raw_signal(infrared->worker, raw->timings, raw->timings_size);
@@ -302,8 +311,11 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
     }
 
     DOLPHIN_DEED(DolphinDeedIrSend);
-    infrared_worker_tx_start(infrared->worker);
     infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
+
+    infrared_worker_tx_set_get_signal_callback(
+        infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared);
+    infrared_worker_tx_start(infrared->worker);
 }
 
 void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
@@ -322,7 +334,16 @@ void infrared_tx_start_received(Infrared* infrared) {
 }
 
 void infrared_tx_stop(Infrared* infrared) {
+    if(!infrared->app_state.is_transmitting) {
+        FURI_LOG_D(INFRARED_LOG_TAG, "Transmitter is already stopped");
+        return;
+    } else {
+        infrared->app_state.is_transmitting = false;
+    }
+
     infrared_worker_tx_stop(infrared->worker);
+    infrared_worker_tx_set_get_signal_callback(infrared->worker, NULL, NULL);
+
     infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
 }
 

+ 2 - 0
applications/infrared/infrared_i.h

@@ -43,6 +43,7 @@
 #define INFRARED_APP_EXTENSION ".ir"
 
 #define INFRARED_DEFAULT_REMOTE_NAME "Remote"
+#define INFRARED_LOG_TAG "InfraredApp"
 
 typedef enum {
     InfraredButtonIndexNone = -1,
@@ -63,6 +64,7 @@ typedef enum {
 typedef struct {
     bool is_learning_new_remote;
     bool is_debug_enabled;
+    bool is_transmitting;
     InfraredEditTarget edit_target : 8;
     InfraredEditMode edit_mode : 8;
     int32_t current_button_index;

+ 8 - 34
applications/infrared/scenes/infrared_scene_learn_success.c

@@ -2,11 +2,6 @@
 
 #include <dolphin/dolphin.h>
 
-typedef enum {
-    InfraredSceneLearnSuccessStateIdle = 0,
-    InfraredSceneLearnSuccessStateSending = 1,
-} InfraredSceneLearnSuccessState;
-
 static void
     infrared_scene_learn_success_dialog_result_callback(DialogExResult result, void* context) {
     Infrared* infrared = context;
@@ -21,9 +16,6 @@ void infrared_scene_learn_success_on_enter(void* context) {
     DOLPHIN_DEED(DolphinDeedIrLearnSuccess);
     infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn);
 
-    infrared_worker_tx_set_get_signal_callback(
-        infrared->worker, infrared_worker_tx_get_signal_steady_callback, context);
-
     if(infrared_signal_is_raw(signal)) {
         InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
         dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter);
@@ -63,57 +55,42 @@ void infrared_scene_learn_success_on_enter(void* context) {
     dialog_ex_set_context(dialog_ex, context);
     dialog_ex_enable_extended_events(dialog_ex);
 
-    scene_manager_set_scene_state(
-        infrared->scene_manager, InfraredSceneLearnSuccess, InfraredSceneLearnSuccessStateIdle);
     view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
 }
 
 bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent event) {
     Infrared* infrared = context;
     SceneManager* scene_manager = infrared->scene_manager;
-    uint32_t scene_state = scene_manager_get_scene_state(scene_manager, InfraredSceneLearnSuccess);
+    const bool is_transmitter_idle = !infrared->app_state.is_transmitting;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeTick) {
-        if(scene_state == InfraredSceneLearnSuccessStateIdle) {
+        if(is_transmitter_idle) {
             infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn);
         }
         consumed = true;
     } else if(event.type == SceneManagerEventTypeBack) {
-        if(scene_state == InfraredSceneLearnSuccessStateIdle) {
+        if(is_transmitter_idle) {
             scene_manager_next_scene(scene_manager, InfraredSceneAskBack);
         }
         consumed = true;
     } else if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == DialogExResultLeft) {
-            if(scene_state == InfraredSceneLearnSuccessStateIdle) {
+            if(is_transmitter_idle) {
                 scene_manager_next_scene(scene_manager, InfraredSceneAskRetry);
             }
             consumed = true;
         } else if(event.event == DialogExResultRight) {
-            if(scene_state == InfraredSceneLearnSuccessStateIdle) {
+            if(is_transmitter_idle) {
                 scene_manager_next_scene(scene_manager, InfraredSceneLearnEnterName);
             }
             consumed = true;
         } else if(event.event == DialogExPressCenter) {
-            if(scene_state == InfraredSceneLearnSuccessStateIdle) {
-                scene_manager_set_scene_state(
-                    scene_manager,
-                    InfraredSceneLearnSuccess,
-                    InfraredSceneLearnSuccessStateSending);
-                infrared_tx_start_received(infrared);
-                infrared_play_notification_message(
-                    infrared, InfraredNotificationMessageBlinkStartSend);
-            }
+            infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff);
+            infrared_tx_start_received(infrared);
             consumed = true;
         } else if(event.event == DialogExReleaseCenter) {
-            if(scene_state == InfraredSceneLearnSuccessStateSending) {
-                scene_manager_set_scene_state(
-                    scene_manager, InfraredSceneLearnSuccess, InfraredSceneLearnSuccessStateIdle);
-                infrared_tx_stop(infrared);
-                infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
-                infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff);
-            }
+            infrared_tx_stop(infrared);
             consumed = true;
         }
     }
@@ -123,9 +100,6 @@ bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent even
 
 void infrared_scene_learn_success_on_exit(void* context) {
     Infrared* infrared = context;
-    InfraredWorker* worker = infrared->worker;
     dialog_ex_reset(infrared->dialog_ex);
-    infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
     infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff);
-    infrared_worker_tx_set_get_signal_callback(worker, NULL, NULL);
 }

+ 21 - 16
applications/infrared/scenes/infrared_scene_remote.c

@@ -31,9 +31,6 @@ void infrared_scene_remote_on_enter(void* context) {
     ButtonMenu* button_menu = infrared->button_menu;
     SceneManager* scene_manager = infrared->scene_manager;
 
-    infrared_worker_tx_set_get_signal_callback(
-        infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared);
-
     size_t button_count = infrared_remote_get_button_count(remote);
     for(size_t i = 0; i < button_count; ++i) {
         InfraredRemoteButton* button = infrared_remote_get_button(remote, i);
@@ -73,12 +70,17 @@ void infrared_scene_remote_on_enter(void* context) {
 bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) {
     Infrared* infrared = context;
     SceneManager* scene_manager = infrared->scene_manager;
+    const bool is_transmitter_idle = !infrared->app_state.is_transmitting;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeBack) {
-        const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
-        consumed = scene_manager_search_and_switch_to_previous_scene_one_of(
-            scene_manager, possible_scenes, COUNT_OF(possible_scenes));
+        if(is_transmitter_idle) {
+            const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
+            consumed = scene_manager_search_and_switch_to_previous_scene_one_of(
+                scene_manager, possible_scenes, COUNT_OF(possible_scenes));
+        } else {
+            consumed = true;
+        }
     } else if(event.type == SceneManagerEventTypeCustom) {
         const uint16_t custom_type = infrared_custom_event_get_type(event.event);
         const int16_t button_index = infrared_custom_event_get_value(event.event);
@@ -92,14 +94,19 @@ bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) {
             consumed = true;
         } else if(custom_type == InfraredCustomEventTypeMenuSelected) {
             furi_assert(button_index < 0);
-            scene_manager_set_scene_state(
-                scene_manager, InfraredSceneRemote, (unsigned)button_index);
-            if(button_index == ButtonIndexPlus) {
-                infrared->app_state.is_learning_new_remote = false;
-                scene_manager_next_scene(scene_manager, InfraredSceneLearn);
-                consumed = true;
-            } else if(button_index == ButtonIndexEdit) {
-                scene_manager_next_scene(scene_manager, InfraredSceneEdit);
+            if(is_transmitter_idle) {
+                scene_manager_set_scene_state(
+                    scene_manager, InfraredSceneRemote, (unsigned)button_index);
+                if(button_index == ButtonIndexPlus) {
+                    infrared->app_state.is_learning_new_remote = false;
+                    scene_manager_next_scene(scene_manager, InfraredSceneLearn);
+                    consumed = true;
+                } else if(button_index == ButtonIndexEdit) {
+                    scene_manager_next_scene(scene_manager, InfraredSceneEdit);
+                    consumed = true;
+                }
+
+            } else {
                 consumed = true;
             }
         }
@@ -110,7 +117,5 @@ bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) {
 
 void infrared_scene_remote_on_exit(void* context) {
     Infrared* infrared = context;
-    infrared_tx_stop(infrared);
-    infrared_worker_tx_set_get_signal_callback(infrared->worker, NULL, NULL);
     button_menu_reset(infrared->button_menu);
 }

+ 36 - 13
applications/infrared/scenes/infrared_scene_rpc.c

@@ -1,20 +1,28 @@
 #include "../infrared_i.h"
 #include "gui/canvas.h"
 
+typedef enum {
+    InfraredRpcStateIdle,
+    InfraredRpcStateLoaded,
+    InfraredRpcStateSending,
+} InfraredRpcState;
+
 void infrared_scene_rpc_on_enter(void* context) {
     Infrared* infrared = context;
     Popup* popup = infrared->popup;
 
-    popup_set_header(popup, "Infrared", 82, 28, AlignCenter, AlignBottom);
-    popup_set_text(popup, "RPC mode", 82, 32, AlignCenter, AlignTop);
+    popup_set_header(popup, "Infrared", 89, 42, AlignCenter, AlignBottom);
+    popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop);
 
-    popup_set_icon(popup, 2, 14, &I_Warning_30x23); // TODO: icon
+    popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61);
 
     popup_set_context(popup, context);
     popup_set_callback(popup, infrared_popup_closed_callback);
 
     view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
 
+    scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle);
+
     notification_message(infrared->notifications, &sequence_display_backlight_on);
 }
 
@@ -24,6 +32,8 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         consumed = true;
+        InfraredRpcState state =
+            scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneRpc);
         if(event.event == InfraredCustomEventTypeBackPressed) {
             view_dispatcher_stop(infrared->view_dispatcher);
         } else if(event.event == InfraredCustomEventTypePopupClosed) {
@@ -31,39 +41,49 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
         } else if(event.event == InfraredCustomEventTypeRpcLoad) {
             bool result = false;
             const char* arg = rpc_system_app_get_data(infrared->rpc_ctx);
-            if(arg) {
+            if(arg && (state == InfraredRpcStateIdle)) {
                 string_set_str(infrared->file_path, arg);
                 result = infrared_remote_load(infrared->remote, infrared->file_path);
-                infrared_worker_tx_set_get_signal_callback(
-                    infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared);
+                if(result) {
+                    scene_manager_set_scene_state(
+                        infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
+                }
             }
             const char* remote_name = infrared_remote_get_name(infrared->remote);
 
             infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name);
             popup_set_text(
-                infrared->popup, infrared->text_store[0], 82, 32, AlignCenter, AlignTop);
+                infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop);
 
             rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventLoadFile, result);
         } else if(event.event == InfraredCustomEventTypeRpcButtonPress) {
             bool result = false;
             const char* arg = rpc_system_app_get_data(infrared->rpc_ctx);
-            if(arg) {
+            if(arg && (state == InfraredRpcStateLoaded)) {
                 size_t button_index = 0;
                 if(infrared_remote_find_button_by_name(infrared->remote, arg, &button_index)) {
                     infrared_tx_start_button_index(infrared, button_index);
                     result = true;
+                    scene_manager_set_scene_state(
+                        infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending);
                 }
             }
             rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventButtonRelease, result);
         } else if(event.event == InfraredCustomEventTypeRpcButtonRelease) {
-            infrared_tx_stop(infrared);
-            rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventButtonRelease, true);
+            bool result = false;
+            if(state == InfraredRpcStateSending) {
+                infrared_tx_stop(infrared);
+                result = true;
+                scene_manager_set_scene_state(
+                    infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
+            }
+            rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventButtonRelease, result);
         } else if(event.event == InfraredCustomEventTypeRpcExit) {
+            scene_manager_stop(infrared->scene_manager);
             view_dispatcher_stop(infrared->view_dispatcher);
             rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventAppExit, true);
         } else if(event.event == InfraredCustomEventTypeRpcSessionClose) {
-            rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL);
-            infrared->rpc_ctx = NULL;
+            scene_manager_stop(infrared->scene_manager);
             view_dispatcher_stop(infrared->view_dispatcher);
         }
     }
@@ -72,6 +92,9 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
 
 void infrared_scene_rpc_on_exit(void* context) {
     Infrared* infrared = context;
-    infrared_tx_stop(infrared);
+    if(scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneRpc) ==
+       InfraredRpcStateSending) {
+        infrared_tx_stop(infrared);
+    }
     popup_reset(infrared->popup);
 }

+ 6 - 0
applications/lfrfid/lfrfid_app.cpp

@@ -56,6 +56,9 @@ static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) {
         LfRfidApp::Event event;
         event.type = LfRfidApp::EventType::RpcSessionClose;
         app->view_controller.send_event(&event);
+        // Detach RPC
+        rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL);
+        app->rpc_ctx = NULL;
     } else if(rpc_event == RpcAppEventAppExit) {
         LfRfidApp::Event event;
         event.type = LfRfidApp::EventType::Exit;
@@ -80,16 +83,19 @@ void LfRfidApp::run(void* _args) {
             rpc_ctx = (RpcAppSystem*)rpc_ctx_ptr;
             rpc_system_app_set_callback(rpc_ctx, rpc_command_callback, this);
             rpc_system_app_send_started(rpc_ctx);
+            view_controller.attach_to_gui(ViewDispatcherTypeDesktop);
             scene_controller.add_scene(SceneType::Rpc, new LfRfidAppSceneRpc());
             scene_controller.process(100, SceneType::Rpc);
         } else {
             string_set_str(file_path, args);
             load_key_data(file_path, &worker.key, true);
+            view_controller.attach_to_gui(ViewDispatcherTypeFullscreen);
             scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate());
             scene_controller.process(100, SceneType::Emulate);
         }
 
     } else {
+        view_controller.attach_to_gui(ViewDispatcherTypeFullscreen);
         scene_controller.add_scene(SceneType::Start, new LfRfidAppSceneStart());
         scene_controller.add_scene(SceneType::Read, new LfRfidAppSceneRead());
         scene_controller.add_scene(SceneType::RetryConfirm, new LfRfidAppSceneRetryConfirm());

+ 0 - 1
applications/lfrfid/lfrfid_app.h

@@ -101,5 +101,4 @@ public:
     bool save_key_data(string_t path, RfidKey* key);
 
     void make_app_folder();
-    //bool rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context);
 };

+ 12 - 20
applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp

@@ -6,9 +6,9 @@
 void LfRfidAppSceneRpc::on_enter(LfRfidApp* app, bool /* need_restore */) {
     auto popup = app->view_controller.get<PopupVM>();
 
-    popup->set_header("LF RFID", 89, 30, AlignCenter, AlignTop);
-    popup->set_text("RPC mode", 89, 43, AlignCenter, AlignTop);
-    popup->set_icon(0, 3, &I_RFIDDolphinSend_97x61);
+    popup->set_header("LF RFID", 89, 42, AlignCenter, AlignBottom);
+    popup->set_text("RPC mode", 89, 44, AlignCenter, AlignTop);
+    popup->set_icon(0, 12, &I_RFIDDolphinSend_97x61);
 
     app->view_controller.switch_to<PopupVM>();
 
@@ -27,33 +27,25 @@ bool LfRfidAppSceneRpc::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
         app->view_controller.send_event(&view_event);
         rpc_system_app_confirm(app->rpc_ctx, RpcAppEventAppExit, true);
     } else if(event->type == LfRfidApp::EventType::RpcSessionClose) {
-        // Detach RPC
-        rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL);
-        app->rpc_ctx = NULL;
-
         consumed = true;
         LfRfidApp::Event view_event;
         view_event.type = LfRfidApp::EventType::Back;
         app->view_controller.send_event(&view_event);
-    } else if(event->type == LfRfidApp::EventType::EmulateStart) {
-        auto popup = app->view_controller.get<PopupVM>();
-        consumed = true;
-        emulating = true;
-
-        app->text_store.set("emulating\n%s", app->worker.key.get_name());
-        popup->set_text(app->text_store.text, 89, 43, AlignCenter, AlignTop);
-
-        notification_message(app->notification, &sequence_blink_start_magenta);
     } else if(event->type == LfRfidApp::EventType::RpcLoadFile) {
         const char* arg = rpc_system_app_get_data(app->rpc_ctx);
+        consumed = true;
         bool result = false;
-        if(arg) {
+        if(arg && !emulating) {
             string_set_str(app->file_path, arg);
             if(app->load_key_data(app->file_path, &(app->worker.key), false)) {
-                LfRfidApp::Event event;
-                event.type = LfRfidApp::EventType::EmulateStart;
-                app->view_controller.send_event(&event);
                 app->worker.start_emulate();
+                emulating = true;
+
+                auto popup = app->view_controller.get<PopupVM>();
+                app->text_store.set("emulating\n%s", app->worker.key.get_name());
+                popup->set_text(app->text_store.text, 89, 44, AlignCenter, AlignTop);
+
+                notification_message(app->notification, &sequence_blink_start_magenta);
                 result = true;
             }
         }

+ 1 - 1
applications/lfrfid/scene/lfrfid_app_scene_write.cpp

@@ -41,7 +41,7 @@ bool LfRfidAppSceneWrite::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
         case RfidWorker::WriteResult::NotWritable:
             if(!card_not_supported) {
                 auto popup = app->view_controller.get<PopupVM>();
-                popup->set_icon(72, 14, &I_DolphinFirstStart8_56x51);
+                popup->set_icon(72, 17, &I_DolphinCommon_56x48);
                 popup->set_header("Still trying to write...", 64, 3, AlignCenter, AlignTop);
                 popup->set_text(
                     "Make sure this\ncard is writable\nand not\nprotected.",

+ 1 - 0
applications/lfrfid_debug/lfrfid_debug_app.cpp

@@ -10,6 +10,7 @@ LfRfidDebugApp::~LfRfidDebugApp() {
 }
 
 void LfRfidDebugApp::run() {
+    view_controller.attach_to_gui(ViewDispatcherTypeFullscreen);
     scene_controller.add_scene(SceneType::Start, new LfRfidDebugAppSceneStart());
     scene_controller.add_scene(SceneType::TuneScene, new LfRfidDebugAppSceneTune());
     scene_controller.process(100);

+ 1 - 1
applications/loader/application.fam

@@ -5,6 +5,6 @@ App(
     entry_point="loader_srv",
     cdefines=["SRV_LOADER"],
     requires=["gui"],
-    stack_size=1 * 1024,
+    stack_size=2 * 1024,
     order=90,
 )

+ 3 - 3
applications/loader/loader.c

@@ -466,9 +466,9 @@ int32_t loader_srv(void* p) {
 
     furi_record_create(RECORD_LOADER, loader_instance);
 
-#ifdef LOADER_AUTOSTART
-    loader_start(loader_instance, LOADER_AUTOSTART, NULL);
-#endif
+    if(FLIPPER_AUTORUN_APP_NAME && strlen(FLIPPER_AUTORUN_APP_NAME)) {
+        loader_start(loader_instance, FLIPPER_AUTORUN_APP_NAME, NULL);
+    }
 
     while(1) {
         uint32_t flags =

+ 2 - 0
applications/nfc/nfc.c

@@ -21,6 +21,8 @@ static void nfc_rpc_command_callback(RpcAppSystemEvent event, void* context) {
 
     if(event == RpcAppEventSessionClose) {
         view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcSessionClose);
+        rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL);
+        nfc->rpc_ctx = NULL;
     } else if(event == RpcAppEventAppExit) {
         view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
     } else if(event == RpcAppEventLoadFile) {

+ 1 - 1
applications/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c

@@ -43,7 +43,7 @@ void nfc_scene_mf_ultralight_read_auth_set_state(Nfc* nfc, NfcSceneMfUlReadState
                 22,
                 AlignLeft,
                 AlignTop);
-            popup_set_icon(nfc->popup, 73, 17, &I_DolphinFirstStart8_56x51);
+            popup_set_icon(nfc->popup, 73, 20, &I_DolphinCommon_56x48);
         }
         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadAuth, state);
     }

+ 1 - 1
applications/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c

@@ -16,7 +16,7 @@ void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) {
     dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop);
     dialog_ex_set_text(
         dialog_ex, "Wrong password\ncan block your\ncard.", 4, 18, AlignLeft, AlignTop);
-    dialog_ex_set_icon(dialog_ex, 73, 17, &I_DolphinFirstStart8_56x51);
+    dialog_ex_set_icon(dialog_ex, 73, 20, &I_DolphinCommon_56x48);
     dialog_ex_set_center_button_text(dialog_ex, "OK");
 
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);

+ 6 - 8
applications/nfc/scenes/nfc_scene_rpc.c

@@ -4,10 +4,10 @@ void nfc_scene_rpc_on_enter(void* context) {
     Nfc* nfc = context;
     Popup* popup = nfc->popup;
 
-    popup_set_header(popup, "NFC", 82, 28, AlignCenter, AlignBottom);
-    popup_set_text(popup, "RPC mode", 82, 32, AlignCenter, AlignTop);
+    popup_set_header(popup, "NFC", 89, 42, AlignCenter, AlignBottom);
+    popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop);
 
-    popup_set_icon(popup, 2, 14, &I_Warning_30x23); // TODO: icon
+    popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61);
 
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
 
@@ -31,13 +31,11 @@ bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) {
         consumed = true;
         if(event.event == NfcCustomEventViewExit) {
             rpc_system_app_confirm(nfc->rpc_ctx, RpcAppEventAppExit, true);
+            scene_manager_stop(nfc->scene_manager);
             view_dispatcher_stop(nfc->view_dispatcher);
-            nfc_blink_stop(nfc);
         } else if(event.event == NfcCustomEventRpcSessionClose) {
-            rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL);
-            nfc->rpc_ctx = NULL;
+            scene_manager_stop(nfc->scene_manager);
             view_dispatcher_stop(nfc->view_dispatcher);
-            nfc_blink_stop(nfc);
         } else if(event.event == NfcCustomEventRpcLoad) {
             bool result = false;
             const char* arg = rpc_system_app_get_data(nfc->rpc_ctx);
@@ -66,7 +64,7 @@ bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) {
 
                     nfc_blink_start(nfc);
                     nfc_text_store_set(nfc, "emulating\n%s", nfc->dev->dev_name);
-                    popup_set_text(popup, nfc->text_store, 82, 32, AlignCenter, AlignTop);
+                    popup_set_text(popup, nfc->text_store, 89, 44, AlignCenter, AlignTop);
                 }
             }
 

+ 21 - 17
applications/rpc/rpc_storage.c

@@ -297,9 +297,9 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex
     const char* path = request->content.storage_read_request.path;
     Storage* fs_api = furi_record_open(RECORD_STORAGE);
     File* file = storage_file_alloc(fs_api);
-    bool result = false;
+    bool fs_operation_success = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING);
 
-    if(storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+    if(fs_operation_success) {
         size_t size_left = storage_file_size(file);
         do {
             response->command_id = request->command_id;
@@ -310,29 +310,31 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex
             if(read_size) {
                 response->content.storage_read_response.has_file = true;
                 response->content.storage_read_response.file.data =
-                    malloc(PB_BYTES_ARRAY_T_ALLOCSIZE());
-                uint8_t* buffer = response->content.storage_read_response.file.data->bytes;
+                    malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(read_size));
+                uint8_t* buffer = &response->content.storage_read_response.file.data->bytes[0];
                 uint16_t* read_size_msg = &response->content.storage_read_response.file.data->size;
 
                 *read_size_msg = storage_file_read(file, buffer, read_size);
-                size_left -= read_size;
-                result = (*read_size_msg == read_size);
+                size_left -= *read_size_msg;
+                fs_operation_success = (*read_size_msg == read_size);
 
-                response->has_next = result && (size_left > 0);
+                response->has_next = fs_operation_success && (size_left > 0);
             } else {
-                response->content.storage_read_response.has_file = false;
+                response->content.storage_read_response.file.data =
+                    malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(0));
+                response->content.storage_read_response.file.data->size = 0;
+                response->content.storage_read_response.has_file = true;
                 response->has_next = false;
-                result = true;
+                fs_operation_success = true;
             }
 
-            rpc_send_and_release(session, response);
-        } while((size_left != 0) && result);
+            if(fs_operation_success) {
+                rpc_send_and_release(session, response);
+            }
+        } while((size_left != 0) && fs_operation_success);
+    }
 
-        if(!result) {
-            rpc_send_and_release_empty(
-                session, request->command_id, rpc_system_storage_get_file_error(file));
-        }
-    } else {
+    if(!fs_operation_success) {
         rpc_send_and_release_empty(
             session, request->command_id, rpc_system_storage_get_file_error(file));
     }
@@ -384,7 +386,9 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte
     bool send_response = false;
 
     if(fs_operation_success) {
-        if(request->content.storage_write_request.has_file) {
+        if(request->content.storage_write_request.has_file &&
+           request->content.storage_write_request.file.data &&
+           request->content.storage_write_request.file.data->size) {
             uint8_t* buffer = request->content.storage_write_request.file.data->bytes;
             size_t buffer_size = request->content.storage_write_request.file.data->size;
             uint16_t written_size = storage_file_write(file, buffer, buffer_size);

+ 1 - 1
applications/storage_settings/scenes/storage_settings_scene_benchmark.c

@@ -122,7 +122,7 @@ void storage_settings_scene_benchmark_on_enter(void* context) {
     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
 
     if(sd_status != FSE_OK) {
-        dialog_ex_set_icon(dialog_ex, 72, 14, &I_DolphinFirstStart8_56x51);
+        dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48);
         dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop);
         dialog_ex_set_text(
             dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop);

+ 1 - 1
applications/storage_settings/scenes/storage_settings_scene_format_confirm.c

@@ -14,7 +14,7 @@ void storage_settings_scene_format_confirm_on_enter(void* context) {
     FS_Error sd_status = storage_sd_status(app->fs_api);
 
     if(sd_status == FSE_NOT_READY) {
-        dialog_ex_set_icon(dialog_ex, 72, 14, &I_DolphinFirstStart8_56x51);
+        dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48);
         dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop);
         dialog_ex_set_text(
             dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop);

+ 1 - 1
applications/storage_settings/scenes/storage_settings_scene_formatting.c

@@ -47,7 +47,7 @@ void storage_settings_scene_formatting_on_enter(void* context) {
         dialog_ex_set_text(
             dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter);
     } else {
-        dialog_ex_set_icon(dialog_ex, 72, 14, &I_DolphinFirstStart8_56x51);
+        dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48);
         dialog_ex_set_header(dialog_ex, "Format\ncomplete!", 14, 15, AlignLeft, AlignTop);
     }
     dialog_ex_set_center_button_text(dialog_ex, "OK");

+ 1 - 1
applications/storage_settings/scenes/storage_settings_scene_sd_info.c

@@ -18,7 +18,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) {
     dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_sd_info_dialog_callback);
 
     if(sd_status != FSE_OK) {
-        dialog_ex_set_icon(dialog_ex, 72, 14, &I_DolphinFirstStart8_56x51);
+        dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48);
         dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop);
         dialog_ex_set_text(
             dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop);

+ 1 - 1
applications/storage_settings/scenes/storage_settings_scene_unmount_confirm.c

@@ -14,7 +14,7 @@ void storage_settings_scene_unmount_confirm_on_enter(void* context) {
     FS_Error sd_status = storage_sd_status(app->fs_api);
 
     if(sd_status == FSE_NOT_READY) {
-        dialog_ex_set_icon(dialog_ex, 72, 14, &I_DolphinFirstStart8_56x51);
+        dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48);
         dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop);
         dialog_ex_set_text(
             dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop);

+ 1 - 1
applications/storage_settings/scenes/storage_settings_scene_unmounted.c

@@ -13,7 +13,7 @@ void storage_settings_scene_unmounted_on_enter(void* context) {
     DialogEx* dialog_ex = app->dialog_ex;
 
     dialog_ex_set_center_button_text(dialog_ex, "OK");
-    dialog_ex_set_icon(dialog_ex, 72, 14, &I_DolphinFirstStart8_56x51);
+    dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48);
 
     if(error == FSE_OK) {
         dialog_ex_set_header(dialog_ex, "SD Card Unmounted", 64, 3, AlignCenter, AlignTop);

+ 1 - 1
applications/subghz/scenes/subghz_scene_need_saving.c

@@ -16,7 +16,7 @@ void subghz_scene_need_saving_on_enter(void* context) {
     SubGhz* subghz = context;
 
     widget_add_string_multiline_element(
-        subghz->widget, 64, 13, AlignCenter, AlignCenter, FontPrimary, "Exit to Sub-Ghz menu?");
+        subghz->widget, 64, 13, AlignCenter, AlignCenter, FontPrimary, "Exit to Sub-GHz menu?");
     widget_add_string_multiline_element(
         subghz->widget,
         64,

+ 1 - 1
applications/subghz/scenes/subghz_scene_receiver_info.c

@@ -102,7 +102,7 @@ void subghz_scene_receiver_info_on_enter(void* context) {
                 subghz);
         }
     } else {
-        widget_add_icon_element(subghz->widget, 32, 12, &I_DolphinFirstStart7_61x51);
+        widget_add_icon_element(subghz->widget, 37, 15, &I_DolphinCommon_56x48);
         widget_add_string_element(
             subghz->widget, 13, 8, AlignLeft, AlignBottom, FontSecondary, "Error history parse.");
     }

+ 27 - 18
applications/subghz/scenes/subghz_scene_rpc.c

@@ -1,16 +1,23 @@
 #include "../subghz_i.h"
 
+typedef enum {
+    SubGhzRpcStateIdle,
+    SubGhzRpcStateLoaded,
+} SubGhzRpcState;
+
 void subghz_scene_rpc_on_enter(void* context) {
     SubGhz* subghz = context;
     Popup* popup = subghz->popup;
 
-    popup_set_header(popup, "Sub-GHz", 82, 28, AlignCenter, AlignBottom);
-    popup_set_text(popup, "RPC mode", 82, 32, AlignCenter, AlignTop);
+    popup_set_header(popup, "Sub-GHz", 89, 42, AlignCenter, AlignBottom);
+    popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop);
 
-    popup_set_icon(popup, 2, 14, &I_Warning_30x23); // TODO: icon
+    popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61);
 
     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup);
 
+    scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
+
     notification_message(subghz->notifications, &sequence_display_backlight_on);
 }
 
@@ -18,28 +25,21 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
     SubGhz* subghz = context;
     Popup* popup = subghz->popup;
     bool consumed = false;
+    SubGhzRpcState state = scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneRpc);
 
     if(event.type == SceneManagerEventTypeCustom) {
         consumed = true;
         if(event.event == SubGhzCustomEventSceneExit) {
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
-                subghz_tx_stop(subghz);
-                subghz_sleep(subghz);
-            }
+            scene_manager_stop(subghz->scene_manager);
             view_dispatcher_stop(subghz->view_dispatcher);
             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventAppExit, true);
         } else if(event.event == SubGhzCustomEventSceneRpcSessionClose) {
-            rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL);
-            subghz->rpc_ctx = NULL;
-            subghz_blink_stop(subghz);
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
-                subghz_tx_stop(subghz);
-                subghz_sleep(subghz);
-            }
-            view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit);
+            scene_manager_stop(subghz->scene_manager);
+            view_dispatcher_stop(subghz->view_dispatcher);
         } else if(event.event == SubGhzCustomEventSceneRpcButtonPress) {
             bool result = false;
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateSleep) {
+            if((subghz->txrx->txrx_state == SubGhzTxRxStateSleep) &&
+               (state == SubGhzRpcStateLoaded)) {
                 subghz_blink_start(subghz);
                 result = subghz_tx_start(subghz, subghz->txrx->fff_data);
                 result = true;
@@ -57,8 +57,10 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
         } else if(event.event == SubGhzCustomEventSceneRpcLoad) {
             bool result = false;
             const char* arg = rpc_system_app_get_data(subghz->rpc_ctx);
-            if(arg) {
+            if(arg && (state == SubGhzRpcStateIdle)) {
                 if(subghz_key_load(subghz, arg, false)) {
+                    scene_manager_set_scene_state(
+                        subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateLoaded);
                     string_set_str(subghz->file_path, arg);
                     result = true;
                     string_t file_name;
@@ -70,7 +72,7 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
                         SUBGHZ_MAX_LEN_NAME,
                         "loaded\n%s",
                         string_get_cstr(file_name));
-                    popup_set_text(popup, subghz->file_name_tmp, 82, 32, AlignCenter, AlignTop);
+                    popup_set_text(popup, subghz->file_name_tmp, 89, 44, AlignCenter, AlignTop);
 
                     string_clear(file_name);
                 }
@@ -83,6 +85,13 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
 
 void subghz_scene_rpc_on_exit(void* context) {
     SubGhz* subghz = context;
+
+    if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
+        subghz_tx_stop(subghz);
+        subghz_sleep(subghz);
+        subghz_blink_stop(subghz);
+    }
+
     Popup* popup = subghz->popup;
 
     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);

+ 1 - 1
applications/subghz/scenes/subghz_scene_show_error_sub.c

@@ -11,7 +11,7 @@ void subghz_scene_show_error_sub_on_enter(void* context) {
 
     // Setup view
     Popup* popup = subghz->popup;
-    popup_set_icon(popup, 72, 14, &I_DolphinFirstStart8_56x51);
+    popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48);
     popup_set_header(popup, string_get_cstr(subghz->error_str), 14, 15, AlignLeft, AlignTop);
     popup_set_timeout(popup, 1500);
     popup_set_context(popup, subghz);

+ 12 - 8
applications/subghz/scenes/subghz_scene_show_only_rx.c

@@ -11,14 +11,18 @@ void subghz_scene_show_only_rx_on_enter(void* context) {
 
     // Setup view
     Popup* popup = subghz->popup;
-    popup_set_icon(popup, 67, 12, &I_DolphinFirstStart7_61x51);
-    popup_set_text(
-        popup,
-        "This frequency can\nonly be used for RX\nin your region",
-        38,
-        40,
-        AlignCenter,
-        AlignBottom);
+
+    const char* header_text = "Transmission is blocked";
+    const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region";
+    if(!furi_hal_region_is_provisioned()) {
+        header_text = "Firmware update needed";
+        message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd";
+    }
+
+    popup_set_header(popup, header_text, 63, 3, AlignCenter, AlignTop);
+    popup_set_text(popup, message_text, 0, 17, AlignLeft, AlignTop);
+    popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48);
+
     popup_set_timeout(popup, 1500);
     popup_set_context(popup, subghz);
     popup_set_callback(popup, subghz_scene_show_only_rx_popup_callback);

+ 8 - 0
applications/subghz/subghz.c

@@ -32,6 +32,8 @@ static void subghz_rpc_command_callback(RpcAppSystemEvent event, void* context)
     if(event == RpcAppEventSessionClose) {
         view_dispatcher_send_custom_event(
             subghz->view_dispatcher, SubGhzCustomEventSceneRpcSessionClose);
+        rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL);
+        subghz->rpc_ctx = NULL;
     } else if(event == RpcAppEventAppExit) {
         view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit);
     } else if(event == RpcAppEventLoadFile) {
@@ -302,6 +304,12 @@ void subghz_free(SubGhz* subghz) {
 int32_t subghz_app(void* p) {
     SubGhz* subghz = subghz_alloc();
 
+    if(!furi_hal_region_is_provisioned()) {
+        subghz_dialog_message_show_only_rx(subghz);
+        subghz_free(subghz);
+        return 1;
+    }
+
     //Load database
     bool load_database = subghz_environment_load_keystore(
         subghz->txrx->environment, EXT_PATH("subghz/assets/keeloq_mfcodes"));

+ 94 - 1
applications/subghz/subghz_cli.c

@@ -16,9 +16,14 @@
 #include <notification/notification_messages.h>
 #include <flipper_format/flipper_format_i.h>
 
+#include <flipper.pb.h>
+#include <pb_decode.h>
+
 #define SUBGHZ_FREQUENCY_RANGE_STR \
     "299999755...348000000 or 386999938...464000000 or 778999847...928000000"
 
+#define SUBGHZ_REGION_FILENAME "/int/.region_data"
+
 void subghz_cli_command_tx_carrier(Cli* cli, string_t args, void* context) {
     UNUSED(context);
     uint32_t frequency = 433920000;
@@ -533,7 +538,7 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) {
             return;
         }
     }
-    if(!furi_hal_subghz_is_tx_allowed(frequency)) {
+    if(!furi_hal_region_is_frequency_allowed(frequency)) {
         printf(
             "In your region, only reception on this frequency (%lu) is allowed,\r\n"
             "the actual operation of the application is not possible\r\n ",
@@ -756,6 +761,46 @@ static void subghz_cli_command(Cli* cli, string_t args, void* context) {
     string_clear(cmd);
 }
 
+static bool
+    subghz_on_system_start_istream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
+    File* file = istream->state;
+    uint16_t ret = storage_file_read(file, buf, count);
+    return (count == ret);
+}
+
+static bool subghz_on_system_start_istream_decode_band(
+    pb_istream_t* stream,
+    const pb_field_t* field,
+    void** arg) {
+    (void)field;
+    FuriHalRegion* region = *arg;
+
+    PB_Region_Band band = {0};
+    if(!pb_decode(stream, PB_Region_Band_fields, &band)) {
+        FURI_LOG_E("SubGhzOnStart", "PB Region band decode error: %s", PB_GET_ERROR(stream));
+        return false;
+    }
+
+    region->bands_count += 1;
+    region =
+        realloc(region, sizeof(FuriHalRegion) + sizeof(FuriHalRegionBand) * region->bands_count);
+    size_t pos = region->bands_count - 1;
+    region->bands[pos].start = band.start;
+    region->bands[pos].end = band.end;
+    region->bands[pos].power_limit = band.power_limit;
+    region->bands[pos].duty_cycle = band.duty_cycle;
+    *arg = region;
+
+    FURI_LOG_I(
+        "SubGhzOnStart",
+        "Add allowed band: start %dHz, stop %dHz, power_limit %ddBm, duty_cycle %d%%",
+        band.start,
+        band.end,
+        band.power_limit,
+        band.duty_cycle);
+    return true;
+}
+
 void subghz_on_system_start() {
 #ifdef SRV_CLI
     Cli* cli = furi_record_open(RECORD_CLI);
@@ -766,4 +811,52 @@ void subghz_on_system_start() {
 #else
     UNUSED(subghz_cli_command);
 #endif
+
+#ifdef SRV_STORAGE
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    File* file = storage_file_alloc(storage);
+    FileInfo fileinfo = {0};
+    PB_Region pb_region = {0};
+    pb_region.bands.funcs.decode = subghz_on_system_start_istream_decode_band;
+
+    do {
+        if(storage_common_stat(storage, SUBGHZ_REGION_FILENAME, &fileinfo) != FSE_OK ||
+           fileinfo.size == 0) {
+            FURI_LOG_W("SubGhzOnStart", "Region data is missing or empty");
+            break;
+        }
+
+        if(!storage_file_open(file, SUBGHZ_REGION_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
+            FURI_LOG_E("SubGhzOnStart", "Unable to open region data");
+            break;
+        }
+
+        pb_istream_t istream = {
+            .callback = subghz_on_system_start_istream_read,
+            .state = file,
+            .errmsg = NULL,
+            .bytes_left = fileinfo.size,
+        };
+
+        pb_region.bands.arg = malloc(sizeof(FuriHalRegion));
+        if(!pb_decode(&istream, PB_Region_fields, &pb_region)) {
+            FURI_LOG_E("SubGhzOnStart", "Invalid region data");
+            free(pb_region.bands.arg);
+            break;
+        }
+
+        FuriHalRegion* region = pb_region.bands.arg;
+        memcpy(
+            region->country_code,
+            pb_region.country_code->bytes,
+            pb_region.country_code->size < 4 ? pb_region.country_code->size : 3);
+        furi_hal_region_set(region);
+    } while(0);
+
+    pb_release(PB_Region_fields, &pb_region);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+#else
+    UNUSED(subghz_cli_command);
+#endif
 }

+ 10 - 10
applications/subghz/subghz_i.c

@@ -219,17 +219,17 @@ void subghz_dialog_message_show_only_rx(SubGhz* subghz) {
     DialogsApp* dialogs = subghz->dialogs;
     DialogMessage* message = dialog_message_alloc();
 
-    dialog_message_set_header(message, "Transmission is blocked", 63, 3, AlignCenter, AlignTop);
+    const char* header_text = "Transmission is blocked";
+    const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region";
+    if(!furi_hal_region_is_provisioned()) {
+        header_text = "Firmware update needed";
+        message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd";
+    }
 
-    dialog_message_set_text(
-        message,
-        "This frequency\nis restricted to\nreceiving only\nin your region.",
-        3,
-        17,
-        AlignLeft,
-        AlignTop);
+    dialog_message_set_header(message, header_text, 63, 3, AlignCenter, AlignTop);
+    dialog_message_set_text(message, message_text, 0, 17, AlignLeft, AlignTop);
 
-    dialog_message_set_icon(message, &I_DolphinFirstStart8_56x51, 72, 14);
+    dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17);
 
     dialog_message_show(dialogs, message);
     dialog_message_free(message);
@@ -278,7 +278,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
             break;
         }
 
-        if(!furi_hal_subghz_is_tx_allowed(temp_data32)) {
+        if(!furi_hal_region_is_frequency_allowed(temp_data32)) {
             FURI_LOG_E(TAG, "This frequency can only be used for RX in your region");
             load_key_state = SubGhzLoadKeyStateOnlyRx;
             break;

+ 17 - 1
applications/unit_tests/subghz/subghz_test.c

@@ -13,7 +13,7 @@
 #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo")
 #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s")
 #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub")
-#define TEST_RANDOM_COUNT_PARSE 158
+#define TEST_RANDOM_COUNT_PARSE 188
 #define TEST_TIMEOUT 10000
 
 static SubGhzEnvironment* environment_handler;
@@ -404,6 +404,14 @@ MU_TEST(subghz_decoder_phoenix_v2_test) {
         "Test decoder " SUBGHZ_PROTOCOL_PHOENIX_V2_NAME " error\r\n");
 }
 
+MU_TEST(subghz_decoder_honeywell_wdb_test) {
+    mu_assert(
+        subghz_decoder_test(
+            EXT_PATH("unit_tests/subghz/honeywell_wdb_raw.sub"),
+            SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME),
+        "Test decoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n");
+}
+
 //test encoders
 MU_TEST(subghz_encoder_princeton_test) {
     mu_assert(
@@ -501,6 +509,12 @@ MU_TEST(subghz_encoder_phoenix_v2_test) {
         "Test encoder " SUBGHZ_PROTOCOL_PHOENIX_V2_NAME " error\r\n");
 }
 
+MU_TEST(subghz_encoder_honeywell_wdb_test) {
+    mu_assert(
+        subghz_encoder_test(EXT_PATH("unit_tests/subghz/honeywell_wdb.sub")),
+        "Test encoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n");
+}
+
 MU_TEST(subghz_random_test) {
     mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n");
 }
@@ -537,6 +551,7 @@ MU_TEST_SUITE(subghz) {
     MU_RUN_TEST(subghz_decoder_bett_test);
     MU_RUN_TEST(subghz_decoder_doitrand_test);
     MU_RUN_TEST(subghz_decoder_phoenix_v2_test);
+    MU_RUN_TEST(subghz_decoder_honeywell_wdb_test);
 
     MU_RUN_TEST(subghz_encoder_princeton_test);
     MU_RUN_TEST(subghz_encoder_came_test);
@@ -554,6 +569,7 @@ MU_TEST_SUITE(subghz) {
     MU_RUN_TEST(subghz_encoder_bett_test);
     MU_RUN_TEST(subghz_encoder_doitrand_test);
     MU_RUN_TEST(subghz_encoder_phoenix_v2_test);
+    MU_RUN_TEST(subghz_encoder_honeywell_wdb_test);
 
     MU_RUN_TEST(subghz_random_test);
     subghz_test_deinit();

BIN
assets/icons/Dolphin/DolphinCommon_56x48.png


BIN
assets/icons/Dolphin/DolphinFirstStart7_61x51.png


BIN
assets/icons/Dolphin/DolphinFirstStart8_56x51.png


+ 1 - 1
assets/protobuf

@@ -1 +1 @@
-Subproject commit cc5918dc488ac3617012ce5377114e086b447324
+Subproject commit 6727eaf287db077dcd28719cd764f5804712223e

+ 2 - 2
assets/resources/Manifest

@@ -1,5 +1,5 @@
 V:0
-T:1659888302
+T:1660218073
 D:badusb
 D:dolphin
 D:infrared
@@ -8,7 +8,7 @@ D:nfc
 D:subghz
 D:u2f
 F:0e41ba26498b7511d7c9e6e6b5e3b149:1592:badusb/demo_macos.txt
-F:e538ad2ce5a06ec45e1b5b24824901b1:1552:badusb/demo_windows.txt
+F:46a332993ca94b9aa692030ebaa19c70:1552:badusb/demo_windows.txt
 D:dolphin/L1_Boxing_128x64
 D:dolphin/L1_Cry_128x64
 D:dolphin/L1_Furippa1_128x64

+ 1 - 1
assets/resources/badusb/demo_windows.txt

@@ -13,7 +13,7 @@ STRING Hello World!
 ENTER
 DEFAULT_DELAY 50
 
-REM Copy-Paste previuos string
+REM Copy-Paste previous string
 UP
 HOME
 SHIFT DOWN

+ 7 - 0
assets/unit_tests/subghz/honeywell_wdb.sub

@@ -0,0 +1,7 @@
+Filetype: Flipper SubGhz Key File
+Version: 1
+Frequency: 868350000
+Preset: FuriHalSubGhzPreset2FSKDev476Async
+Protocol: Honeywell
+Bit: 48
+Key: 00 00 0E DB 70 20 00 01

Plik diff jest za duży
+ 5 - 0
assets/unit_tests/subghz/honeywell_wdb_raw.sub


Plik diff jest za duży
+ 0 - 0
assets/unit_tests/subghz/test_random_raw.sub


+ 5 - 0
fbt_options.py

@@ -1,5 +1,7 @@
 import posixpath
 
+# For more details on these options, run 'fbt -h'
+
 
 # Default hardware target
 TARGET_HW = 7
@@ -59,6 +61,9 @@ SVD_FILE = "debug/STM32WB55_CM4.svd"
 # Look for blackmagic probe on serial ports and local network
 BLACKMAGIC = "auto"
 
+# Application to start on boot
+LOADER_AUTOSTART = ""
+
 FIRMWARE_APPS = {
     "default": [
         "crypto_start",

+ 10 - 2
firmware.scons

@@ -139,7 +139,7 @@ fwenv.AppendUnique(
 # Depends on virtual value-only node, so it only gets rebuilt when set of apps changes
 apps_c = fwenv.ApplicationsC(
     "applications/applications.c",
-    Value(fwenv["APPS"]),
+    [Value(fwenv["APPS"]), Value(fwenv["LOADER_AUTOSTART"])],
 )
 # Adding dependency on manifest files so apps.c is rebuilt when any manifest is changed
 fwenv.Depends(apps_c, fwenv.GlobRecursive("*.fam", "#/applications"))
@@ -210,11 +210,19 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program(
 Depends(fwelf, lib_targets)
 # Output extra details after building firmware
 AddPostAction(fwelf, fwenv["APPBUILD_DUMP"])
-AddPostAction(fwelf, Action("@$SIZECOM"))
+AddPostAction(
+    fwelf,
+    Action('${PYTHON3} "${ROOT_DIR}/scripts/fwsize.py" elf ${TARGET}', "Firmware size"),
+)
 
 # Produce extra firmware files
 fwhex = fwenv["FW_HEX"] = fwenv.HEXBuilder("${FIRMWARE_BUILD_CFG}")
 fwbin = fwenv["FW_BIN"] = fwenv.BINBuilder("${FIRMWARE_BUILD_CFG}")
+AddPostAction(
+    fwbin,
+    Action('@${PYTHON3} "${ROOT_DIR}/scripts/fwsize.py" bin ${TARGET}'),
+)
+
 fwdfu = fwenv["FW_DFU"] = fwenv.DFUBuilder("${FIRMWARE_BUILD_CFG}")
 Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_dfu", fwdfu)
 

+ 1 - 0
firmware/targets/f7/furi_hal/furi_hal.c

@@ -49,6 +49,7 @@ void furi_hal_init() {
     FURI_LOG_I(TAG, "GPIO OK");
 
     furi_hal_version_init();
+    furi_hal_region_init();
 
     furi_hal_spi_init();
 

+ 4 - 1
firmware/targets/f7/furi_hal/furi_hal_clock.c

@@ -75,7 +75,10 @@ void furi_hal_clock_init() {
         LL_EXTI_LINE_18); /* Why? Because that's why. See RM0434, Table 61. CPU1 vector table. */
     LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINE_18);
     LL_RCC_EnableIT_LSECSS();
-    LL_RCC_LSE_EnableCSS();
+    /* ES0394, extended case of 2.2.2 */
+    if(!LL_RCC_IsActiveFlag_BORRST()) {
+        LL_RCC_LSE_EnableCSS();
+    }
 
     /* Main PLL configuration and activation */
     LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_HSE, LL_RCC_PLLM_DIV_2, 8, LL_RCC_PLLR_DIV_2);

+ 7 - 1
firmware/targets/f7/furi_hal/furi_hal_info.c

@@ -1,6 +1,11 @@
 #include <furi_hal_info.h>
-#include <furi_hal.h>
+#include <furi_hal_region.h>
+#include <furi_hal_version.h>
+#include <furi_hal_bt.h>
+#include <furi_hal_crypto.h>
+
 #include <shci.h>
+#include <m-string.h>
 #include <protobuf_version.h>
 
 void furi_hal_info_get(FuriHalInfoValueCallback out, void* context) {
@@ -45,6 +50,7 @@ void furi_hal_info_get(FuriHalInfoValueCallback out, void* context) {
     out("hardware_color", string_get_cstr(value), false, context);
     string_printf(value, "%d", furi_hal_version_get_hw_region());
     out("hardware_region", string_get_cstr(value), false, context);
+    out("hardware_region_provisioned", furi_hal_region_get_name(), false, context);
     const char* name = furi_hal_version_get_name_ptr();
     if(name) {
         out("hardware_name", name, false, context);

+ 135 - 0
firmware/targets/f7/furi_hal/furi_hal_region.c

@@ -0,0 +1,135 @@
+#include <furi_hal_region.h>
+#include <furi_hal_version.h>
+
+const FuriHalRegion furi_hal_region_zero = {
+    .country_code = "00",
+    .bands_count = 1,
+    .bands = {
+        {
+            .start = 0,
+            .end = 1000000000,
+            .power_limit = 12,
+            .duty_cycle = 50,
+        },
+    }};
+
+const FuriHalRegion furi_hal_region_eu_ru = {
+    .country_code = "EU",
+    .bands_count = 2,
+    .bands = {
+        {
+            .start = 433050000,
+            .end = 434790000,
+            .power_limit = 12,
+            .duty_cycle = 50,
+        },
+        {
+            .start = 868150000,
+            .end = 868550000,
+            .power_limit = 12,
+            .duty_cycle = 50,
+        }}};
+
+const FuriHalRegion furi_hal_region_us_ca_au = {
+    .country_code = "US",
+    .bands_count = 3,
+    .bands = {
+        {
+            .start = 304100000,
+            .end = 321950000,
+            .power_limit = 12,
+            .duty_cycle = 50,
+        },
+        {
+            .start = 433050000,
+            .end = 434790000,
+            .power_limit = 12,
+            .duty_cycle = 50,
+        },
+        {
+            .start = 915000000,
+            .end = 928000000,
+            .power_limit = 12,
+            .duty_cycle = 50,
+        }}};
+
+const FuriHalRegion furi_hal_region_jp = {
+    .country_code = "JP",
+    .bands_count = 2,
+    .bands = {
+        {
+            .start = 312000000,
+            .end = 315250000,
+            .power_limit = 12,
+            .duty_cycle = 50,
+        },
+        {
+            .start = 920500000,
+            .end = 923500000,
+            .power_limit = 12,
+            .duty_cycle = 50,
+        }}};
+
+static const FuriHalRegion* furi_hal_region = NULL;
+
+void furi_hal_region_init() {
+    FuriHalVersionRegion region = furi_hal_version_get_hw_region();
+
+    if(region == FuriHalVersionRegionUnknown) {
+        furi_hal_region = &furi_hal_region_zero;
+    } else if(region == FuriHalVersionRegionEuRu) {
+        furi_hal_region = &furi_hal_region_eu_ru;
+    } else if(region == FuriHalVersionRegionUsCaAu) {
+        furi_hal_region = &furi_hal_region_us_ca_au;
+    } else if(region == FuriHalVersionRegionJp) {
+        furi_hal_region = &furi_hal_region_jp;
+    }
+}
+
+const FuriHalRegion* furi_hal_region_get() {
+    return furi_hal_region;
+}
+
+void furi_hal_region_set(FuriHalRegion* region) {
+    furi_hal_region = region;
+}
+
+bool furi_hal_region_is_provisioned() {
+    return furi_hal_region != NULL;
+}
+
+const char* furi_hal_region_get_name() {
+    if(furi_hal_region) {
+        return furi_hal_region->country_code;
+    } else {
+        return "--";
+    }
+}
+
+bool furi_hal_region_is_frequency_allowed(uint32_t frequency) {
+    if(!furi_hal_region) {
+        return false;
+    }
+
+    const FuriHalRegionBand* band = furi_hal_region_get_band(frequency);
+    if(!band) {
+        return false;
+    }
+
+    return true;
+}
+
+const FuriHalRegionBand* furi_hal_region_get_band(uint32_t frequency) {
+    if(!furi_hal_region) {
+        return NULL;
+    }
+
+    for(size_t i = 0; i < furi_hal_region->bands_count; i++) {
+        if(furi_hal_region->bands[i].start <= frequency &&
+           furi_hal_region->bands[i].end >= frequency) {
+            return &furi_hal_region->bands[i];
+        }
+    }
+
+    return NULL;
+}

+ 2 - 45
firmware/targets/f7/furi_hal/furi_hal_subghz.c

@@ -1,6 +1,7 @@
 #include "furi_hal_subghz.h"
 #include "furi_hal_subghz_configs.h"
 
+#include <furi_hal_region.h>
 #include <furi_hal_version.h>
 #include <furi_hal_rtc.h>
 #include <furi_hal_gpio.h>
@@ -308,52 +309,8 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value) {
     return value;
 }
 
-bool furi_hal_subghz_is_tx_allowed(uint32_t value) {
-    //checking regional settings
-    bool is_allowed = false;
-    switch(furi_hal_version_get_hw_region()) {
-    case FuriHalVersionRegionEuRu:
-        //433,05..434,79; 868,15..868,55
-        if(!(value >= 433050000 && value <= 434790000) &&
-           !(value >= 868150000 && value <= 868550000)) {
-        } else {
-            is_allowed = true;
-        }
-        break;
-    case FuriHalVersionRegionUsCaAu:
-        //304,10..321,95; 433,05..434,79; 915,00..928,00
-        if(!(value >= 304100000 && value <= 321950000) &&
-           !(value >= 433050000 && value <= 434790000) &&
-           !(value >= 915000000 && value <= 928000000)) {
-        } else {
-            if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
-                if((value >= 304100000 && value <= 321950000) &&
-                   ((furi_hal_subghz.preset == FuriHalSubGhzPresetOok270Async) ||
-                    (furi_hal_subghz.preset == FuriHalSubGhzPresetOok650Async))) {
-                    furi_hal_subghz_load_patable(furi_hal_subghz_preset_ook_async_patable_au);
-                }
-            }
-            is_allowed = true;
-        }
-        break;
-    case FuriHalVersionRegionJp:
-        //312,00..315,25; 920,50..923,50
-        if(!(value >= 312000000 && value <= 315250000) &&
-           !(value >= 920500000 && value <= 923500000)) {
-        } else {
-            is_allowed = true;
-        }
-        break;
-
-    default:
-        is_allowed = true;
-        break;
-    }
-    return is_allowed;
-}
-
 uint32_t furi_hal_subghz_set_frequency(uint32_t value) {
-    if(furi_hal_subghz_is_tx_allowed(value)) {
+    if(furi_hal_region_is_frequency_allowed(value)) {
         furi_hal_subghz.regulation = SubGhzRegulationTxRx;
     } else {
         furi_hal_subghz.regulation = SubGhzRegulationOnlyRx;

+ 2 - 0
firmware/targets/f7/furi_hal/furi_hal_version.c

@@ -262,6 +262,8 @@ const char* furi_hal_version_get_hw_region_name() {
         return "R02";
     case FuriHalVersionRegionJp:
         return "R03";
+    case FuriHalVersionRegionWorld:
+        return "R04";
     }
     return "R??";
 }

+ 1 - 0
firmware/targets/furi_hal_include/furi_hal.h

@@ -18,6 +18,7 @@ template <unsigned int N> struct STOP_EXTERNING_ME {};
 #include "furi_hal_sd.h"
 #include "furi_hal_i2c.h"
 #include "furi_hal_resources.h"
+#include "furi_hal_region.h"
 #include "furi_hal_rtc.h"
 #include "furi_hal_speaker.h"
 #include "furi_hal_gpio.h"

+ 73 - 0
firmware/targets/furi_hal_include/furi_hal_region.h

@@ -0,0 +1,73 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+typedef struct {
+    uint32_t start;
+    uint32_t end;
+    int8_t power_limit;
+    uint8_t duty_cycle;
+} FuriHalRegionBand;
+
+typedef struct {
+    char country_code[4];
+    uint16_t bands_count;
+    FuriHalRegionBand bands[];
+} FuriHalRegion;
+
+/** Initialize region */
+void furi_hal_region_init();
+
+/** Get Region Data.
+ * 
+ * Region data may be allocated in Flash or in RAM.
+ * Keep in mind that we don't do memory management on our side.
+ *
+ * @return     pointer to FuriHalRegion instance (in RAM or Flash, check before freeing on region update)
+ */
+const FuriHalRegion* furi_hal_region_get();
+
+/** Set device region data
+ *
+ * @param      region  pointer to the FuriHalRegion
+ */
+void furi_hal_region_set(FuriHalRegion* region);
+
+/** Check if region data provisioned
+ *
+ * @return     true if provisioned, false otherwise
+ */
+bool furi_hal_region_is_provisioned();
+
+/** Get region name
+ * 
+ * 2 letter Region code according to iso 3166 standard
+ * There are 2 extra values that we use in special cases:
+ * - "00" - developer edition, unlocked
+ * - "WW" - world wide, region provisioned by default
+ * - "--" - no provisioned region
+ *
+ * @return     Pointer to string
+ */
+const char* furi_hal_region_get_name();
+
+/** Сheck if transmission is allowed on this frequency for your flipper region
+ *
+ * @param[in]  frequency  The frequency
+ * @param      value  frequency in Hz
+ *
+ * @return     true if allowed
+ */
+bool furi_hal_region_is_frequency_allowed(uint32_t frequency);
+
+/** Get band data for frequency
+ * 
+ * 
+ *
+ * @param[in]  frequency  The frequency
+ *
+ * @return     { description_of_the_return_value }
+ */
+const FuriHalRegionBand* furi_hal_region_get_band(uint32_t frequency);

+ 0 - 8
firmware/targets/furi_hal_include/furi_hal_subghz.h

@@ -180,14 +180,6 @@ bool furi_hal_subghz_is_frequency_valid(uint32_t value);
  */
 uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value);
 
-/** Сheck if transmission is allowed on this frequency for your flipper region
- *
- * @param      value  frequency in Hz
- *
- * @return     true if allowed
- */
-bool furi_hal_subghz_is_tx_allowed(uint32_t value);
-
 /** Set frequency
  *
  * @param      value  frequency in Hz

+ 1 - 0
firmware/targets/furi_hal_include/furi_hal_version.h

@@ -41,6 +41,7 @@ typedef enum {
     FuriHalVersionRegionEuRu = 0x01,
     FuriHalVersionRegionUsCaAu = 0x02,
     FuriHalVersionRegionJp = 0x03,
+    FuriHalVersionRegionWorld = 0x04,
 } FuriHalVersionRegion;
 
 /** Device Display */

+ 4 - 1
lib/app-scened-template/view_controller.hpp

@@ -26,7 +26,6 @@ public:
            0)...);
 
         gui = static_cast<Gui*>(furi_record_open("gui"));
-        view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
     };
 
     ~ViewController() {
@@ -96,6 +95,10 @@ public:
         furi_check(result == FuriStatusOk);
     }
 
+    void attach_to_gui(ViewDispatcherType type) {
+        view_dispatcher_attach_to_gui(view_dispatcher, gui, type);
+    }
+
 private:
     /**
      * @brief ViewModulesHolder

+ 8 - 0
lib/subghz/blocks/math.c

@@ -7,3 +7,11 @@ uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit) {
     }
     return key_reverse;
 }
+
+uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit) {
+    uint8_t parity = 0;
+    for(uint8_t i = 0; i < count_bit; i++) {
+        parity += bit_read(key, i);
+    }
+    return parity & 0x01;
+}

+ 8 - 0
lib/subghz/blocks/math.h

@@ -17,3 +17,11 @@
  * @return Reverse data
  */
 uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit);
+
+/**
+ * Get parity the data bitwise.
+ * @param key In data
+ * @param count_bit number of data bits
+ * @return parity
+ */
+uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit);

+ 4 - 4
lib/subghz/protocols/bett.c

@@ -231,16 +231,16 @@ void subghz_protocol_decoder_bett_feed(void* context, bool level, uint32_t durat
 
     switch(instance->decoder.parser_step) {
     case BETTDecoderStepReset:
-        if((!level) && (DURATION_DIFF(duration, subghz_protocol_bett_const.te_short * 42) <
-                        subghz_protocol_bett_const.te_delta * 21)) {
+        if((!level) && (DURATION_DIFF(duration, subghz_protocol_bett_const.te_short * 44) <
+                        (subghz_protocol_bett_const.te_delta * 15))) {
             //Found Preambula
             instance->decoder.parser_step = BETTDecoderStepCheckDuration;
         }
         break;
     case BETTDecoderStepSaveDuration:
         if(!level) {
-            if(duration >= ((uint32_t)subghz_protocol_bett_const.te_short * 10 +
-                            subghz_protocol_bett_const.te_delta)) {
+            if(DURATION_DIFF(duration, subghz_protocol_bett_const.te_short * 44) <
+               (subghz_protocol_bett_const.te_delta * 15)) {
                 instance->decoder.parser_step = BETTDecoderStepSaveDuration;
                 if(instance->decoder.decode_count_bit ==
                    subghz_protocol_bett_const.min_count_bit_for_found) {

+ 1 - 1
lib/subghz/protocols/came_twee.c

@@ -455,7 +455,7 @@ void subghz_protocol_decoder_came_twee_get_string(void* context, string_t output
 
     string_cat_printf(
         output,
-        "%s %dbit\r\n"
+        "%s %db\r\n"
         "Key:0x%lX%08lX\r\n"
         "Btn:%lX\r\n"
         "DIP:" DIP_PATTERN "\r\n",

+ 399 - 0
lib/subghz/protocols/honeywell_wdb.c

@@ -0,0 +1,399 @@
+#include "honeywell_wdb.h"
+
+#include "../blocks/const.h"
+#include "../blocks/decoder.h"
+#include "../blocks/encoder.h"
+#include "../blocks/generic.h"
+#include "../blocks/math.h"
+
+#define TAG "SubGhzProtocolHoneywellWDB"
+
+/*
+ * 
+ * https://github.com/klohner/honeywell-wireless-doorbell
+ *
+ */
+
+static const SubGhzBlockConst subghz_protocol_honeywell_wdb_const = {
+    .te_short = 160,
+    .te_long = 320,
+    .te_delta = 60,
+    .min_count_bit_for_found = 48,
+};
+
+struct SubGhzProtocolDecoderHoneywell_WDB {
+    SubGhzProtocolDecoderBase base;
+
+    SubGhzBlockDecoder decoder;
+    SubGhzBlockGeneric generic;
+    const char* device_type;
+    const char* alert;
+    uint8_t secret_knock;
+    uint8_t relay;
+    uint8_t lowbat;
+};
+
+struct SubGhzProtocolEncoderHoneywell_WDB {
+    SubGhzProtocolEncoderBase base;
+
+    SubGhzProtocolBlockEncoder encoder;
+    SubGhzBlockGeneric generic;
+};
+
+typedef enum {
+    Honeywell_WDBDecoderStepReset = 0,
+    Honeywell_WDBDecoderStepFoundStartBit,
+    Honeywell_WDBDecoderStepSaveDuration,
+    Honeywell_WDBDecoderStepCheckDuration,
+} Honeywell_WDBDecoderStep;
+
+const SubGhzProtocolDecoder subghz_protocol_honeywell_wdb_decoder = {
+    .alloc = subghz_protocol_decoder_honeywell_wdb_alloc,
+    .free = subghz_protocol_decoder_honeywell_wdb_free,
+
+    .feed = subghz_protocol_decoder_honeywell_wdb_feed,
+    .reset = subghz_protocol_decoder_honeywell_wdb_reset,
+
+    .get_hash_data = subghz_protocol_decoder_honeywell_wdb_get_hash_data,
+    .serialize = subghz_protocol_decoder_honeywell_wdb_serialize,
+    .deserialize = subghz_protocol_decoder_honeywell_wdb_deserialize,
+    .get_string = subghz_protocol_decoder_honeywell_wdb_get_string,
+};
+
+const SubGhzProtocolEncoder subghz_protocol_honeywell_wdb_encoder = {
+    .alloc = subghz_protocol_encoder_honeywell_wdb_alloc,
+    .free = subghz_protocol_encoder_honeywell_wdb_free,
+
+    .deserialize = subghz_protocol_encoder_honeywell_wdb_deserialize,
+    .stop = subghz_protocol_encoder_honeywell_wdb_stop,
+    .yield = subghz_protocol_encoder_honeywell_wdb_yield,
+};
+
+const SubGhzProtocol subghz_protocol_honeywell_wdb = {
+    .name = SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME,
+    .type = SubGhzProtocolTypeStatic,
+    .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM |
+            SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
+            SubGhzProtocolFlag_Send,
+
+    .decoder = &subghz_protocol_honeywell_wdb_decoder,
+    .encoder = &subghz_protocol_honeywell_wdb_encoder,
+};
+
+void* subghz_protocol_encoder_honeywell_wdb_alloc(SubGhzEnvironment* environment) {
+    UNUSED(environment);
+    SubGhzProtocolEncoderHoneywell_WDB* instance =
+        malloc(sizeof(SubGhzProtocolEncoderHoneywell_WDB));
+
+    instance->base.protocol = &subghz_protocol_honeywell_wdb;
+    instance->generic.protocol_name = instance->base.protocol->name;
+
+    instance->encoder.repeat = 10;
+    instance->encoder.size_upload = 128;
+    instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
+    instance->encoder.is_runing = false;
+    return instance;
+}
+
+void subghz_protocol_encoder_honeywell_wdb_free(void* context) {
+    furi_assert(context);
+    SubGhzProtocolEncoderHoneywell_WDB* instance = context;
+    free(instance->encoder.upload);
+    free(instance);
+}
+
+/**
+ * Generating an upload from data.
+ * @param instance Pointer to a SubGhzProtocolEncoderHoneywell_WDB instance
+ * @return true On success
+ */
+static bool subghz_protocol_encoder_honeywell_wdb_get_upload(
+    SubGhzProtocolEncoderHoneywell_WDB* instance) {
+    furi_assert(instance);
+    size_t index = 0;
+    size_t size_upload = (instance->generic.data_count_bit * 2) + 2;
+    if(size_upload > instance->encoder.size_upload) {
+        FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
+        return false;
+    } else {
+        instance->encoder.size_upload = size_upload;
+    }
+    //Send header
+    instance->encoder.upload[index++] =
+        level_duration_make(false, (uint32_t)subghz_protocol_honeywell_wdb_const.te_short * 3);
+    //Send key data
+    for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
+        if(bit_read(instance->generic.data, i - 1)) {
+            //send bit 1
+            instance->encoder.upload[index++] =
+                level_duration_make(true, (uint32_t)subghz_protocol_honeywell_wdb_const.te_long);
+            instance->encoder.upload[index++] =
+                level_duration_make(false, (uint32_t)subghz_protocol_honeywell_wdb_const.te_short);
+        } else {
+            //send bit 0
+            instance->encoder.upload[index++] =
+                level_duration_make(true, (uint32_t)subghz_protocol_honeywell_wdb_const.te_short);
+            instance->encoder.upload[index++] =
+                level_duration_make(false, (uint32_t)subghz_protocol_honeywell_wdb_const.te_long);
+        }
+    }
+    instance->encoder.upload[index++] =
+        level_duration_make(true, (uint32_t)subghz_protocol_honeywell_wdb_const.te_short * 3);
+    return true;
+}
+
+bool subghz_protocol_encoder_honeywell_wdb_deserialize(
+    void* context,
+    FlipperFormat* flipper_format) {
+    furi_assert(context);
+    SubGhzProtocolEncoderHoneywell_WDB* instance = context;
+    bool res = false;
+    do {
+        if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
+            FURI_LOG_E(TAG, "Deserialize error");
+            break;
+        }
+        if(instance->generic.data_count_bit !=
+           subghz_protocol_honeywell_wdb_const.min_count_bit_for_found) {
+            FURI_LOG_E(TAG, "Wrong number of bits in key");
+            break;
+        }
+        //optional parameter parameter
+        flipper_format_read_uint32(
+            flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
+
+        subghz_protocol_encoder_honeywell_wdb_get_upload(instance);
+        instance->encoder.is_runing = true;
+
+        res = true;
+    } while(false);
+
+    return res;
+}
+
+void subghz_protocol_encoder_honeywell_wdb_stop(void* context) {
+    SubGhzProtocolEncoderHoneywell_WDB* instance = context;
+    instance->encoder.is_runing = false;
+}
+
+LevelDuration subghz_protocol_encoder_honeywell_wdb_yield(void* context) {
+    SubGhzProtocolEncoderHoneywell_WDB* instance = context;
+
+    if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) {
+        instance->encoder.is_runing = false;
+        return level_duration_reset();
+    }
+
+    LevelDuration ret = instance->encoder.upload[instance->encoder.front];
+
+    if(++instance->encoder.front == instance->encoder.size_upload) {
+        instance->encoder.repeat--;
+        instance->encoder.front = 0;
+    }
+
+    return ret;
+}
+
+void* subghz_protocol_decoder_honeywell_wdb_alloc(SubGhzEnvironment* environment) {
+    UNUSED(environment);
+    SubGhzProtocolDecoderHoneywell_WDB* instance =
+        malloc(sizeof(SubGhzProtocolDecoderHoneywell_WDB));
+    instance->base.protocol = &subghz_protocol_honeywell_wdb;
+    instance->generic.protocol_name = instance->base.protocol->name;
+    return instance;
+}
+
+void subghz_protocol_decoder_honeywell_wdb_free(void* context) {
+    furi_assert(context);
+    SubGhzProtocolDecoderHoneywell_WDB* instance = context;
+    free(instance);
+}
+
+void subghz_protocol_decoder_honeywell_wdb_reset(void* context) {
+    furi_assert(context);
+    SubGhzProtocolDecoderHoneywell_WDB* instance = context;
+    instance->decoder.parser_step = Honeywell_WDBDecoderStepReset;
+}
+
+void subghz_protocol_decoder_honeywell_wdb_feed(void* context, bool level, uint32_t duration) {
+    furi_assert(context);
+    SubGhzProtocolDecoderHoneywell_WDB* instance = context;
+    switch(instance->decoder.parser_step) {
+    case Honeywell_WDBDecoderStepReset:
+        if((!level) && (DURATION_DIFF(duration, subghz_protocol_honeywell_wdb_const.te_short * 3) <
+                        subghz_protocol_honeywell_wdb_const.te_delta)) {
+            //Found header Honeywell_WDB
+            instance->decoder.decode_count_bit = 0;
+            instance->decoder.decode_data = 0;
+            instance->decoder.parser_step = Honeywell_WDBDecoderStepSaveDuration;
+        }
+        break;
+    case Honeywell_WDBDecoderStepSaveDuration:
+        if(level) { //save interval
+            if(DURATION_DIFF(duration, subghz_protocol_honeywell_wdb_const.te_short * 3) <
+               subghz_protocol_honeywell_wdb_const.te_delta) {
+                if((instance->decoder.decode_count_bit ==
+                    subghz_protocol_honeywell_wdb_const.min_count_bit_for_found) &&
+                   ((instance->decoder.decode_data & 0x01) ==
+                    subghz_protocol_blocks_get_parity(
+                        instance->decoder.decode_data >> 1,
+                        subghz_protocol_honeywell_wdb_const.min_count_bit_for_found - 1))) {
+                    instance->generic.data = instance->decoder.decode_data;
+                    instance->generic.data_count_bit = instance->decoder.decode_count_bit;
+
+                    if(instance->base.callback)
+                        instance->base.callback(&instance->base, instance->base.context);
+                }
+                instance->decoder.parser_step = Honeywell_WDBDecoderStepReset;
+                break;
+            }
+            instance->decoder.te_last = duration;
+            instance->decoder.parser_step = Honeywell_WDBDecoderStepCheckDuration;
+        } else {
+            instance->decoder.parser_step = Honeywell_WDBDecoderStepReset;
+        }
+        break;
+    case Honeywell_WDBDecoderStepCheckDuration:
+        if(!level) {
+            if((DURATION_DIFF(
+                    instance->decoder.te_last, subghz_protocol_honeywell_wdb_const.te_short) <
+                subghz_protocol_honeywell_wdb_const.te_delta) &&
+               (DURATION_DIFF(duration, subghz_protocol_honeywell_wdb_const.te_long) <
+                subghz_protocol_honeywell_wdb_const.te_delta)) {
+                subghz_protocol_blocks_add_bit(&instance->decoder, 0);
+                instance->decoder.parser_step = Honeywell_WDBDecoderStepSaveDuration;
+            } else if(
+                (DURATION_DIFF(
+                     instance->decoder.te_last, subghz_protocol_honeywell_wdb_const.te_long) <
+                 subghz_protocol_honeywell_wdb_const.te_delta) &&
+                (DURATION_DIFF(duration, subghz_protocol_honeywell_wdb_const.te_short) <
+                 subghz_protocol_honeywell_wdb_const.te_delta)) {
+                subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+                instance->decoder.parser_step = Honeywell_WDBDecoderStepSaveDuration;
+            } else
+                instance->decoder.parser_step = Honeywell_WDBDecoderStepReset;
+        } else {
+            instance->decoder.parser_step = Honeywell_WDBDecoderStepReset;
+        }
+        break;
+    }
+}
+
+/** 
+ * Analysis of received data
+ * @param instance Pointer to a SubGhzProtocolDecoderHoneywell_WDB* instance
+ */
+static void subghz_protocol_honeywell_wdb_check_remote_controller(
+    SubGhzProtocolDecoderHoneywell_WDB* instance) {
+    /*
+ *
+ * Frame bits used in Honeywell RCWL300A, RCWL330A, Series 3, 5, 9 and all Decor Series Wireless Chimes
+ * 0000 0000 1111 1111 2222 2222 3333 3333 4444 4444 5555 5555
+ * 7654 3210 7654 3210 7654 3210 7654 3210 7654 3210 7654 3210
+ * XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XX.. XXX. .... KEY DATA (any change and receiver doesn't seem to recognize signal)
+ * XXXX XXXX XXXX XXXX XXXX .... .... .... .... .... .... .... KEY ID (different for each transmitter)
+ * .... .... .... .... .... 0000 00.. 0000 0000 00.. 000. .... KEY UNKNOWN 0 (always 0 in devices I've tested)
+ * .... .... .... .... .... .... ..XX .... .... .... .... .... DEVICE TYPE (10 = doorbell, 01 = PIR Motion sensor)
+ * .... .... .... .... .... .... .... .... .... ..XX ...X XXX. FLAG DATA (may be modified for possible effects on receiver)
+ * .... .... .... .... .... .... .... .... .... ..XX .... .... ALERT (00 = normal, 01 or 10 = right-left halo light pattern, 11 = full volume alarm)
+ * .... .... .... .... .... .... .... .... .... .... ...X .... SECRET KNOCK (0 = default, 1 if doorbell is pressed 3x rapidly)
+ * .... .... .... .... .... .... .... .... .... .... .... X... RELAY (1 if signal is a retransmission of a received transmission, only some models)
+ * .... .... .... .... .... .... .... .... .... .... .... .X.. FLAG UNKNOWN (0 = default, but 1 is accepted and I don't observe any effects)
+ * .... .... .... .... .... .... .... .... .... .... .... ..X. LOWBAT (1 if battery is low, receiver gives low battery alert)
+ * .... .... .... .... .... .... .... .... .... .... .... ...X PARITY (LSB of count of set bits in previous 47 bits)
+ * 
+ */
+
+    instance->generic.serial = (instance->generic.data >> 28) & 0xFFFFF;
+    switch((instance->generic.data >> 20) & 0x3) {
+    case 0x02:
+        instance->device_type = "Doorbell";
+        break;
+    case 0x01:
+        instance->device_type = "PIR-Motion";
+        break;
+    default:
+        instance->device_type = "Unknown";
+        break;
+    }
+
+    switch((instance->generic.data >> 16) & 0x3) {
+    case 0x00:
+        instance->alert = "Normal";
+        break;
+    case 0x01:
+    case 0x02:
+        instance->alert = "High";
+        break;
+    case 0x03:
+        instance->alert = "Full";
+        break;
+    default:
+        instance->alert = "Unknown";
+        break;
+    }
+
+    instance->secret_knock = (uint8_t)((instance->generic.data >> 4) & 0x1);
+    instance->relay = (uint8_t)((instance->generic.data >> 3) & 0x1);
+    instance->lowbat = (uint8_t)((instance->generic.data >> 1) & 0x1);
+}
+
+uint8_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context) {
+    furi_assert(context);
+    SubGhzProtocolDecoderHoneywell_WDB* instance = context;
+    return subghz_protocol_blocks_get_hash_data(
+        &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
+}
+
+bool subghz_protocol_decoder_honeywell_wdb_serialize(
+    void* context,
+    FlipperFormat* flipper_format,
+    SubGhzPresetDefinition* preset) {
+    furi_assert(context);
+    SubGhzProtocolDecoderHoneywell_WDB* instance = context;
+    return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
+}
+
+bool subghz_protocol_decoder_honeywell_wdb_deserialize(
+    void* context,
+    FlipperFormat* flipper_format) {
+    furi_assert(context);
+    SubGhzProtocolDecoderHoneywell_WDB* instance = context;
+    bool ret = false;
+    do {
+        if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
+            break;
+        }
+        if(instance->generic.data_count_bit !=
+           subghz_protocol_honeywell_wdb_const.min_count_bit_for_found) {
+            FURI_LOG_E(TAG, "Wrong number of bits in key");
+            break;
+        }
+        ret = true;
+    } while(false);
+    return ret;
+}
+
+void subghz_protocol_decoder_honeywell_wdb_get_string(void* context, string_t output) {
+    furi_assert(context);
+    SubGhzProtocolDecoderHoneywell_WDB* instance = context;
+    subghz_protocol_honeywell_wdb_check_remote_controller(instance);
+
+    string_cat_printf(
+        output,
+        "%s %dbit\r\n"
+        "Key:0x%lX%08lX\r\n"
+        "Sn:0x%05lX\r\n"
+        "DT:%s  Al:%s\r\n"
+        "SK:%01lX R:%01lX LBat:%01lX\r\n",
+        instance->generic.protocol_name,
+        instance->generic.data_count_bit,
+        (uint32_t)((instance->generic.data >> 32) & 0xFFFFFFFF),
+        (uint32_t)(instance->generic.data & 0xFFFFFFFF),
+        instance->generic.serial,
+        instance->device_type,
+        instance->alert,
+        instance->secret_knock,
+        instance->relay,
+        instance->lowbat);
+}

+ 111 - 0
lib/subghz/protocols/honeywell_wdb.h

@@ -0,0 +1,111 @@
+#pragma once
+
+#include "base.h"
+
+#define SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME "Honeywell"
+
+typedef struct SubGhzProtocolDecoderHoneywell_WDB SubGhzProtocolDecoderHoneywell_WDB;
+typedef struct SubGhzProtocolEncoderHoneywell_WDB SubGhzProtocolEncoderHoneywell_WDB;
+
+extern const SubGhzProtocolDecoder subghz_protocol_honeywell_wdb_decoder;
+extern const SubGhzProtocolEncoder subghz_protocol_honeywell_wdb_encoder;
+extern const SubGhzProtocol subghz_protocol_honeywell_wdb;
+
+/**
+ * Allocate SubGhzProtocolEncoderHoneywell_WDB.
+ * @param environment Pointer to a SubGhzEnvironment instance
+ * @return SubGhzProtocolEncoderHoneywell_WDB* pointer to a SubGhzProtocolEncoderHoneywell_WDB instance
+ */
+void* subghz_protocol_encoder_honeywell_wdb_alloc(SubGhzEnvironment* environment);
+
+/**
+ * Free SubGhzProtocolEncoderHoneywell_WDB.
+ * @param context Pointer to a SubGhzProtocolEncoderHoneywell_WDB instance
+ */
+void subghz_protocol_encoder_honeywell_wdb_free(void* context);
+
+/**
+ * Deserialize and generating an upload to send.
+ * @param context Pointer to a SubGhzProtocolEncoderHoneywell_WDB instance
+ * @param flipper_format Pointer to a FlipperFormat instance
+ * @return true On success
+ */
+bool subghz_protocol_encoder_honeywell_wdb_deserialize(
+    void* context,
+    FlipperFormat* flipper_format);
+
+/**
+ * Forced transmission stop.
+ * @param context Pointer to a SubGhzProtocolEncoderHoneywell_WDB instance
+ */
+void subghz_protocol_encoder_honeywell_wdb_stop(void* context);
+
+/**
+ * Getting the level and duration of the upload to be loaded into DMA.
+ * @param context Pointer to a SubGhzProtocolEncoderHoneywell_WDB instance
+ * @return LevelDuration 
+ */
+LevelDuration subghz_protocol_encoder_honeywell_wdb_yield(void* context);
+
+/**
+ * Allocate SubGhzProtocolDecoderHoneywell_WDB.
+ * @param environment Pointer to a SubGhzEnvironment instance
+ * @return SubGhzProtocolDecoderHoneywell_WDB* pointer to a SubGhzProtocolDecoderHoneywell_WDB instance
+ */
+void* subghz_protocol_decoder_honeywell_wdb_alloc(SubGhzEnvironment* environment);
+
+/**
+ * Free SubGhzProtocolDecoderHoneywell_WDB.
+ * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance
+ */
+void subghz_protocol_decoder_honeywell_wdb_free(void* context);
+
+/**
+ * Reset decoder SubGhzProtocolDecoderHoneywell_WDB.
+ * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance
+ */
+void subghz_protocol_decoder_honeywell_wdb_reset(void* context);
+
+/**
+ * Parse a raw sequence of levels and durations received from the air.
+ * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance
+ * @param level Signal level true-high false-low
+ * @param duration Duration of this level in, us
+ */
+void subghz_protocol_decoder_honeywell_wdb_feed(void* context, bool level, uint32_t duration);
+
+/**
+ * Getting the hash sum of the last randomly received parcel.
+ * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance
+ * @return hash Hash sum
+ */
+uint8_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context);
+
+/**
+ * Serialize data SubGhzProtocolDecoderHoneywell_WDB.
+ * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance
+ * @param flipper_format Pointer to a FlipperFormat instance
+ * @param preset The modulation on which the signal was received, SubGhzPresetDefinition
+ * @return true On success
+ */
+bool subghz_protocol_decoder_honeywell_wdb_serialize(
+    void* context,
+    FlipperFormat* flipper_format,
+    SubGhzPresetDefinition* preset);
+
+/**
+ * Deserialize data SubGhzProtocolDecoderHoneywell_WDB.
+ * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance
+ * @param flipper_format Pointer to a FlipperFormat instance
+ * @return true On success
+ */
+bool subghz_protocol_decoder_honeywell_wdb_deserialize(
+    void* context,
+    FlipperFormat* flipper_format);
+
+/**
+ * Getting a textual representation of the received data.
+ * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance
+ * @param output Resulting text
+ */
+void subghz_protocol_decoder_honeywell_wdb_get_string(void* context, string_t output);

+ 11 - 10
lib/subghz/protocols/registry.c

@@ -1,16 +1,17 @@
 #include "registry.h"
 
 const SubGhzProtocol* subghz_protocol_registry[] = {
-    &subghz_protocol_gate_tx,      &subghz_protocol_keeloq,      &subghz_protocol_star_line,
-    &subghz_protocol_nice_flo,     &subghz_protocol_came,        &subghz_protocol_faac_slh,
-    &subghz_protocol_nice_flor_s,  &subghz_protocol_came_twee,   &subghz_protocol_came_atomo,
-    &subghz_protocol_nero_sketch,  &subghz_protocol_ido,         &subghz_protocol_kia,
-    &subghz_protocol_hormann,      &subghz_protocol_nero_radio,  &subghz_protocol_somfy_telis,
-    &subghz_protocol_somfy_keytis, &subghz_protocol_scher_khan,  &subghz_protocol_princeton,
-    &subghz_protocol_raw,          &subghz_protocol_linear,      &subghz_protocol_secplus_v2,
-    &subghz_protocol_secplus_v1,   &subghz_protocol_megacode,    &subghz_protocol_holtek,
-    &subghz_protocol_chamb_code,   &subghz_protocol_power_smart, &subghz_protocol_marantec,
-    &subghz_protocol_bett,         &subghz_protocol_doitrand,    &subghz_protocol_phoenix_v2,
+    &subghz_protocol_gate_tx,       &subghz_protocol_keeloq,      &subghz_protocol_star_line,
+    &subghz_protocol_nice_flo,      &subghz_protocol_came,        &subghz_protocol_faac_slh,
+    &subghz_protocol_nice_flor_s,   &subghz_protocol_came_twee,   &subghz_protocol_came_atomo,
+    &subghz_protocol_nero_sketch,   &subghz_protocol_ido,         &subghz_protocol_kia,
+    &subghz_protocol_hormann,       &subghz_protocol_nero_radio,  &subghz_protocol_somfy_telis,
+    &subghz_protocol_somfy_keytis,  &subghz_protocol_scher_khan,  &subghz_protocol_princeton,
+    &subghz_protocol_raw,           &subghz_protocol_linear,      &subghz_protocol_secplus_v2,
+    &subghz_protocol_secplus_v1,    &subghz_protocol_megacode,    &subghz_protocol_holtek,
+    &subghz_protocol_chamb_code,    &subghz_protocol_power_smart, &subghz_protocol_marantec,
+    &subghz_protocol_bett,          &subghz_protocol_doitrand,    &subghz_protocol_phoenix_v2,
+    &subghz_protocol_honeywell_wdb,
 
 };
 

+ 1 - 0
lib/subghz/protocols/registry.h

@@ -32,6 +32,7 @@
 #include "bett.h"
 #include "doitrand.h"
 #include "phoenix_v2.h"
+#include "honeywell_wdb.h"
 
 /**
  * Registration by name SubGhzProtocol.

+ 1 - 1
lib/subghz/subghz_tx_rx_worker.c

@@ -237,7 +237,7 @@ bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) {
 
     instance->worker_running = true;
 
-    if(furi_hal_subghz_is_tx_allowed(frequency)) {
+    if(furi_hal_region_is_frequency_allowed(frequency)) {
         instance->frequency = frequency;
         res = true;
     }

+ 52 - 0
scripts/fwsize.py

@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+
+from flipper.app import App
+import subprocess
+import os
+import math
+
+
+class Main(App):
+    def init(self):
+        self.subparsers = self.parser.add_subparsers(help="sub-command help")
+
+        self.parser_elfsize = self.subparsers.add_parser("elf", help="Dump elf stats")
+        self.parser_elfsize.add_argument("elfname", action="store")
+        self.parser_elfsize.set_defaults(func=self.process_elf)
+
+        self.parser_binsize = self.subparsers.add_parser("bin", help="Dump bin stats")
+        self.parser_binsize.add_argument("binname", action="store")
+        self.parser_binsize.set_defaults(func=self.process_bin)
+
+    def process_elf(self):
+        all_sizes = subprocess.check_output(
+            ["arm-none-eabi-size", "-A", self.args.elfname], shell=False
+        )
+        all_sizes = all_sizes.splitlines()
+
+        sections_to_keep = (".text", ".rodata", ".data", ".bss", ".free_flash")
+        for line in all_sizes:
+            line = line.decode("utf-8")
+            parts = line.split()
+            if len(parts) != 3:
+                continue
+            section, size, _ = parts
+            if section not in sections_to_keep:
+                continue
+            print(f"{section:<11} {size:>8} ({(int(size)/1024):6.2f} K)")
+
+        return 0
+
+    def process_bin(self):
+        PAGE_SIZE = 4096
+        binsize = os.path.getsize(self.args.binname)
+        pages = math.ceil(binsize / PAGE_SIZE)
+        last_page_state = (binsize % PAGE_SIZE) * 100 / PAGE_SIZE
+        print(
+            f"{os.path.basename(self.args.binname):<11}: {pages:>4} flash pages (last page {last_page_state:.02f}% full)"
+        )
+        return 0
+
+
+if __name__ == "__main__":
+    Main()()

+ 1 - 0
scripts/otp.py

@@ -24,6 +24,7 @@ OTP_REGIONS = {
     "eu_ru": 0x01,
     "us_ca_au": 0x02,
     "jp": 0x03,
+    "world": 0x04,
 }
 
 OTP_DISPLAYS = {

+ 1 - 0
scripts/toolchain/fbtenv.cmd

@@ -31,6 +31,7 @@ if not "%REAL_TOOLCHAIN_VERSION%" == "%FLIPPER_TOOLCHAIN_VERSION%" (
 
 set "HOME=%USERPROFILE%"
 set "PYTHONHOME=%FBT_TOOLCHAIN_ROOT%\python"
+set "PYTHONPATH="
 set "PATH=%FBT_TOOLCHAIN_ROOT%\python;%FBT_TOOLCHAIN_ROOT%\bin;%FBT_TOOLCHAIN_ROOT%\protoc\bin;%FBT_TOOLCHAIN_ROOT%\openocd\bin;%PATH%"
 set "PROMPT=(fbt) %PROMPT%"
 

+ 6 - 0
site_scons/commandline.scons

@@ -174,6 +174,12 @@ vars.Add(
     default="update_default",
 )
 
+vars.Add(
+    "LOADER_AUTOSTART",
+    help="Application name to automatically run on Flipper boot",
+    default="",
+)
+
 
 vars.Add(
     "FIRMWARE_APPS",

+ 7 - 2
site_scons/fbt/appmanifest.py

@@ -200,8 +200,9 @@ class ApplicationsCGenerator:
         FlipperAppType.STARTUP: ("FlipperOnStartHook", "FLIPPER_ON_SYSTEM_START"),
     }
 
-    def __init__(self, buildset: AppBuildset):
+    def __init__(self, buildset: AppBuildset, autorun_app: str = ""):
         self.buildset = buildset
+        self.autorun = autorun_app
 
     def get_app_ep_forward(self, app: FlipperApplication):
         if app.apptype == FlipperAppType.STARTUP:
@@ -219,7 +220,11 @@ class ApplicationsCGenerator:
      .flags = {'|'.join(f"FlipperApplicationFlag{flag}" for flag in app.flags)} }}"""
 
     def generate(self):
-        contents = ['#include "applications.h"', "#include <assets_icons.h>"]
+        contents = [
+            '#include "applications.h"',
+            "#include <assets_icons.h>",
+            f'const char* FLIPPER_AUTORUN_APP_NAME = "{self.autorun}";',
+        ]
         for apptype in self.APP_TYPE_MAP:
             contents.extend(
                 map(self.get_app_ep_forward, self.buildset.get_apps_of_type(apptype))

+ 1 - 3
site_scons/site_tools/crosscc.py

@@ -7,7 +7,6 @@ from SCons.Tool import gnulink
 import strip
 import gdb
 import objdump
-import size
 
 from SCons.Action import _subproc
 import subprocess
@@ -38,7 +37,7 @@ def _get_tool_version(env, tool):
 
 
 def generate(env, **kw):
-    for orig_tool in (asm, gcc, gxx, ar, gnulink, strip, gdb, objdump, size):
+    for orig_tool in (asm, gcc, gxx, ar, gnulink, strip, gdb, objdump):
         orig_tool.generate(env)
     env.SetDefault(
         TOOLCHAIN_PREFIX=kw.get("toolchain_prefix"),
@@ -57,7 +56,6 @@ def generate(env, **kw):
             "GDB",
             "GDBPY",
             "OBJDUMP",
-            "SIZE",
         ],
     )
     # Call CC to check version

+ 1 - 1
site_scons/site_tools/fbt_apps.py

@@ -51,7 +51,7 @@ def DumpApplicationConfig(target, source, env):
 def build_apps_c(target, source, env):
     target_file_name = target[0].path
 
-    gen = ApplicationsCGenerator(env["APPBUILD"])
+    gen = ApplicationsCGenerator(env["APPBUILD"], env.subst("$LOADER_AUTOSTART"))
     with open(target_file_name, "w") as file:
         file.write(gen.generate())
 

+ 0 - 24
site_scons/site_tools/size.py

@@ -1,24 +0,0 @@
-from SCons.Builder import Builder
-from SCons.Action import Action
-
-
-def generate(env):
-    env.SetDefault(
-        SIZE="size",
-        SIZEFLAGS=[],
-        SIZECOM="$SIZE $SIZEFLAGS $TARGETS",
-    )
-    env.Append(
-        BUILDERS={
-            "ELFSize": Builder(
-                action=Action(
-                    "${SIZECOM}",
-                    "${SIZECOMSTR}",
-                ),
-            ),
-        }
-    )
-
-
-def exists(env):
-    return True

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